Stream: beginners

Topic: How to WASM


view this post on Zulip Luke Boswell (Apr 23 2023 at 04:11):

I'm trying to get Roc to generate a .wasm file. I've been following the instructions in examples/platform-switching/web-assembly-platform/README.md but I can't seem to get anything to work. I'm not really sure where I'm going wrong.

I would love to be able to generate a valid WASM file, and potentially even something that could be used with WASI. I appreciate that there probably aren't many platforms yet, and we don't have RocGlue for it, but I'm interested in working on this.

I've thought it might be fun to try and make something work for WCGI. But don't have much experience, and I'm thinking it may be too big a project for me but worth giving it a go.

$ cargo run -- build --target wasm32 examples/platform-switching/rocLovesWebAssembly.roc
   Compiling palette v0.6.1
   Compiling signal-hook v0.3.15
   Compiling pulldown-cmark v0.9.2
   Compiling roc_cli v0.0.1 (/Users/luke/Documents/GitHub/roc/crates/cli)
   Compiling roc_repl_expect v0.0.1 (/Users/luke/Documents/GitHub/roc/crates/repl_expect)
   Compiling roc_code_markup v0.0.1 (/Users/luke/Documents/GitHub/roc/crates/code_markup)
   Compiling roc_docs v0.0.1 (/Users/luke/Documents/GitHub/roc/crates/docs)
    Finished dev [unoptimized + debuginfo] target(s) in 8.27s
     Running `target/debug/roc build --target wasm32 examples/platform-switching/rocLovesWebAssembly.roc`
🔨 Rebuilding platform...
An internal compiler expectation was broken.
This is definitely a compiler bug.
Please file an issue here: https://github.com/roc-lang/roc/issues/new/choose
thread '<unnamed>' panicked at 'Error:
    Failed to rebuild host.zig:
        The executed command was:
            zig build-obj examples/platform-switching/web-assembly-platform/host.zig -femit-llvm-ir=examples/platform-switching/web-assembly-platform/main.bc --pkg-begin str crates/compiler/builtins/bitcode/src/str.zig --pkg-end --library c -target i386-linux-musl -fPIC --strip
        stderr of that command:
            ./examples/platform-switching/web-assembly-platform/host.zig:7:9: error: This platform is for WebAssembly only. You need to pass `--target wasm32` to the Roc compiler.
        @compileError("This platform is for WebAssembly only. You need to pass `--target wasm32` to the Roc compiler.");
        ^
', crates/compiler/build/src/link.rs:1493:21
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'Failed to (re)build platform.: Any { .. }', crates/compiler/build/src/program.rs:941:46

view this post on Zulip Luke Boswell (Apr 23 2023 at 04:41):

I think I might be using the wrong version of zig. Just going to build 0.9.1 from source and see if that helps. didn't help

view this post on Zulip Luke Boswell (Apr 23 2023 at 06:56):

It seems to think the target is i386-linux-musl. Using --target wasm32 doesn't seem to do anything. Indeed when I hardcode it to Target::Wasm32 in crates/cli/src/main.rs this doesn't change the arguments to zig build.

So I searched for where that might be and have found this in crates/compiler/build/src/link.rs which I can hardcode to wasm32-wasi but then I get a successful build with a series of errors like below when it tries to link with ld-temp.o.

let zig_target = if matches!(opt_level, OptLevel::Development) {
    "wasm32-wasi"
} else {
    // For LLVM backend wasm we are emitting a .bc file anyway so this target is OK
    "i386-linux-musl"
    // ^^^^^^^^^^^^^ If I change this to "wasm32-wasi" I get a sucessful build the below errors.
};
wasm-ld: warning: Linking two modules of different target triples: '/Users/luke/
Documents/GitHub/roc/target/debug/build/wasi_libc_sys-5e2583e767e30111/out/wasi-
libc.a/Users/luke/Documents/GitHub/roc/target/debug/build/wasi_libc_sys-5e2583e7
67e30111/out/zig-cache/o/4811dec3fd38e36f9330ff74e399d7a1/memset.o' is
'wasm32-unknown-wasi-musl' whereas 'ld-temp.o' is 'wasm32-unknown-unknown-unknown'

view this post on Zulip Luke Boswell (Apr 23 2023 at 07:57):

Ok, so I've realised that I have this working now. They are just compiler warnings that I have above. Only realised after manually compiling the host using zig. I had to fix a minor bug in host.zig to get it to compile. Modified some things... and now I have a roc platform that runs with wasmer.

% wasmer run examples/platform-switching/rocLovesWebAssembly.wasm
Hello, from Zig!

view this post on Zulip Luke Boswell (Apr 23 2023 at 08:11):

Is it possible to target wasm using a Rust platform at the moment?

view this post on Zulip Brian Carroll (Apr 23 2023 at 08:26):

I'm not sure of the current status. I do remember at some point in the past the compiler's build system only supported Zig platforms. That goes back to our first Wasm examples with llvm. I'm not sure if that restriction has been lifted since then. I did a lot of the Wasm support but not this part. @Folkert de Vries might know more about it.

view this post on Zulip Brian Carroll (Apr 23 2023 at 08:32):

We'd need a specific Rust platform to work on. I don't think one exists right now.

view this post on Zulip Brian Carroll (Apr 23 2023 at 08:39):

Also note that Roc glue exists at the language level (Rust/Zig), not the instruction set level (x86_64/Wasm).

view this post on Zulip Brian Carroll (Apr 23 2023 at 08:39):

Rust glue does support the Wasm target as far as I know

view this post on Zulip Luke Boswell (Apr 23 2023 at 09:38):

Super. Looking forward to building a rust platform for wasm with glue. I might need a minimal example and then I should be good to add more features. I think Brendan is working on an example using glue, not sure if he's thinking wasm though.

view this post on Zulip Folkert de Vries (Apr 23 2023 at 12:23):

we do try to generate glue for wasm, see

#[cfg(target_arch = "arm")]
mod arm;
#[cfg(target_arch = "arm")]
pub use arm::*;
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use aarch64::*;
#[cfg(target_arch = "wasm32")]
mod wasm32;
#[cfg(target_arch = "wasm32")]
pub use wasm32::*;
#[cfg(target_arch = "x86")]
mod x86;
#[cfg(target_arch = "x86")]
pub use x86::*;
#[cfg(target_arch = "x86_64")]
mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::*;

