Stream: ideas

Topic: Build wasm modules without a platform


view this post on Zulip Oskar Hahn (Apr 09 2024 at 06:28):

I think the concept of Roc and the concept of Wasm share some similarities, that make then a perfect match.

In Wasm, you have modules, that run in a runtime. The module exports some functions and require, that the runtime exports some functions.

In Roc, you have an app, that requires a platform. The platform provides (currently only) one function and can call Effects/Tasks that are essentially functions.

You could say, that a Roc app is like a Wasm module and a Roc platform is like a Wasm runtime. The difference are, that in Roc, the app and the platform are combined at compile time and in Wasm, they are combined at initialization time.

If you currently want to build a Wasm module with Roc, you have to choose a Roc platform. In the case of a Wasm module, you probably want to define, which functions to export and which to import. But currently only the platform can do this. This means, if you want to call specific functions (Tasks) in your Wasm module, you also have to write your own Roc platform. But this is something, you should not need to do as a Roc developer. It would be nice, if you could just write a Roc app, that you can compile to a wasm module, without using any platform.

I think, this is possible. I created three examples of very simple Roc apps, that can be compiled to a Wasm module. They use a platform, that does nothing. I suggest a change, that the Roc compiler would not need that platform at all.

https://github.com/ostcar/roc-wasm-experiment

The examples show, that it is possible for JavaScript to do all the work of a Roc platform (memory management, defining complex Roc types like Str and providing Effects). This JavaScript code could be generated. As a Roc developer, you would write an Roc app and you would get a .wasm file and a .js file that you could use in the browser or any other Wasm runtime.

What I am proposing is, that there is a way of an app to say "I am a wasm module and I don't need a platform". For example like:

app "wasm"
    packages {
        pf: "wasm",
    }
    imports []
    provides [main] to pf

And I also propose, that such an app can define Effects to be used in Tasks.

What do you think? Is this in scope of the Roc project?

view this post on Zulip Brendan Hansknecht (Apr 09 2024 at 14:43):

How is this different from --lib? Just an implicit exposed spec?

view this post on Zulip Oskar Hahn (Apr 09 2024 at 17:31):

--lib does not work currently not work with --target=wasm32. It gets the error

error: wasm-ld: entry symbol not defined (pass --no-entry to suppress): _start
thread 'main' panicked at crates/compiler/build/src/program.rs:1043:17:
not yet implemented: gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code Some(1)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

But this is not the point. Even if it would work, you would still need to have a platform. Currently, an app needs a platform. If there is no generic wasm-platform, you have to write your own. Even, if it does nothing. There is no syntax for an app without a platform.

view this post on Zulip Brendan Hansknecht (Apr 09 2024 at 18:06):

I see. Makes sense

view this post on Zulip Brendan Hansknecht (Apr 09 2024 at 18:06):

In these cases, is the real platform in js or do you directly use the wasm some other way?

view this post on Zulip Oskar Hahn (Apr 09 2024 at 18:37):

Yes, JavaScript (or any other Wasm runtime) is the platform. The difference is, that Roc has no knowledge of the platform at compile time.

view this post on Zulip Brendan Hansknecht (Apr 09 2024 at 20:12):

Sounds good. I guess I would just propose making it platform: None or something like that. For non-wasm, We would make it just always generate an object file or shared library. Never do linking or generate a final executable

view this post on Zulip Brendan Hansknecht (Apr 09 2024 at 20:14):

Oh, one other note, long term, it would just return a tag union of tasks cause the effects module is going away. So may not be worth going through the effort to support it using the effects module (depending on how complex that ends up being, I honestly have no idea, may be trivial)

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:42):

And I also propose, that such an app can define Effects to be used in Tasks.

so I explored this a bunch when working on the module redesign. I wasn't thinking about wasm but rather the similar case where you're making an application that plugs into an existing code case, and the platform would be trivial and only used by 1 application ever

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:44):

if you're making a platform that will be used by multiple applications then naturally you need to have that code in a separate module, but if it's going to be tightly coupled to exactly 1 application then there's no innate need to put the platform code in a different module from the application code, and it would be more concise to put everything in one module

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:47):

however, all the attempts I made to come up with a design where you could do both in the same module ended up feeling more complicated and increasing the learning curve of the language to the point where I ended up backing away from that direction and going back to "you just have to put them in separate modules no matter what, and that's the one design that's used everywhere"

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:48):

it's definitely less convenient in the "1 application ever" subset of platforms, but so far I haven't seen a design that's both more convenient for that use case and yet still feels reasonably simple for the ecosystem as a whole

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:50):

that said, I'm still open to exploring the design space there

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:52):

I think it's great how today I can write a single .roc file and roc run it as a script, and I like the idea of finding some way to do the same thing but with a quick roc binary library - as in, i just write one .roc file and then run one roc command and end up with a dylib or .o file

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:53):

the tricky part is being able to specify that while also specifying what tasks it has available etc.

view this post on Zulip Richard Feldman (Apr 09 2024 at 20:59):

so if anyone has concrete design ideas to share about how that might look, I think the critical questions to answer would be:

view this post on Zulip Oskar Hahn (Apr 14 2024 at 08:13):

I think I solved this.

I though, that a roc program consists of two parts: the app and the platform. But when I understand it correctly, there are actually three parts: The app the platform and the host, where the platform is the *.roc-files and the host is the part, written in another language.

After realizing this, I think, the goal is not to build a wasm module without a platform, but a wasm module, without a host. So you still define the function to export and the tasks/effects to import in a platform-module. But you don't need zig or a dynhost.

This is already possible. I updated the example repo to demonstrate it.

To build a wasm module without a host, you have to call:

roc build --target=wasm32 --no-link --output /tmp/main.o
wasm-ld /tmp/main.o -o main.wasm --no-entry --export-dynamic --strip-all --import-undefined

The only dependency is wasm-ld. You don't need zig or wasi-libc. So this solves #5585 and #5573

As you can see in the example, it is not necessary to put the platform files inside a separate directory. You can call the platform platform.roc and have it next to the main.roc. In the Example3 it was also possible to put the hosted module Effect.roc in the same directory.

I am satisfied. Now, this is more like a "show and tell" then an "idea" :sweat_smile:


Last updated: Jun 16 2026 at 16:19 UTC