I'm trying to build a wasm host and app manually to check I really understand the process.
I've been following along closely with the implementation in crates/compiler/build/src/link.rs
specifically for build_zig_host_wasm32
and link_wasm32
, and experimenting with a bunch of things.
I dont have an easy way to reproduce this rn, though I guess I could make a random commit if that would help.
But basically I'm running the following commands, but having some issues. This is the furthest I've managed to get I think (it's hard to follow the errors and see if I'm progressing or regressing sometimes).
# compile host into a LLVM .bc file
zig build-obj -target wasm32-wasi-musl -lc -rdynamic -femit-llvm-ir=platform/host.bc host/main.zig
# compile app into a LLVM .ll file
roc build --no-link --emit-llvm-ir rocLovesWebAssembly.roc
# link together host and app
zig build-exe -target wasm32-wasi-musl -fPIC -fstrip -rdynamic -lc platform/host.bc rocLovesWebAssembly.ll
My understanding here is that we are successfuly building the host.zig
into a LLVM bitcode .bc
file (I'm still not 100% sure why we need to go that route, but I can't get a library or anything else working either).
Roc is generating a LLVM human readable bitcode .ll
file.
When we link these together to build a .wasm
file the implementation of symbols in compiler-rt is slightly different, and we are missing setjmp and longjmp.
I am looking for any ideas why the roc generated code might be missing these, or different to zig. I'm using zig version 0.11.0 for everything. Do we include our own version of compiler-rt? playing with zig's -fcompiler-rt
doesn't seem to make any difference. Maybe we have included those in the builtins or something?
Also unsure why setjmp and longjmp are undefined. From what I can tell these should be included in musl.
error: wasm-ld:
note: defined as (i32, i32, i32) -> i32 in /Users/luke/.cache/zig/o/e1ad72288e7681c39fb42e87c2506cfb/libcompiler_rt.a(/Users/luke/.cache/zig/o/e1ad72288e7681c39fb42e87c2506cfb/libcompiler_rt.a.o)
note: defined as (i32, i32, i64) -> i32 in /Users/luke/.cache/zig/o/8e32b79d2f74ce8703ec76103c3fa0de/rocLovesWebAssembly.o
error: wasm-ld:
note: defined as (i32, i32, i32) -> i32 in /Users/luke/.cache/zig/o/e1ad72288e7681c39fb42e87c2506cfb/libcompiler_rt.a(/Users/luke/.cache/zig/o/e1ad72288e7681c39fb42e87c2506cfb/libcompiler_rt.a.o)
note: defined as (i32, i32, i64) -> i32 in /Users/luke/.cache/zig/o/8e32b79d2f74ce8703ec76103c3fa0de/rocLovesWebAssembly.o
error: wasm-ld: /Users/luke/.cache/zig/o/8e32b79d2f74ce8703ec76103c3fa0de/rocLovesWebAssembly.o: undefined symbol: setjmp
error: wasm-ld: /Users/luke/.cache/zig/o/8e32b79d2f74ce8703ec76103c3fa0de/rocLovesWebAssembly.o: undefined symbol: longjmp
If I update the final step to zig build-exe -target wasm32-wasi-musl -lc platform/host.bc rocLovesWebAssembly.ll
, now I get;
LLD Link... warning(link): unexpected LLD stderr:
wasm-ld: warning: function signature mismatch: memcpy
>>> defined as (i32, i32, i64) -> i32 in /Users/luke/.cache/zig/o/7f59f835ce3672f0de84e2fa02508ea0/rocLovesWebAssembly.o
>>> defined as (i32, i32, i32) -> i32 in /Users/luke/.cache/zig/o/8b8b8ee442982e6b4064dc7bd089866a/libc.a(/Users/luke/.cache/zig/o/10a7521927d8b2330ade4b85f73bcd27/memcpy.o)
wasm-ld: warning: function signature mismatch: memset
>>> defined as (i32, i32, i64) -> i32 in /Users/luke/.cache/zig/o/7f59f835ce3672f0de84e2fa02508ea0/rocLovesWebAssembly.o
>>> defined as (i32, i32, i32) -> i32 in /Users/luke/.cache/zig/o/8b8b8ee442982e6b4064dc7bd089866a/libc.a(/Users/luke/.cache/zig/o/46d827fde6ae7406d7854e8a8727cccb/memset.o)
I think removing -rdynamic
solves the setjmp and longjmp issue
It all seems far more complicated than I had expected. I thought we would compile the host to a .wasm
file and then the app too, and link them together.
If I use zig build-exe -target wasm32-wasi host/main.zig
I get a nice wasm file, that I can inspect with wasmer inspect
.
But roc build --target=wasm32 --no-link rocLovesWebAssembly.roc
gives me a file that seems broken.
$ wasmer inspect rocLovesWebAssembly.wasm
error: failed to inspect `rocLovesWebAssembly.wasm`
╰─▶ 1: WebAssembly translation error: Error when converting wat: input bytes aren't valid utf-8
Sharing all this here... because I don't really know which way is up, and trying to make sense of things.
Actually... digging some more, these issues may all just be from using LLVM backend. If we switch over to look at the dev backend then it looks like we are using roc_wasm_module
. So maybe the question I should be asking is how to preprocess a wasm host for the surgical linker?
re that wasmer thing: something seems confused between the text and binary formats there
wat
is the webassembly text format, that lisp-like syntax for wasm. What we produce is a binary file.
Yeah, seems strange.
Do you think the WASM examples should focus on support for legacy or surgical linker or both?
unfortunately we need both for now
do we? I thought wasm was pretty much complete too? what breaks when we feed it a module produced by LLVM?
Instead of wasmer inspect, try to get your hands on wasm-objdump and wasm-validate, which are part of a project called.. binaryen I think
Or maybe wabt
Thank you.
Ill give that a go
Actually wasmer inspect
works just fine for me on files that I know for sure have binary webassembly inside them.
I wonder if you somehow have a file that has the text format inside it but with a .wasm extension which is for the binary format.
Do we include our own version of compiler-rt?
As part of building the Roc project, crates/wasi-libc-sys/build.rs
uses Zig to generate compiled libc and compiler-rt, finds their paths, and sets constants equal to those paths.
Then in the linker commands in crates/compiler/build/src/link.rs
we provide wasm-ld
with those paths as well as the paths to the host and the Roc app. See preprocess_host_wasm32
.
In the early days this build process only worked when building Roc from source. But I think later we made it work with nightly builds too. Could be worth double checking that though.
How did setjmp end up in the roc binary? Shouldn't that be a host-side thing? I have been a bit out of touch with Roc lately, might have missed some big change.
I don't think it's actually possible to implement setjmp
and longjmp
in the Wasm instruction set. There are no arbitrary jumps, only structured control flow. So the compiler would have to generate early returns in every function all the way up the call stack. And then I'm not sure why you would even need functions with those names. So maybe one of these errors is happening because they don't exist in this target?
I just downloaded a zip of the Zig repo at tag 0.11.0
and searched for references to setjmp
in wasm libc and compiler-rt files etc., and I can't find anything.
One of the error messages complains about a mismatch in last argument of memcpy
and memset
so we can look up cppreference.com to find out the actual type signatures.
void* memcpy( void *dest, const void *src, size_t count );
void *memset( void *dest, int ch, size_t count );
The last argument for each is a size_t
. That's a pointer-sized integer, so 32 bits is the correct size for that in Wasm. Whichever one is generating 64 is wrong. Most likely there is somewhere we forgot about 32-bit targets and generated size_t
as 64 bits. This is very common. Everyone forgets about 32 bits. It's the root cause of 99% of code gen issues with Wasm.
@Folkert de Vries any idea on the setjmp part of that question?
I think this message is pretty clear
// Utils continued - SJLJ
// For tests (in particular test_gen), roc_panic is implemented in terms of
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/roc-lang/roc/issues/2965),
// so instead we ask Zig to please provide implementations for us, which is does
// (seemingly via musl).
pub extern fn setjmp([*c]c_int) c_int;
pub extern fn longjmp([*c]c_int, c_int) noreturn;
pub extern fn _setjmp([*c]c_int) c_int;
pub extern fn _longjmp([*c]c_int, c_int) noreturn;
pub extern fn sigsetjmp([*c]c_int, c_int) c_int;
pub extern fn siglongjmp([*c]c_int, c_int) noreturn;
pub extern fn longjmperror() void;
so: tests need an implementation of roc_panic!
. There is not much we can do there beside a longjmp really
@Brian Carroll what are your thoughts on the best way to pre-build a WASM platform? I'm updating roc so that the platform rebuilding is the sole responsibility of the platform, which provides the pre-built binaries. What should this look like for WASM though?
What are you doing for the others and how is Wasm different?
Actually let me rephrase that as a statement rather than a question! I don't think there's any conceptual difference with Wasm, it just uses a different linker. And you can't rely on the OS to provide libc for you.
So I assume that what you want to produce for all targets, including Wasm, is a library that contains:
Although for most targets you can make the library depend on the host machine's libc. For Wasm you have to link that in.
I've converted all the others in https://github.com/roc-lang/roc/pull/6696
I've written a build script for each of the different platforms.
Can you outline it for me so I don't have to go through the whole PR?
Most of these only support the legacy linker though... so I'm generating a macos-arm64.a
file for the platform prebuilt-binary.
That c-archive sits next to the platform/main.roc file and then when we roc run app.roc
the roc cli will build the app, link with the platform to produce the final application binary
I'm just not sure about WASM, in that the same (or similar) approach seems to require jumping through a lot of hoops. If it should be the same, and we just have a bug somewhere to track down then that is very helpful to know.
What hoops are those?
Well to support the legacy linker at least. I suspect the surgical linker would be much easier
For example, we need to compile the host into a LLVM .bc
file, instead of just a c-archive
OK why is that?
I was going to say I haven't seen .a used very much with wasm. But not sure what constraint actually forces this because it's just a concatenation of object files.
I would have guessed you could create a .wasm
file, or a .a
file, containing everything you need.
I don't see why .bc is necessary unless LLVM is for some reason forcing that.
But I am not super familiar with trying to get LLVM to produce Wasm in various ways. Most of my experience with this stuff is from working on the Wasm dev back end with no LLVM in the path.
The Wasm dev backend has no understanding whatsoever of .bc
or .ll
so that would be incompatible with this setup.
Essentially nobody would ever be able to use the Wasm dev backend because the ecosystem would be incompatible with it.
So I think it would be worth trying to find a way to use .wasm as that's the only thing the dev backend can work with
You can look at what I've done for the wasm unit tests or the wasm REPL. They both have "test hosts" that I build into .wasm files.
crates/repl_wasm/build.rs build_wasm_platform
crates/compiler/test_gen/build.rs build_wasm_test_host
I think currently we generated a .bc
and then let llvm directly consume the .bc
file cause it was easy and it worked around some early issue when setting up wasm linking with the llvm backend (@Folkert de Vries may know what the issue actually was). That said, it is just a hack, our over-reliance on zig for llvm backend wasm linking is also a dependency we need to fix. Fundamentally, I think most of that is old tech debt that we really need to wrip out a clean up
Wasm should just generate a/multiple .wasm
file and let the additive linker concatenate them together.
That said, last time I checked, the additive linker could only be used with our wasm dev backend and had some sort of issue linking wasm files generated form the llvm backend (but that might just be a setup/wiring issue and not a real bug, been a long time since I looked at any of this).
Even if we don't change the llvm backend to use the additive linker, it should still generate a .wasm
and that should be consumed by the platform or by a linker of some sort to generate the final executable. The .bc
stuff all needs to die and is just a workaround.
Yes "additive linking" is a term I came up with for the linking that is built into the wasm dev back end. It is a feature of that back end
It is not possible to extract as a separate thing and use with LLVM.
ah. Thought it could be generalized to anything wasm...guess not
It works by reading in the host file and using that to initialise the state of the code generator so that when you generate more code it is "added on top".
We do not have that kind of access to the internals of LLVM.
I see. Yeah, I thought it came at a later stage and was consuming fully generated functions from the wasm dev backend. Thus, I assumed we could pass full wasm functions from llvm to be added on top of the wasm platform. Good to know that is not the case and it is more integrated.
yeah, so we still need to figure out a good linking story for llvm wasm (that or leave it 100% to the end users). Today, IIRC, we compile to a .bc
and then pass the .bc
to zig such that it can build the host with the .bc
file and generate a finalized platform.
So we essentially use .bc
in a way to avoid needing to figure out correctly linking.
leave it all to zig
I believe I might have solved that problem already in the build.rs
files I mentioned above. Or at least a very similar problem.
They produce a preprocessed host as .wasm file containing the host, the Roc builtins, and any libc stuff the host and builtins need.
So then with LLVM Wasm you would just need to do a final linking step with wasm-ld
to combine the preprocessed host .wasm
file with the app .wasm
file.
And the Wasm dev backend can also use it by having the preprocessed host passed via CLI options. That's already implemented.
By the way, a .wasm
file is most comparable to .so
on Linux or .dylib
on Mac. By definition of how Wasm works, it always needs to receive some callback functions from the Wasm interpreter at runtime. And that is a dynamic linking step.
So they are always dynamic libraries, and they can be linked together. The "imports" and "exports" get merged, and sometimes one export satisfies another import and it gets removed from the final file. In the end you are left with just the ones that actually need to be exposed at runtime.
is it possible to do stack unwinding in wasm?
No
If you had enough power to do that, it would break the security model.
hm, I see
I was trying to think of other ways we could potentially make roc_panic
work in wasm
since setjmp/longjmp don't work
You can "trap" and catch outside and call again
ah gotcha
so that works as long as you don't need to recover from the roc_panic from within wasm code
I guess the exception handling proposal could address that in the future
for tests we don't need to recover from roc_panic
, so if setjmp/longjmp in the compiler is a problem we can explore that?
I need to write up a bit of a summary -- but this is where I got to on the plane. https://github.com/lukewilliamboswell/roc-platform-template-wasi
Basically, I think the way we do linking from roc cli makes this a bit interesting and unique. Most tooling i.e. zig is geared towards making a standalone .wasm
library. So a bit of a challenge if we want roc to drive the build/linking. If we build the app separately using roc build --target=wasm32 --no-link app.roc
and then use zig or something else to drive the build I think things get much simpler (but not the end user experience consistent with the other platforms).
I think the next step is to figure out how to manually create the _start
symbol, so when we link using e.g. something like /opt/homebrew/opt/llvm@16/bin/wasm-ld app.wasm platform/host.wasm -o out.wasm
it has everything required. Once we can build the pre-built binary and link manually, we will need to revisit the linking in roc cli if we want to support this workflow.
Mostly leaving this comment to remind my future self.
That all sounds good
I don't think you should have to manually create _start. The toolchain should be doing that for you if configured to generate a WASI executable. But this is the sort of thing that can be tricky to set up.
Thank you. I think I need to investigate it further. From what I can tell so far it's not generated by zig with b.addSharedLibrary
but it is with b.addExecutable
. So that's why my current hypothesis is we may need to generate that manually.
As an aside to resolve the function signature mismatch: memset
issue, I changed the void
return in our zig extern from extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
to extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) i32;
.
It works now and is happy.
But, I'm just wondering if that is something we should be changing in the other zig platforms too, or maybe it is actually a symptom of another bug elsewhere.
I'm having trouble working with WASM things. I think our build pipeline is a bit broken at the moment.
If I have two files, platform/main.roc
and main.roc
and I build this using roc build --target=wasm32 --no-link main.roc
on current main I should get valid WASM right?
platform "wasm"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main
app [main] {
pf: platform "platform/main.roc",
}
main = "hi"
$ roc build --target=wasm32 --no-link main.roc
0 errors and 0 warnings found in 53 ms
while successfully building:
main.wasm
$ wasmer inspect main.wasm
error: failed to inspect `main.wasm`
╰─▶ 1: WebAssembly translation error: Error when converting wat: input bytes aren't valid utf-8
$ wasmer validate main.wasm
error: failed to validate `main.wasm`
╰─▶ 1: `wasmer validate` only validates WebAssembly files
Also, it doesn't seem to like the dev backend.
$ roc build --target=wasm32 --no-link --dev main.roc
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 'main' panicked at crates/compiler/build/src/program.rs:499:9:
Failed to read host object file platform/wasm32.rh! Try omitting --prebuilt-platform
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Ok, update on progress with https://github.com/lukewilliamboswell/roc-platform-template-wasi :smiley:
I think I am now really close to having a working WASM platform -- specifically, a WASI platform using a Task based API. :fingers_crossed:
I updated the repository to use shell.nix
so it provides zig 13. (I spent ages trying to get a flake working, but alas no joy... :sad: )
Updated the glue script to use the latest release of roc-glue-code-gen.
Fixed up build.zig
and main.zig
with some minor changes for zig 13.
Now we have it building the host and it looks good to me.
$ roc build.roc
🎉 Generated type declarations in:
host/
$ wasmer inspect platform/host.wasm
Type: wasm
Size: 13.3 KB
Imports:
Functions:
"wasi_snapshot_preview1"."args_get": [I32, I32] -> [I32]
"wasi_snapshot_preview1"."args_sizes_get": [I32, I32] -> [I32]
"wasi_snapshot_preview1"."fd_write": [I32, I32, I32, I32] -> [I32]
"wasi_snapshot_preview1"."proc_exit": [I32] -> []
Memories:
"env"."memory": not shared (1 pages..)
Tables:
"env"."__indirect_function_table": FuncRef (1..)
Globals:
"env"."__stack_pointer": I32 (mutable)
"env"."__memory_base": I32 (constant)
"env"."__table_base": I32 (constant)
"GOT.mem"."__heap_end": I32 (mutable)
"GOT.mem"."__heap_base": I32 (mutable)
Exports:
Functions:
"__wasm_apply_data_relocs": [] -> []
"_start": [] -> []
"main": [I32, I32] -> [I32]
"roc_alloc": [I32, I32] -> [I32]
"roc_realloc": [I32, I32, I32, I32] -> [I32]
"roc_dealloc": [I32, I32] -> []
"roc_panic": [I32, I32] -> []
"roc_dbg": [I32, I32, I32] -> []
"roc_memset": [I32, I32, I32] -> [I32]
"roc_fx_stdoutLine": [I32, I32] -> []
Memories:
Tables:
Globals:
The problem is I can't get roc to produce valid WASM for the app using --no-link
to then link and verify things manually. I would like to be able to do things manually, before hacking away at the cli build pipeline.
The cli is definitely not working yet... it tries to rebuild the host using host.zig
, or tries to use the surgical linker, or some other combination of things that all fail. Some examples below.
$ roc build --target=wasm32 app.roc
🔨 Rebuilding platform...
error: unrecognized file extension of parameter 'glue'
thread 'main' panicked at crates/compiler/build/src/program.rs:1048: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
$ roc build --target=wasm32 --dev app.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 crates/compiler/build/src/link.rs:1410:21:
Error:
Failed to rebuild platform/wasm32.rh:
The executed command was:
zig wasm-ld /tmp/host_bitcodecUbER9HV.wasm platform/main.o /Users/luke/Documents/GitHub/roc/target/release/lib/wasi-libc.a /Users/luke/Documents/GitHub/roc/target/release/build/wasi_libc_sys-0c00ed7e69066201/out/zig-cache/o/fbe76c5baee393dea3c7d358da7d00e5/libcompiler_rt.a -o platform/wasm32.rh --export-all --no-entry --import-undefined --relocatable
stderr of that command:
wasm-ld: error: cannot open platform/main.o: No such file or directory
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at crates/compiler/build/src/program.rs:927:18:
Failed to (re)build platform.: Any { .. }
$ roc build --target=wasm32 --prebuilt-platform app.roc
Because I was run with --prebuilt-platform, I was expecting this file to exist:
platform/host.zig
However, it was not there!
If you have the platform's source code locally, you may be able to generate it by re-running this command omitting --prebuilt-platform
I spent ages trying to get a flake working, but alas no joy...
I really wanted something that would give me a shell with the correct versions of roc and zig. It feels like something nix can do, based on the flakes we have in basic-cli and basic-webserver
So I think the next steps with this are to;
--no-link
and produce valid WASM.wasm
host instead of building that from source. Maybe it defaults to using the prebuilt platform and only tries to rebuild the host if it cant find a platform/host.wasm
or somethingGreat progress Luke! :smiley:
Those next steps make sense.
At step 2 I'd be curious to know how long wasm-ld
takes to link the app and host. If it turns out to be really fast maybe there'll be no need to make our own surgical linker for Wasm (from pieces of the dev backend). The decision might be different from native code.
Maybe we can also run the same app through the dev backend and dump some timing info.
It looks like wasmer validate
is not useful for figuring out _why_ a file is invalid, and debugging it. I always used wasm-objdump
for that.
It's part of the WebAssembly Binary Toolkit, wabt
I was working a little with Luke here. Here is the relevant excerpt from our DMs
image.png
When the llvm compiles wasm currently it is emitting the LLVM bytecode directly to the wasm file because of that 2nd branch
allowing wasm to follow the same branch as native code (plus a little bit of feature flag cleanup) results in this
https://github.com/roc-lang/roc/pull/6852
I am able to compile the nodejs interop example without linking
obviously there is some stuff missing from that output (but at least it builds in <10ms even with a debug build of the compiler :laughing: )
Most notably it is missing the bump allocator that gets inlined to the wasm output of the gen_wasm
module
Good job debugging that :+1:
Makes sense now why it wasn't valid Wasm if it was actually LLVM!
I didn't follow what you meant in the last part about the bump allocator. Do you mean that the output file has no roc_alloc? Does the nodejs host use a bump allocator?
I really wanted something that would give me a shell with the correct versions of roc and zig. It feels like something nix can do, based on the flakes we have in basic-cli and basic-webserver
In the inputs section you'll need to change the line nixpkgs.follows = "roc/nixpkgs";
to nixpkgs.url = "github:nixos/nixpkgs?rev=ab7cf5d23c90ee5b83444e0a80a606688d278ecd";
if you want to be able to get zig 13. The package is just named zig in that case, not zig_0_13.
Ok, I am getting very close on this PR. At this point I think I am running into artificial limitations / panics in the compiler. I can hand generate the unlinked app, host, wasi-libc and link them. Which basically leaves the platform. I cannot just roc build platform/main.roc --target=wasm32 --no-link --optimize
. Is there a reason for or do we want to keep this limitation?
I also have questions about how much of the wasi platform we include in the compiler. It seems like the entire wasm_interp/src/wasi.rs
should be pulled out into a seperate wasi platform. This would simplefy a bunch. You would not have to ship a wasi-libc. You would get freestanding
wasm for free since now since wasi is not baked into the compiler. There could be a yet another wasm freestanding platform.
Another platform author could provide their own wasi implementation that does not depend on libc
Wasm interp is not part of the compiler, we just use it for our tests
My mistake. I was wondering what was providing all this in the final wasm binary. It must be wasi-libc.a
then?
I just started hunting for the symbols in the roc codebase and hit that module
Yeah there's a crate called something like wasi-libc-sys
. It contains almost no code, just a build.rs that fools zig into giving us a path to its libc implementation.
If an app doesn't use anything from libc then the linker just won't need to use that library and that's it.
So check this out. Platformless Roc.
https://github.com/roc-lang/roc/pull/6852/files
You can check it out and run:
export PROJ=$(pwd)
cargo build
cd examples/nodejs-interop/noplatform
bash doit.sh
Running this gets you:
+ /home/ryan/src/roc-lang/roc/target/debug/roc build main.roc --target=wasm32 --optimize --no-link
0 errors and 0 warnings found in 160 ms
while successfully building:
main.wasm
+ wasm-ld main.wasm -o out.wasm --export-all --no-entry --export-table --import-undefined
+ node nolink.js
@om Roc! 3
Now obviously in my haste to throw this together and experiment i messed up my pointers somewhere (probably not taking alignment into account). But I see it! Roc performs the computation and spits out the result without a (compiled) platform in wasm! This also omits wasi-libc.a and the compiler_rt.a (though admittedly i do not know what this one does) from the linking process.
So basically I forked and was experimenting with the nodejs-interop/wasm example. I used --no-link and --optimize to build an unlinked main.wasm. I have to run wasm-ld to populate the global memory and stack pointer as well as publicly expose the roc functions.
Last updated: Jul 06 2025 at 12:14 UTC