but I don't think that's been tested in any way so far

view this post on Zulip Luke Boswell (Apr 25 2023 at 04:11):

Status: Blocked - I think I need assistance, still trying to figure it out.

Goal - Compile Minimal Rust platform to WASM

Get a minimal example that compiles a Roc platform/app to a WASM file for e.g. a Netlify serverless function. Basically I just want to use STDIO and STDOUT for request/response, and make an Echo example.

Basic concept is something like mainForHost : List U8 -> Str or similar, with a platform lib.rs maybe something like this.

use std::io::{self, Read};

fn main() {
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  let mut roc_str = RocStr::from_slice_unchecked(&mut buf);
  unsafe { roc_main(&mut roc_str) };

  io::stdout().write_all(roc_str.as_bytes()).unwrap();
  io::stdout().flush().unwrap();
}

Step 1 - Compile library (SUCCESS)

Using examples/platform-switching/rust-platform as a starting point I get.

% roc build --target wasm32 --no-link rocLovesRust.roc
0 errors and 0 warnings found in 36 ms while successfully building:

    rocLovesRust.o

Step 2 - Link WASM file (SUCCESS)

% wasm-ld --no-entry --export-dynamic --allow-undefined -o rocLovesRust.wasm rocLovesRust.o

Step 3 - Run WASM file (FAIL) :sadface:

% wasmer rocLovesRust.wasm
error: failed to run `rocLovesRust.wasm`
╰─▶ 1: Error while importing "env"."__udivti3": unknown import. Expected Function(FunctionType { params: [I32, I64, I64, I64, I64], results: [] })

When I ask ChatGPT how to fix it I get this.

The error you're encountering indicates that the WebAssembly module you generated is trying to import a function named __udivti3 from the "env" namespace, but it can't find it. The __udivti3 function is a compiler-generated helper function for 128-bit unsigned integer division. It is part of the compiler-rt library, which provides runtime support for various compiler-generated functions. To resolve this issue, you need to link your Rust code with the appropriate wasm32 version of the compiler-rt library.

And this is where I get stuck.

Any ideas on how to get rust to link compiler-rt library in would be most appreciated.

view this post on Zulip Luke Boswell (Apr 25 2023 at 05:35):

I tried another method and created a rust project for the sole purpose of compiling the missing functions into .o files against the wasm32-unknown-unknown target, and then tried linking them using wasm-ld, but it still doesn't seem to work. I really don't have much experience here, so this is probably a crazy idea. Sharing in case this helps.

% wasm-ld --no-entry --export-dynamic -o rocLovesRust.wasm rocLovesRust.o ./udivmodti4.o ./udivti3.o ./libcompiler-rt.a
wasm-ld: error: lto.tmp: undefined symbol: __udivti3
wasm-ld: error: lto.tmp: undefined symbol: __udivti3
wasm-ld: error: lto.tmp: undefined symbol: __multi3

view this post on Zulip Luke Boswell (Apr 25 2023 at 09:03):

I've pivoted to just use the Zig platform. It works really well. I hope to polish it a little and hopefully have a working example for the next meetup. Just a few (maybe just one) compiler issues to fix so this can be used by anyone using a nightly release and a URL platform. :smiley:

view this post on Zulip Luke Boswell (Apr 25 2023 at 11:17):

The following builds WASM ok with roc build --target wasm32 examples/echo.roc, and also runs well using % wasmer run examples/echo.wasm, except it prints out hello, world!% instead of what I expect it to which is Goodbye!%. I'm pretty confident it is a combination of my lack of Zig, and not quite understanding how we call the exposed Roc functions.

Looking for any pointers to get Roc to take the list of bytes in and return the modified list back.

platform "serverless"
    requires {} { main : List U8 -> List U8 }
    exposes []
    packages {}
    imports []
    provides [mainForHost]

mainForHost : List U8 -> List U8
mainForHost = main
// <-- other Roc related definitions above here removed for brevity -->

pub extern fn roc__mainForHost_1_exposed_generic(ret: *RocList) void;

pub fn main() !void {

    const stdout = std.io.getStdOut().writer();

    const helloStr = "hello, world!";
    var str = RocList.fromSlice(u8, helloStr[0..]);

    roc__mainForHost_1_exposed_generic(&str);

    const maybeBytes = str.elements(u8);
    const bytes = maybeBytes orelse {
        std.debug.print("Error getting elements\n", .{});
        std.process.exit(1);
    };

    const array_len: usize = str.len();
    const byteSlice = bytes[0..array_len];

    try stdout.writeAll(byteSlice); // Write the slice to stdout

}
app "echo"
    packages { pf: "../platform/main.roc" }
    imports []
    provides [main] to pf

main = \_ ->
    Str.toUtf8 "Goodbye!\n"

view this post on Zulip Folkert de Vries (Apr 25 2023 at 11:18):

the signature of pub extern fn roc__mainForHost_1_exposed_generic(ret: *RocList) void; needs two arguments

view this post on Zulip Folkert de Vries (Apr 25 2023 at 11:18):

first the input string, and then a pointer to store the output into

view this post on Zulip Luke Boswell (Apr 25 2023 at 11:27):

Thank you. I thought that might be the case, but then I get the following error which I have trouble understanding or debugging. There is only one place were I define roc__mainForHost_1_exposed_generic so I don't think that is causing it.

wasm-ld: warning: function signature mismatch: roc__mainForHost_1_exposed_generic
>>> defined as (i32, i32) -> void in /var/folders/48/39th9k0n0wdcj18k3yhm_g5c0000gn/T/roc_appNrHux0.o
>>> defined as (i32) -> void in lto.tmp

0 errors and 0 warnings found in 427 ms while successfully building:

    examples/echo.wasm
luke@192-168-1-104 roc-serverless % wasmer run examples/echo.wasm
error: failed to run `examples/echo.wasm`
│   1: RuntimeError: unreachable
╰─▶ 2: unreachable
pub extern fn roc__mainForHost_1_exposed_generic(arg: *RocList, ret: *RocList) void;

