Stream: general

Topic: Implementing a wasm REPL


view this post on Zulip Philippe Vaillancourt (Dec 31 2023 at 19:20):

So I understand how to instantiate a wasm module in JS. But let's say I wanted to implement a WAT REPL, what would my options be to interactively execute the user provided wat code? And output the result of that execution.

view this post on Zulip Philippe Vaillancourt (Jan 01 2024 at 11:53):

I've been doing a bit of research into how other "compiled to IL" languages do it and this led me to the .NET Roslyn Scripting API. https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-CSharp-Code-Compilation-Revisited-for-Roslyn#roslyn-scripting-apis

It looks more complicated than I had hoped, compared with doing it with a simple interpreter. From what I understand, every snippet of code is basically wrapped in an assembly and that assembly is dynamically linked into the "main" REPL assembly. I haven't gone as far as trying to tease out how it's implemented in code but that seems like the gist of it.

How would this mechanism translate to WebAssembly? I guess you would somehow need to create a new module instance for each new snippet of code that is typed in the repl. But the mechanism for making all these modules behave like they are all one big happy family eludes me. I know you can import and export functions from modules, which would allow the host to pipe one function from one module into another module. But that is done at compile time, isn't it? You need to know the function names.

How would one achieve this at runtime, dynamically. Sharing Memory?

Recently I needed to update my scripting tools that are integrated into Markdown Monster, in order to support .NET Core. The old CodeDom compiler APIs I'd been using as part of my `Westwind.Scripting` library aren't supported in Core and so I ended up updating to the newer Roslyn CodeAnalysis APIs. In this post I discuss how the Roslyn compilation APIs work, and how I built and updated the Westwind.Scripting library to provide an easy to use wrapper around these tools.

view this post on Zulip Karel Hrkal (kajacx) (Jan 02 2024 at 10:13):

The Rust wat crate can translate WAT into a WebAssembly binary bytes. You can (relatively easily) expose this function to JS code with wasm-bindgen and then you can give the bytes to the browser as usual.

This could work well if you "just" wanted to call an exported method where the user implements the method, provides the arguments for it and gets the result. If you have more complicated code (for example an entire game where the user-provided functionality is called like a plugin), then you would probably need to add a WASM runtime to your code that would load and run the user-provided code. I can elaborate if that's what you want.

Facilitating high-level interactions between Wasm modules and JavaScript - GitHub - rustwasm/wasm-bindgen: Facilitating high-level interactions between Wasm modules and JavaScript

view this post on Zulip Karel Hrkal (kajacx) (Jan 02 2024 at 11:58):

There is also something called wepl (WebAssembly REPL), but that seems to be a command line application.

view this post on Zulip Philippe Vaillancourt (Jan 02 2024 at 13:17):

@Karel Hrkal (kajacx) yeah, I would want more than that. For context, what I want to do is build a compiler/interpreter pipeline that takes Tiny-BASIC source code, compiles it to wasm and runs it using wasmtime. As part of that project, I wanted to provide a Tiny-BASIC REPL that plugs-in to this pipeline.

Having done a bit more research into it, I think that what languages like C# and Java, which have an AOT compilation step to IL before handing the IL over to their VM, do in order to provide a REPL is basically create a new assembly that wraps every input from the REPL. They use some sort of context to keep track of the state of the REPL session (mainly, the global and local variables declared and assigned to) and inject that into each new assembly.

So, if I'm thinking about emulating such a mechanism for my Tiny-BASIC compiler REPL, I would basically have the REPL host compile and instantiate a new wasm module for each input coming in from the terminal, somehow injecting the global and local variables held in the REPL context, along with the public API (exported function, shared memory etc.) of the already loaded wasm modules, and then executing that module.

At least, that's kind of how I'm conceptualizing this in my head at the moment.

view this post on Zulip Ryan Levick (rylev) (Jan 02 2024 at 13:23):

You may be interested in my Project WEPL which is intended as a Wasm Component repl: https://github.com/rylev/wepl

A repl for WebAssembly Components. Contribute to rylev/wepl development by creating an account on GitHub.

view this post on Zulip Philippe Vaillancourt (Jan 02 2024 at 13:42):

Ryan Levick (rylev) said:

You may be interested in my Project WEPL which is intended as a Wasm Component repl: https://github.com/rylev/wepl

Looks interesting. I'll take it for a spin. Is it a JS REPL?

view this post on Zulip Karel Hrkal (kajacx) (Jan 02 2024 at 13:57):

@Philippe Vaillancourt Ok, can you explain what would I, an user of the finished project that you are thinking of, actually do? Would I open a website with text area where I would write my in Tiny-BASIC, then hit "Run" and it would run the code? Would it be entirely on client-side, or is compilation on a server an option? Or would it be a console application?

I am not sure how you would want it to work, so I cannot help you make it unless I understand what you want to make. Explain it to me like you would explain it to someone who has never seen it before and wants to use it.

view this post on Zulip Philippe Vaillancourt (Jan 15 2024 at 16:23):

@Karel Hrkal (kajacx) I was planning to make it a console application.

The same way you can just type node from the command line and it starts a REPL. That's the kind of experience I want for the user.
image.png

view this post on Zulip Karel Hrkal (kajacx) (Jan 27 2024 at 15:27):

In that case, you probably want to read from and write to the terminal with WASI. I would recommend to write some minimal example in rust and see what it compiles to and then go from there. There is a lot of "background" knowledge to get first - how to crate a wasi component, how to convert the wasm binary format to the wat text format, how the wasm instructions work, how to convert the wat text format back to binary and then how to actually run that wasi component, and I don't have all the links to tutorials on how to do these things on hand unfortunately.


Last updated: Dec 23 2024 at 12:05 UTC