Hi,
im am trying to build a platform written in zig and preprocess is, so it can be used with roc build --prebuilt-platform
.
What I am doing is the following:
roc build --no-link example/main.roc
. This creates example/main.o
roc gen-stub-lib example/main.roc
. This creates platform/libapp.so
platform/host.zig
, example/main.o
and platform/libapp.so
. This creates platform/dynhost
roc run --prebuilt-platform example/main.roc
This works. The app runs correctly. But if I change the code of example/main.roc
a bit (for example changing a constant string), and rerun Step 4, it still runs the old code.
It seems, the surgical linker does not replace the roc code. Do you have an idea, what I am doing wrong?
Here is the current code. The steps 1-3 can be executed by calling zig build preprocess
When I vendor all zig dependencies, then roc can build the platform. roc build example/main.roc
creates a libapp.so
and /tmp/host_bitcodeXXX.o
. When I take this files and use them instead of the results from my steps 1 and 2, then everything works.
So my question is, how does roc generate this two files?
host_bitcodeXXX.o
is probably not the result of roc build --no-link example/main.roc
, but a compiled version of the roc standard lib. Is this correct? How can I create it?
I don't understand, what libapp.so
is. If I use the version created with roc gen-stub-lib example/main.roc
, then I get the error:
error: ld.lld: undefined symbol: roc__mainForHost_1_caller
error: ld.lld: undefined symbol: roc__mainForHost_0_caller
How does roc create the file libapp.so
?
In your linking example/main.o
should not be part of the platform linking process.
error: ld.lld: undefined symbol: roc__mainForHost_1_caller
error: ld.lld: undefined symbol: roc__mainForHost_0_caller
Probably a bug in our stub lib generation such that it isn't propulating the right function names.
Not sure where you are seeing host_bitcodeXXX.o
in relation to everything else here.
Anyway. simple workaround should be roc build --lib example/main.roc
. Then use the resulting libapp.so
instead of the one generated by roc gen-stub-lib
Fundamentally, your goal is to build a zig app that dynamically links to a libapp.so
that is the roc application to load.
The surgical linker will take that dynamically linked setup, add the roc code to it, and turn it into a statically linked setup.
Aside, instead of having gen-stub-lib
we could also just require all platforms to have at least one example and use that to generate a libapp.so
that is used for setting up dynhost
for surgical linking. gen-stub-lib
generates an empty shared library with a number of symbol to satisfy the host linker. Basically tricks it into thinking that it linked with areal application. @Richard Feldman any thoughts on cli design around this?
When I call roc build --lib example/main.roc
I get a file called main.so.1.0
. When I build the host with this file, and then call roc preprocess-host examples/main.roc
, I get the error:
thread 'main' panicked at 'Shared lib not found as a dependency of the executable', crates/linker/src/elf.rs:1037:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The same happens, if I rename the file to libapp.so
.
I am using this file by adding the following line to the build.zig
: dynhost.addObjectFile(.{ .path = "platform/libapp.so" });
. If I use the libapp.so
, that is generated, when I use roc build
, then it works. So there seems to be something different from the file, created with roc build --lib
and the file created from roc build
.
I tried to find the place in the roc source, that creates the libapp.so
, but could not find it.
The tip from Brian to call roc build
with ROC_PRINT_BUILD_COMMANDS=1
only shows the zig command, that creates the dynhost
, but not the command, that creates the libapp.so
or the host_bitcodeXXX.o
.
It shows me the following command:
zig build-exe -fPIE -rdynamic examples/hello_world/../../platform/libapp.so /tmp/host_bitcodel8mYsWfv.o examples/hello_world/../../platform/host.zig -femit-bin=examples/hello_world/../../platform/dynhost --mod glue::/home/ossi/src/roc/crates/compiler/builtins/bitcode/src/glue.zig --deps glue -lc -target native -fcompiler-rt
As you can see, zig is called with three sources. The libapp.so
, the host.zig
and a temporary file host_bitcodeXXX.o
. When I look in my temporary directory, there are many of these files, and all are the same. Since code for the roc builtins is in a folder called bitcode, I guess, that this is a compiled version of the roc builtins.
Ah yeah, --lib
is generating a shared library with the wrong name. And I'm pretty sure name is also part of the shared library on the filesystem. We specifically look for an app library
Let me see if I can figure that out
Oh, what if you add --output libapp.so
might get roc to also set the lib name correctly
in terms of CLI design, I like the idea of having it be a sibling flag to --output
like when you build the platform, you can say --output
to specify where the compiled binary goes, and then --stubs
to specify where the stubs go
seems like you'd always want both, in the world where we're having hosts driving the builds
Brendan Hansknecht said:
Oh, what if you add
--output libapp.so
might get roc to also set the lib name correctly
This does not work. I creates a file called libapp.so.1.0
. There is this open PR that might fix this. I have not tested it on that branch
I tested it on the PR #6523. On this branch, roc build --lib --output libapp.so
creates the correct file. But the result is the same. If I build dynhost
with that file and then call roc preprocess-host examples/main.roc
, I get the same error as before:
thread 'main' panicked at 'Shared lib not found as a dependency of the executable', crates/linker/src/elf.rs:1037:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Yeah I have neglected that PR, there are some failing tests. If you rename the shared lib to libapp.so.1 does that work? I had to do that for something similar.
What does ldd
on dynhost
print. Does it show libapp.so
?
Interesting: When I build on the roc-main-branch with --output libapp.so
, it creates the file libapp.so.1.0
. If I build dynhost with it, ldd platform/dynhost
shows:
ldd platform/dynhost
linux-vdso.so.1 (0x00007ffdd1586000)
platform/libapp.so.1 => not found
libc.so.6 => /usr/lib/libc.so.6 (0x000070ad8fc53000)
When I rename the file to libapp.so.1
and recreate dynhost
with it, I get:
linux-vdso.so.1 (0x00007fff6ff9e000)
platform/libapp.so.1 (0x0000711438424000)
libc.so.6 => /usr/lib/libc.so.6 (0x0000711438219000)
libm.so.6 => /usr/lib/libm.so.6 (0x000071143812d000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x0000711438108000)
But when I call roc preprocess-host examples/main.roc
with that file , I still get the same error:
thread 'main' panicked at 'Shared lib not found as a dependency of the executable', crates/linker/src/elf.rs:1037:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
When I use the libapp.so
, that is created as a sideeffect of roc build example/main.roc
, ldd dynhost
shows:
linux-vdso.so.1 (0x00007fff64934000)
libapp.so => not found
libc.so.6 => /usr/lib/libc.so.6 (0x00007f02fc00f000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f02fc360000)
But on this version, roc preprocess-host
works.
When I build dynhost
with platform/libapp.so.1.0
and then call patchelf --replace-needed platform/libapp.so.1 libapp.so platform/dynhost
, I can successfully call roc preprocess-host examples/main.roc
. It creates a .rh
and .rm
file.
But when I call roc run --prebuilt-platform examples/main.roc
, I get the error:
$ roc run --prebuilt-platform examples/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 'libc::fexecve("/proc/self/fd/3", ..., ...) failed: Errno { code: 8, description: Some("Exec format error") }', crates/cli/src/lib.rs:1127:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
With roc dev --prebuilt-platform
, I get:
$ roc dev --prebuilt-platform examples/main.roc
Error Os { code: 8, kind: Uncategorized, message: "Exec format error" }
I found a solution. I looked some more at the roc source code, how the file is build. With this change, everything works for me: https://github.com/roc-lang/roc/pull/6611
With this change, roc build --lib --output platform/libapp.o
creates the correct file (without the .1.0
extension). ldd dynhost
shows the correct dependency (without the .1
extension).
In the old code, there was a link to an intel issue, but the page does not exist anymore. So I can not see, why the version number was added in the past.
It was added with this two PRs four years ago:
Do we even need -soname output_path
at all? Without it, I believe that it should default to the file name.
Let's give it a try!
Brendan Hansknecht said:
Do we even need
-soname output_path
at all? Without it, I believe that it should default to the file name.
I can confirm, that it works without the -soname
argument. I removed it in the PR.
I have no idea, why the tests of the PR are failing. Would it be possible from one of you to have a look?
Sure :)
This seems to be the cause:
/nix/store/fzlkaj1ax7gl655blfcr6zzvml1vx3bj-binutils-2.40/bin/ld: input file '/tmp/nix-shell.DO4SN3/.tmp8u8Aig/app.o' is the same as output file
I think, I need more basic help. I am not familiar with the roc sourcecode or how rust-test works.
I see, that the panic happens here. But every else is a mystery to me.
I think, I found the problem and a solution.
The problem is in the repl here: https://github.com/roc-lang/roc/blob/c72b95de971ff7c16cfd068bb2f1fbc14cf72263/crates/repl_cli/src/cli_gen.rs#L332-L343
It calls link()
and uses app_o_file
as input file and as output file. This was not a problem before, since the extension .so.1.0
was added to the output file name (app.o
-> app.o.so.1.0
). The error was visible when typing anything into the repl.
So the solution was, to use another file as output.
I found a similar call to link()
here: https://github.com/roc-lang/roc/blob/c72b95de971ff7c16cfd068bb2f1fbc14cf72263/crates/compiler/test_gen/src/helpers/dev.rs#L212-L223
I hope, it should now work
My solution was broken. roc expects a different file extension for linux, macos and windows. So the much easier solution is to just ensure the fileextension so
on linux.
The old PR had to much unnecessary changes, that where not needed. I though, it would be cleaner to open a new one. I hope, this was ok: https://github.com/roc-lang/roc/pull/6614
I don't like on this version, that the fileextension so
is enforced, even, when another fileextension is specified. For example roc build --lib --output filename.MY_EXTENSION
will create a file called filename.so
.
But this is the old behavior, expect so.1.0
is replaced with so
, which fixed my original problem.
To respect the fileextension from the --output
-attribute needs some fixes in the roc-codebase, and maybe a rethinking, what file names are expected.
To respect the fileextension from the --output-attribute needs some fixes in the roc-codebase, and maybe a rethinking, what file names are expected.
Can you make an issue for that?
Sure, here it is.
Thank you for mergin the PR. It makes building zig platforms with dependencies easier
Oskar Hahn has marked this topic as resolved.
Last updated: Jul 06 2025 at 12:14 UTC