pub fn main() !void {

    const stdout = std.io.getStdOut().writer();

    const helloStr = "hello, world!";
    var arg = RocList.fromSlice(u8, helloStr[0..]);
    var ret = RocList.empty();

    roc__mainForHost_1_exposed_generic(&arg, &ret);

    const maybeBytes = ret.elements(u8);
    const bytes = maybeBytes orelse {
        std.debug.print("Error getting elements\n", .{});
        std.process.exit(1);
    };

    const array_len: usize = ret.len();
    const byteSlice = bytes[0..array_len];

    try stdout.writeAll(byteSlice); // Write the slice to stdout

}

view this post on Zulip Luke Boswell (Apr 25 2023 at 11:29):

Specifically, any idea where the defined as (i32) -> void in lto.tmp might be referring to?

view this post on Zulip Folkert de Vries (Apr 25 2023 at 11:56):

ah, ok

view this post on Zulip Folkert de Vries (Apr 25 2023 at 11:58):

that signature is just wrong then. i'm not sure how the wasm backend generates them

view this post on Zulip Folkert de Vries (Apr 25 2023 at 11:58):

you might have more luck if you try to build the roc app with --optimize. that ll use the llvm wasm backend

view this post on Zulip Folkert de Vries (Apr 25 2023 at 11:58):

just to figure out if that is the issue

view this post on Zulip Luke Boswell (Apr 25 2023 at 20:36):

Unfortunately, that didn't change anything. I still get the same error with --optimize :sad:

view this post on Zulip Brendan Hansknecht (Apr 25 2023 at 21:07):

Is your code pushed to a branch or repo somewhere? Would be nice to be able to look at and play with it.

view this post on Zulip Luke Boswell (Apr 25 2023 at 21:51):

Ok, I'll do that. Just out for a few minutes.

view this post on Zulip Luke Boswell (Apr 25 2023 at 22:39):

Here is the platform roc-serverless, it doesn't build as is due to the zig builtins issue we've been talking about. I have it working with a local modification in my roc cli so that it knows where to find the builtins. I pushed an update with the builtins locally, so it does build and reproduce the WASM error above.

view this post on Zulip Brian Carroll (Apr 25 2023 at 22:42):

To me this is using the LLVM backend. That is the default, you don't have to specify --optimize. Rather, you need --dev to get the dev one. And the dev backend does not use wasm-ld, it has its own built-in linker.

view this post on Zulip Brian Carroll (Apr 25 2023 at 22:43):

At a guess, lto.tmp sounds like a temporary file used for link time optimization by wasm-ld. Again, I'm guessing!

view this post on Zulip Brian Carroll (Apr 25 2023 at 22:43):

No idea how it would have two different signatures though

view this post on Zulip Brian Carroll (Apr 25 2023 at 22:47):

Maybe try it with --dev and see what happens!

view this post on Zulip Luke Boswell (Apr 25 2023 at 22:51):

I forgot I had another change I had to make in the compiler to get this to run. Added to README.

view this post on Zulip Luke Boswell (Apr 25 2023 at 22:53):

I tried with --dev but that still gives the the same function type mismatch. Btw this was using % cargo run -- build --target wasm32 --dev ../roc-serverless/examples/echo.roc to build it. It gives

<-- other warnings removed -->

wasm-ld: warning: Linking two modules of different target triples: '/Users/luke/Documents/GitHub/roc/target/debug/build/wasi_libc_sys-5e2583e767e30111/out/wasi-libc.a/Users/luke/Documents/GitHub/roc/target/debug/build/wasi_libc_sys-5e2583e767e30111/out/zig-cache/o/ea7f435e8379892b4b98d80660f9fbc9/memset.o' is 'wasm32-unknown-wasi-musl' whereas 'ld-temp.o' is 'wasm32-unknown-unknown-unknown'


wasm-ld: warning: function signature mismatch: roc__mainForHost_1_exposed_generic
>>> defined as (i32, i32) -> void in /var/folders/48/39th9k0n0wdcj18k3yhm_g5c0000gn/T/roc_appf32a4I.o
>>> defined as (i32) -> void in lto.tmp

view this post on Zulip Luke Boswell (Apr 25 2023 at 22:57):

Btw the other change is

First, change "i386-linux-musl" to "wasm32-wasi" on line 373 in crates/compiler/build/src/link.rs

Which leads me to wonder if there is an issue in cli where it doesn't recognise --dev when we have --target wasm32

view this post on Zulip Luke Boswell (Apr 25 2023 at 23:18):

How can I confirm if it is using the dev backend and not LLVM?

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 00:39):

Based on the source code here, it looks like wasm does ignore the --dev flag.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 00:40):

Though it also looks like it would always use the dev wasm and never llvm

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 00:40):

So definitely a bit confused by that

view this post on Zulip Luke Boswell (Apr 26 2023 at 00:59):

I think I found it, if I change BuildConfig::BuildAndRunIfNoErrors to BuildConfig::BuildOnly in cli/src/lib.rs then it builds correctly using the dev backend, and not wasm-ld

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:06):

Actually, I think that just changes the linking_strategy from LinkingStrategy::Legacy to LinkingStrategy::Additive which builds without errors, but then there is an issue running the wasm, which I think may because it doesn't actually add the app into it or something.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:07):

Interesting. I think this is the issue. We always call llvm when the backend is set to wasm: https://github.com/roc-lang/roc/blob/ffe30af2167385ad75cb89c073608166df18b0e7/crates/compiler/build/src/program.rs#L116-L124

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:11):

Changing it to works;

CodeGenBackend::Wasm => {
            gen_from_mono_module_dev_wasm32(arena, loaded, preprocessed_host_path, wasm_dev_stack_bytes)
        }

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:14):

Yeah, that actually makes sense, cause other stuff is not expecting the development backend and is using the wrong file becuase it is matching what the llvm backend wants for files.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:15):

You also need to update this, which is another bug/discrepency: https://github.com/roc-lang/roc/blob/ffe30af2167385ad75cb89c073608166df18b0e7/crates/cli/src/lib.rs#L606-L616

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:15):

to:

        match (
            matches.is_present(FLAG_OPTIMIZE),
            matches.is_present(FLAG_OPT_SIZE),
            matches.is_present(FLAG_DEV),
        ) {
            (true, false, false) => OptLevel::Optimize,
            (false, true, false) => OptLevel::Size,
            (false, false, true) => OptLevel::Development,
            (false, false, false) => OptLevel::Normal,
            _ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
        }

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:15):

Then with --dev, it seems to compile fine.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:15):

Not sure if it works though

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:16):

So yeah, some of the wasm pipelining seems to have been messed up over time.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:16):

Not sure what is breaking with the llvm backend though. That is a separate issue.

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:17):

So that gives me a .o file instead of a .wasm file now.

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:20):

% cargo run -- build --target wasm32 --dev ../roc-serverless/examples/echo.roc
 🔨 Rebuilding platform...
0 errors and 0 warnings found in 394 ms while successfully building:

    ../roc-serverless/examples/echo.o

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:20):

I'm not really sure, but fundamentally, we need to rework these pipelines.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:21):

Also, I think we need to make two different targets for wasm32, one for browser, and one for wasi.

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:22):

Also, echo.o may just be a misnaming of echo.wasm. Assuming the wasm dev backend generated correctly, I think it was just filling in whatever file name it was given.

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:25):

Yeah, unfortunately that doesn't seem to be the case.

% wasmer validate examples/echo.o
error: failed to validate `examples/echo.o`
╰─▶ 1: Validation error: unexpected end-of-file (at offset 0x0)

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:26):

haha. ok

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:32):

So if I comment out the wasm_module.eliminate_dead_code(env.arena, called_fns); in build_app_binary in crates/compiler/gen_wasm/src/lib.rs is does generate a valid wasm filed, named echo.o. But then I have this issue;

% wasmer validate examples/echo.o
Validation passed for `examples/echo.o`.
% wasmer run examples/echo.o
error: failed to run `examples/echo.o`
│   1: failed to instantiate WASI module
│   2: Instantiation failed
╰─▶ 3: Error while importing "env"."roc_panic": unknown import. Expected Function(FunctionType { params: [I32, I32], results: [] })

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:35):

So I think I'm almost there, it looks good in the wasm module. I just don't have a zig implementation for roc_panic I think. I'll see if I can figure that out.

view this post on Zulip Luke Boswell (Apr 26 2023 at 01:49):

Adding roc_panic like this, and now I have a different issue.

export fn roc_panic(_: *anyopaque, _: *anyopaque) void {
    // do something?
}
% wasmer run examples/echo.o
error: failed to run `examples/echo.o`
│   1: failed to instantiate WASI module
│   2: Instantiation failed
╰─▶ 3: Error while importing "env"."roc__mainForHost_1_exposed_generic": unknown import. Expected Function(FunctionType { params: [I32, I32], results: [] })

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:57):

Can you switch away from the generic version of the function and to the concrete one

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:57):

Fundamentally should be something like roc__mainForHost_1_exposed(arg: *RocList) *RocList

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 01:58):

Maybe dev wasm doesn't generate the generic version...not sure though

view this post on Zulip Luke Boswell (Apr 26 2023 at 02:03):

Hmmm, not sure about that.

...
pub extern fn roc__mainForHost_1_exposed(arg: *RocList) *RocList;

pub fn main() !void {
    ...
    var arg = RocList.fromSlice(u8, helloStr[0..]);
    var ret: *RocList = roc__mainForHost_1_exposed(&arg);
% wasmer run examples/echo.o
error: failed to run `examples/echo.o`
╰─▶ 1: RuntimeError: Parameters of type [] did not match signature [F64, I64, I64] -> [F64]

view this post on Zulip Luke Boswell (Apr 26 2023 at 02:05):

It might have worked actually, the issue now might be my main.

Exports:
  Functions:
    "_start": [F64, I64, I64] -> [F64]

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 02:07):

Awesome! Though i have no ideas for that error.

view this post on Zulip Luke Boswell (Apr 26 2023 at 02:23):

Yeah, trying to figure this one out is tricky. I think we've got the right code for working with Roc, and my issue is purely WASI specific now. Start should be "_start": [] -> []

view this post on Zulip Brian Carroll (Apr 26 2023 at 05:32):

Oh that's right I never implemented that for the generic main. Never had an example of it.

view this post on Zulip Luke Boswell (Apr 26 2023 at 06:59):

Ok, I think I've exhausted up to the limit of my knowledge for now.

I've reverted back to the simpler main : Str for the platform (as in the rocLovesWasm example) and want to focus on integrating with a cloud. I've uncovered a some issues here, and I'm not confident enough to say which of these need to be fixed or investigated further. I'm happy to log issues for anything I've mentioned above we want to keep track of, just need a pointer here and there.

For Rust Host -> WASM it seems the main issue is that we only support generating WASM when we have a host.zig host. All the Rust examples have a host.c host. I have an example Rust platform that I've added a host.zig to. I'm reasonably confident it should work, cargo check is happy etc, and it generates valid wasm, however it doesn't seem to build and then link in any of the Rust (and by extension the Roc) functions into the end file.

For Zig Host -> WASM there are a few issues in the build tooling which I've discussed above. If these issues are worked around with manual compiler patches, we end up with _start not being generated correctly which is the standard entry for WASI. Brendan suggested we need a separate target for WASI; I feel like a Roc app will always have a main so we can always support WASI without issues.

view this post on Zulip Luke Boswell (Apr 26 2023 at 07:02):

I've pushed the latest I have to github and updated README to work using current main, in case anyone would like to see.

view this post on Zulip Brian Carroll (Apr 26 2023 at 11:11):

I'll make a few comments that some people might know already but others might not.
The _start function is not unique to WASI or Wasm, native binaries have the same thing. When you write a C program with a int main(int argc, char** argv), the C compiler inserts code to call main from a _start function that is usually written in assembly and provided by libc. It sets up the stack and the values of argc and argv and then calls main.
https://embeddedartistry.com/blog/2019/04/08/a-general-overview-of-what-happens-before-main/
The Roc entry point main is of course not the overall entry point for the whole binary. The real main is in Zig or Rust or whatever, and that is the one that should be called from _start. _start normally has no arguments. I can't remember if it returns an exit code as i32, or if it returns void.
In the virtual DOM platform that I started work on a while ago (and is now on hold) the client side Zig platform has no main or _start because we are running in a browser, not an OS, and there are no command line arguments etc.
Similarly if Roc is being used to generate a plugin for a game engine or something, we will compile to an object file or dynamic library rather than an executable, and it will have no main.

view this post on Zulip Richard Feldman (Apr 26 2023 at 13:06):

do we actually need wasm-ld anymore?

view this post on Zulip Richard Feldman (Apr 26 2023 at 13:06):

now that we have a surgical linking implementation

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 15:27):

Looking at the current code, wasm-ld is used in a few places, all driven by zig. It is used for compiling the host, preprocessing the host, and linking if not using the additive linker.

view this post on Zulip Richard Feldman (Apr 26 2023 at 20:08):

gotcha - but if we switched to always use the additive linker for compiling Roc application code, then it would only be used on the host and that's it?

view this post on Zulip Brendan Hansknecht (Apr 26 2023 at 21:01):

Yeah, I think so. Would only need it for building and preprocessing the host. That said, no idea how it works and what it would take to use with the llvm backend. No idea if any part of it is special.

view this post on Zulip Brian Carroll (Apr 28 2023 at 22:13):

Not sure what you mean by special, but can't think of anything unusual about it.

But as I mentioned in another thread recently, the surgical / additive linking is quite specific to the Dev Wasm backend. It is not a general purpose linker like wasm-ld is. It could be developed into something more but why? Wasm ld does what we need.

view this post on Zulip Brian Carroll (Apr 28 2023 at 22:13):

We need it to create a preprocessed host

view this post on Zulip Brian Carroll (Apr 28 2023 at 22:18):

Also my linking code will not work for llvm

view this post on Zulip Richard Feldman (Apr 28 2023 at 23:11):

well ultimately the goal in general (not just for wasm, but all backends) is that:

view this post on Zulip Brian Carroll (Apr 29 2023 at 05:11):

Ok so if we always start from a preprocessed host then a platform author needs wasm-ld to create that.

view this post on Zulip Richard Feldman (Apr 29 2023 at 05:56):

oh yeah platform authors will totally need third-party tools to create preprocessed hosts :big_smile:

view this post on Zulip Richard Feldman (Apr 29 2023 at 05:56):

but that's already unavoidable because they need some other language besides Roc to create the host anyway

view this post on Zulip Richard Feldman (Apr 29 2023 at 05:57):

so really I should have said application authors shouldn't need anything other than roc to use it

view this post on Zulip Brian Carroll (Apr 29 2023 at 06:48):

Richard Feldman said:

do we actually need wasm-ld anymore?

Ok so back to this question, the answer is that the llvm backend needs it because our surgical linking doesn't work with that.
And we also need it to build any of our official platforms or example platforms.

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:02):

I got an example working on NodeJS via wasm - https://github.com/roc-lang/roc/pull/5346 - but I got stuck trying to figure out how to pass a NodeJS string into wasm :sweat_smile:

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:03):

anyone with wasm knowledge know how to go about doing that? I basically want to have main.roc give a Str -> Str function instead of a Str, and then call that from JS passing a JS string converted to a wasm representation that Roc can use

view this post on Zulip Brendan Hansknecht (Apr 30 2023 at 04:09):

Just a note, I know Luke was running into some generation issue with the LLVM backend. For List U8 -> List U8 i think it broke and generated the wrong signature.

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:09):

I've had trouble making that change as well. I think it is my understanding of the pub extern fn roc__mainForHost_1_exposed_generic(_: *RocStr, _: *RocStr) void; function which has me stumped. I keep getting stuck on a Zig unreachable error when I try and run the wasm file that gets generated. I was hoping to copy your implementation.

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:09):

I really don't know how to figure out what the function types should be for the Roc exposed stuff

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:11):

I feel like I'm reaching in the dark here. I had the idea of generating the types for Rust using glue, which works well. But then I think my Zig lets me down when I try and fault find. So haven't made much progress.

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:21):

Basically I keep playing around with the signature of roc__mainForHost and I am getting different errors like,

wasm-ld: warning: function signature mismatch: roc__mainForHost_1_exposed_generic
>>> defined as (i32, i64, i32) -> void in lto.tmp
>>> defined as (i32) -> void in ../roc-serverless/examples/../platform/zig-cache/o/60f380eba017104c3db40f606e31875f/roc_app145t9T.o

I am reasonably sure it should be something like pub extern fn roc__mainForHost_1_exposed_generic(_: *RocStr, _: RocStr) void;; but I am not sure.

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:21):

well one hurdle that has to be overcome is that it relies on pub fn main() in host.zig

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:22):

and main isn't allowed to accept arguments directly

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:22):

it would need to be some other function than main and then exported differently, but I haven't been able to figure out how to get that to work

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:22):

This is what I have currently. My plan is to use STDIO, you can bind to that from JS

pub extern fn roc__mainForHost_1_exposed_generic(_: *RocStr, _: RocStr) void;

pub fn main() u8 {

    // Call Roc and get the Str
    var arg : RocStr = RocStr.empty();
    var ret : *RocStr = &RocStr.empty();

    roc__mainForHost_1_exposed_generic(ret, arg);

    // Print to stdio
    const stdout = std.io.getStdOut().writer();
    stdout.print("Printing...", .{}) catch unreachable;
    stdout.print("{s}", .{ret.asSlice()}) catch unreachable;

    // value.decref();

    return 0;
}

view this post on Zulip Brendan Hansknecht (Apr 30 2023 at 04:24):

@Richard Feldman shouldn't it be similar to a browser example where it is controlled by a host.js file? Then you would setup zig the same way as that example.

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:26):

I have an example here somewhere, I'll dig it out. It shows the WASM and the JS for using stdio/stdout

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:26):

unfortunately that example is using main() with no arguments - https://github.com/roc-lang/roc/blob/e520eaddccd4042432efb1cc4cf0389845efbb00/examples/platform-switching/web-assembly-platform/host.js#L48 (which compiles to _start)

view this post on Zulip Brendan Hansknecht (Apr 30 2023 at 04:26):

Or like this. Zig still has a main, but other functions can be called/shared with wasm module

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:28):

how though? in examples like that I've done console.log on wasm.instance.exports and all it has is _start

view this post on Zulip Richard Feldman (Apr 30 2023 at 04:28):

including if I define another pub function just like main (except not named main)

view this post on Zulip Luke Boswell (Apr 30 2023 at 04:32):

This announcing-wasi-on-workers is the example I was thinking of, unfortunately it uses a library from cloudfare. I thought it was vanilla JS.

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:07):

A good mental model here is that the wasm program is a dynamic library, not an executable. You don't want a main because the main is in the browser or node. That runs the event loop which enters JS, which calls Wasm. So Wasm is not really an entry point.

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:08):

So if you have a zig host then export some functions from it

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:09):

Then when you instantiate your Wasm module pass it an object of the functions it needs to "link" to

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:10):

And the exported functions will appear in exports

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:11):

We have a hello world example and a virtual dom example, they both do this. Did you try copying those?

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:15):

pub is not enough, you need to export it. That's the same technique we use for our built-ins. Those are also built into a library for external foreign code to use. This is no different.

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:23):

It's confusing because Zig automatically exports a function called main but no other names

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:30):

For npm you should look at browser examples only, and ignore all WASI examples. WASI does the opposite of what you want, as it usually focuses on executables. But for node or browser you never want that. There is only a way for Wasm to be called from JS. There's no way for it to be main.

view this post on Zulip Brian Carroll (Apr 30 2023 at 06:42):

OK I have some time today and tomorrow. Richard and Luke, if you like, I can spend an hour or so with each of you separately on Zoom and try to unblock you. DM me to arrange. It would be good to spread familiarity of this stuff around a bit more! If we succeed then we can post back here to let people what worked.

view this post on Zulip Brian Carroll (Apr 30 2023 at 16:25):

The chats with Luke and Richard both concluded that we have some bugs in our build system for Wasm targets.
I came across one place in particular where we are assigning a variable preprocessed_host_path to host.zig
But that can't be right because "preprocessed" implies "compiled" but this is a source code file. We are then passing it to wasm-ld which doesn't make sense.
I think we need to spend some time debugging that build process and create a test for it that runs in CI to keep it working, such as the Node.js integration example.

view this post on Zulip Brendan Hansknecht (Apr 30 2023 at 18:00):

This may be correct, but only because our llvm wasm build currently is heavily tied to zig. It actually tosses out the preprocessed host and use host.zig. The final command it runs is a command to compile host.zig and roc_app.bc into a wasm file. I think it was a hack to make the build process simple and get it to work for llvm wasm.

That all said, no matter what, that needs to change in the long term to separate llvm wasm from zig.

view this post on Zulip Brendan Hansknecht (Apr 30 2023 at 18:02):

In other words, currently, llvm wasm throws away all prepocessing and instead lets zig completely control the build.

view this post on Zulip Richard Feldman (Apr 30 2023 at 18:05):

gotcha - what would it take to change that?

view this post on Zulip Brendan Hansknecht (Apr 30 2023 at 18:13):

I don't actually know. I just happened to learn about the weird dependencies when working on my recent wasm related PR.

view this post on Zulip Brian Carroll (May 01 2023 at 06:29):

As far as I could tell, host.zig is passed _directly_ as an argument to wasm-ld. But wasm-ld is not a Zig compiler.

view this post on Zulip Brian Carroll (May 01 2023 at 06:31):

oh, rereading your comment, maybe I was getting fooled by a variable name

view this post on Zulip Brian Carroll (May 01 2023 at 06:32):

either way we need to change this, if only to refactor the existing behaviour to make it clear what's happening!

view this post on Zulip Ryan Barth (Jun 26 2024 at 18:03):

Heya, sorry to necro this topic, but most of the context for this conversation is here. I am very interested in platform building and specifically standalone wasm platforms for roc.

I got my first taste trying to use the nightly package to compile some of the wasm examples. I hit a few issues, dug in, and got my first PR in fixing the issues with the nightly release. I tried to get a feel for where things were though the process of opening that PR, reading the two linkers, and reading through some of the conversations here.

It appears that there is still some work to be done in the surgical linker to support my goals. I browsed through the elf linker to get an idea what the work would be and this is obviously a much larger task than the one I took on previously. Before I begin I just had a couple questions.

  1. Does this work align with the project's near term goals? It seems like it?
  2. Am I stepping on any toes? It looks like @Brian Carroll and @Folkert de Vries have done most of the work around the current linker.
  3. I believe the final outcome of this is building a wasm platform would more or less mirror the process for the go platform. It this the desired end state? Am I incorrect that because of the preprocess-host step building a platform will still depend on some final target roc app?
  4. The current surgical linker implementation is entirely dependent on the object crate. That library does not support wasm. It also does not have any sort of public trait to implement its API for a wasm target we define. From the elf code it looks like we predominantly rely on the .sections() and .relocations() iterators / items. Would you want to just duck-type their api? Implement our own trait wrapping object's functionality, then implement that for wasm? Try to push such an implementation back upstream before we take this on?
  5. Any tips before I begin? Any landmines I have not uncovered yet?

Sorry for the barrage of questions. I hoped I could give the best idea of what I want to implement by packaging some of the broad directional strokes alongside some of the implementation details. Thanks for taking the time to read this far!

view this post on Zulip Folkert de Vries (Jun 26 2024 at 18:04):

re toe stepping, definitely not.

view this post on Zulip Brendan Hansknecht (Jun 26 2024 at 19:01):

I think there is a misconception here. The surgical linker is only for native targets.

view this post on Zulip Brendan Hansknecht (Jun 26 2024 at 19:01):

So it is is not needed for any wasm work

view this post on Zulip Brendan Hansknecht (Jun 26 2024 at 19:02):

We have a separate custom wasm linker that @Brian Carroll wrote and it should be mostly good to just use. Otherwise, can always fallback to the legacy wasm linker

view this post on Zulip Brendan Hansknecht (Jun 26 2024 at 19:03):

That said, I know some of our wasm building and linking was tied into zig for simplicity. Depending on your platform goals, that may need to be unwound.

view this post on Zulip Brendan Hansknecht (Jun 26 2024 at 19:06):

If anything, I assume this work would be built on top of the additive linker (assuming it is missing the features needed)....

Actually I think I already know what is missing. If I understand correctly, the additive linker only works with the wasm dev backend and not the wasm llvm backend. Something probably needs to be done for the llvm backend and wasm backend to both link in the same way.

view this post on Zulip Luke Boswell (Jun 26 2024 at 19:10):

Totally not stepping on toes. Any assistance would be most appreciated. I'll try and explaing my understanding of the current situation. I'm not an expert on these things, and have mostly just stumbled into these things while trying to make a change. Sorry for the long post.

view this post on Zulip Luke Boswell (Jun 26 2024 at 19:10):

I've been trying to work towards removing the platform rebuilding from the cli so its not a dependency from the compiler on the platform hosts. This will remove complexity from the compiler and make it easier to maintain and to support more host languages, by shifting the responsibility for (pre)building binaries to the platform hosts, who can use thier own tooling etc. Roc is then only responsible for linking the host and the app together.

view this post on Zulip Luke Boswell (Jun 26 2024 at 19:10):

It turns out, what I thought would be a relatively minor change is a rather large yak that requires shaving. There seems to be a lot of different things that are interrelated and need to happen first.

view this post on Zulip Luke Boswell (Jun 26 2024 at 19:10):

In short the current status is that I am stuck on https://github.com/roc-lang/roc/pull/6808, specifically I don't have a great setup on linux so it's been slow to fix this, and I've gotten distracted by other things I can make progress on. This change fixes the preprocess host subcommand, which means we can make prebuilt binaries for the platform hosts that are suitable for the surgical linker. That will enable us to land this basic-webserver and this basic-cli PRs which refactor the host out into crates and add a build script. Without the pre-process host subcommand working, we can't build the prebuilt binaries to support the surgical linker.

view this post on Zulip Luke Boswell (Jun 26 2024 at 19:10):

For WASM specifically, I have been stuck on at least one issue. I started the roc-platform-template-wasi platform to try and find the correct way to build WASM platforms. The current roc cli rebuilds a WASM host using some workarounds by compiling to llvm bytcode and then using zig to build the final .wasm file. I think this is a limitation in zig 0.11.0 that is resolved in zig 0.12.0 and later versions. If we upgrade our zig version (which looks doable now then I think we can use zig to link two .wasm files just using zig build-exe or zig build-lib. I discovered this while working on upgrading the glue for zig platforms. So ideally, we can have the host (pre)built into a .wasm file and then all the roc cli needs to do is link that with the app .wasm file to make the final binary. We may be able to use the WASM linker that we already have for the dev backend, I haven't looked into that yet. I haven't been able to build and link everything correctly manually yet.

view this post on Zulip Luke Boswell (Jun 26 2024 at 19:10):

After unlocking the preprocess-host subcommand, and updating the platforms to support surgical linking, we can then return to the cli and switch off platform rebuilding. But doing this breaks all of the cli platform tests in the compiler as they now need a second step to build the platform. All of those changes are mostly done and ready to go in #6696. We also want to move most of the examples from roc-lang/roc/examples and into the examples repository at roc-lang/examples, and the cli test that are there into roc-lang/roc/crates/cli/tests/.... We have cli tests for CI to ensure we don't break basic-cli etc, but these examples aren't the best experience for newcomers wanting to checkout roc, that is why we have the examples repository.

view this post on Zulip Ryan Barth (Jun 26 2024 at 19:12):

Ok, yes I think there is some misconception on the surgical linker then. I tried to build the wasm platform in rust instead of zig. I was looking to build wasm plugins for a host (not roc host) rust program. To do that it was helpful to share data structures and logic between the host program and the roc platform code. So I tried to do a "standalone" wasm build, adapting the nodejs interop example to the instructions found in the go platform tutorial. I got all the way to step 4 with roc preprocess-host main.roc --target wasm32 and it failed with thread 'main' panicked at crates/linker/src/lib.rs:335:14: not yet implemented: surgical linker does not support target Wasm32. At that point I dove into the surgical linker since that was where that error came from.

view this post on Zulip Ryan Barth (Jun 26 2024 at 19:26):

Is this the "seperate custom linker" you were referring to?
https://github.com/roc-lang/roc/blame/f8c6786502bc253ab202a55e2bccdcc693e549c8/crates/compiler/build/src/link.rs#L1196

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:32):

Something probably needs to be done for the llvm backend and wasm backend to both link in the same way.

There's a common misunderstanding to watch out for. "The additive linker" is not really thing. It's not a separate entity from the wasm dev back end. Rather, that backend is designed to not really need any linking.

The backend has some internal state where it keeps track of the "instructions generated so far". To initialise that state, I "cheat" by loading it up with the instructions from the host instead. So the backend "thinks" it generated the host. It then "appends" the instructions for the Roc app to the instructions for the host.

There is not really any linking because you never have two separate things to link.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:33):

When Brendan mentioned "seperate custom linker" this is what he was referring to.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:36):

Here is the type signature of build_app_module.

pub fn build_app_module<'a, 'r>(
    env: &'r Env<'a>,
    layout_interner: &'r mut STLayoutInterner<'a>,
    interns: &'r mut Interns,
    host_module: WasmModule<'a>,
    procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> (WasmModule<'a>, BitVec<usize>, u32)

You can see one of the arguments is host_module: WasmModule<'a> and the return value is another, larger WasmModule<'a>.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:37):

So it takes the host and adds the app to it.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:38):

Now I oversimplified a little when I said there is "no" linking to do. There is no linking needed for app-to-host calls. But we do need to do some linker type work for host-to-app calls. In other words, the mainForHost call.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:38):

So there is some linking functionality there

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:42):

And if someone wanted to make a separate linker for Wasm then there is plenty of relevant code there you could start from. In particular the wasm_module crate. It knows how to parse a binary file into that WasmModule data structure. We have Rust code for parsing and traversing the linking data in a Wasm file.

view this post on Zulip Ryan Barth (Jun 26 2024 at 19:46):

Thanks for the pointer @Brian Carroll ! I am going to have to take a hot second to digest what you wrote here. I have not been through the gen_wasm compiler module yet.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:52):

OK cool. I'll throw some more links at you for the bits that do linking stuff.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:54):

Nearly all the linking related stuff is in wasm_module rather than gen_wasm. So if someone wanted to build a separate surgical linker to use with the LLVM backend, they would import stuff from wasm_module and not gen_wasm.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:55):

Might be worth looking at the README files as well.

view this post on Zulip Ryan Barth (Jun 26 2024 at 19:56):

And the reason wasm_module is not invoked from the linker today is it does not have produce the appropriate llvm metadata, so cannot be optimized?

The surgical linker is all about getting LLVM to optimize the final output?

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:58):

No that's not right.
Optimisation happens before linking.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:59):

Also link.rs is not a linker

view this post on Zulip Ryan Barth (Jun 26 2024 at 19:59):

Sorry, just to level set. I am an overly ambitious app developer (with a background mostly in web). Not a compiler dev.

view this post on Zulip Brian Carroll (Jun 26 2024 at 19:59):

cool :thumbs_up:

view this post on Zulip Brian Carroll (Jun 26 2024 at 20:00):

most of the compiler devs here were overly ambitious app devs not that long ago. 2 years or so in my case.

view this post on Zulip Brian Carroll (Jun 26 2024 at 20:01):

Sorry, the short replies there were just cos someone was interrupting me here in the real world!

view this post on Zulip Ryan Barth (Jun 26 2024 at 20:02):

No worries! I am greatful for any feedback at all.

view this post on Zulip Brian Carroll (Jun 26 2024 at 20:03):

So the link.rs file is part of a kind of ad-hoc build system. It makes shell commands to invoke linkers with various command line options.

view this post on Zulip Ryan Barth (Jun 26 2024 at 20:13):

Ok, so lets take a step back. Under the build instructions here I was confused about the "why" behind some of these things. My naive assumptions -> the things confusing me are:

  1. The platform is entirely independent of the app using it. -> This makes it confusing why roc gen-stub-lib and roc build --lib main.roc --output platform/libapp.so exist.
  2. it is possible for the roc compiler to consume an independently built platform -> Prebuilt platforms require .rh and .rm files, but those require calling roc preprocess-host main.roc on an app module (which in turn references the platform you are trying to build those files for :dizzy: )
  3. host == the language a roc platform is built in, platform == the implementation of the roc platform api in a host language
  4. When compiling an object is built for the roc app then "linked" via either the surgical or legacy linkers to the prebuilt platform
  5. I am getting most of these steps from here then trying to apply them to the couple wasm examples in the roc repo.

view this post on Zulip Brian Carroll (Jun 26 2024 at 21:32):

  1. The platform is entirely independent of the app using it. -> This makes it confusing why roc gen-stub-lib and roc build --lib main.roc --output platform/libapp.so exist.

I think those two commands are creating what this diagram calls the "app library" and "platform library".

  1. it is possible for the roc compiler to consume an independently built platform -> Prebuilt platforms require .rh and .rm files, but those require calling roc preprocess-host main.roc on an app module (which in turn references the platform you are trying to build those files for :dizzy: )

I think what's happening here is that you are building the platform with a dummy app that will later gets replaced with a real one. Definitely a bit roundabout, and that's because we are dealing with existing toolchains for other languages that we didn't create. They don't have command line arguments for the exact thing Roc wants to do, so we end up doing tricks like this. Most Roc app developers just download the prebuilt platform and never have to think about this weird stuff.

  1. host == the language a roc platform is built in, platform == the implementation of the roc platform api in a host language

platform = host + some Roc code on top to present a nice API to apps
So no the platform is not just in a host language, it is a combination of host language and Roc.

  1. When compiling an object is built for the roc app then "linked" via either the surgical or legacy linkers to the prebuilt platform

Yep

  1. I am getting most of these steps from here then trying to apply them to the couple wasm examples in the roc repo.

OK

view this post on Zulip Luke Boswell (Jun 26 2024 at 21:33):

The platform is entirely independent of the app using it. -> This makes it confusing why roc gen-stub-lib and roc build --lib main.roc --output platform/libapp.so exist.

We don't need gen-stub-lib anymore you can use roc build --lib main.roc --output platform/libapp.so instead, the plan is to remove it. See #6696. There is currently a bug in pre-process host that overwrites the dylib, and we want to change that API.

view this post on Zulip Luke Boswell (Jun 26 2024 at 21:34):

I put together some slides to try and give an overview of platform development, you can find the thread https://roc.zulipchat.com/#narrow/stream/303057-gatherings/topic/Roc.20Online.20Meetup.20May.202024/near/440575947

view this post on Zulip Luke Boswell (Jun 26 2024 at 21:35):

WASM is a little special in some ways though.

view this post on Zulip Luke Boswell (Jun 26 2024 at 21:38):

That https://www.roc-lang.org/examples/GoPlatform/README.html example only shows how to build for surgical linking, which is not applicable for WASM -- and coupled closely with the current cli which we plan to change.

view this post on Zulip Luke Boswell (Jun 26 2024 at 21:38):

So it is not going to be a helpful guide for what you are trying to do

view this post on Zulip Luke Boswell (Jun 26 2024 at 21:39):

I would recommend you look at another platform like https://github.com/lukewilliamboswell/roc-platform-template-zig or https://github.com/lukewilliamboswell/roc-platform-template-wasi or https://github.com/lukewilliamboswell/roc-platform-template-go

view this post on Zulip Ryan Barth (Jun 26 2024 at 23:12):

Thank you both. Your comments have already been very helpful, and I still have a lot more reading to do! I get that it is still early in the project, and there are some hacks in place to use existing toolchains, but I think the work you all are doing here is really stellar. I would love to continue contributing. I will hapily take guidance If you have a good next issue for me to chew on given what we talked about today while I digest what you both have shared with me.

view this post on Zulip Ryan Barth (Jun 26 2024 at 23:33):

Just saw you moved messages @Luke Boswell

In short the current status is that I am stuck on https://github.com/roc-lang/roc/pull/6808, specifically I don't have a great setup on linux so it's been slow to fix this, and I've gotten distracted by other things I can make progress on.

My linux environment is all setup and ready to go, so this could be a good fit working towards enabling more independent platforms.

view this post on Zulip Luke Boswell (Jun 27 2024 at 00:52):

Sounds great. I've been using that PR for refactor host in basic-cli to test it. They're kind of paired, so when the PRs are ready we can do a new release of both and not block anyone. If you can look into that I would appreciate it, let me know if you have any questions. @Brendan Hansknecht has provided a lot of guidance with that API change too. @Anton has also done some investigation, but I haven't looked into that yet.


Last updated: Jul 05 2025 at 12:14 UTC