Stream: platform development

Topic: ✔ precompile a zig platform


view this post on Zulip Oskar Hahn (Mar 29 2024 at 09:28):

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:

  1. Build an example app with roc build --no-link example/main.roc. This creates example/main.o
  2. Building a stub-lib with roc gen-stub-lib example/main.roc. This creates platform/libapp.so
  3. Build zig with platform/host.zig, example/main.o and platform/libapp.so. This creates platform/dynhost
  4. Run the example with 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

view this post on Zulip Oskar Hahn (Mar 29 2024 at 14:28):

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?

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:09):

In your linking example/main.o should not be part of the platform linking process.

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:10):

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.

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:12):

Not sure where you are seeing host_bitcodeXXX.o in relation to everything else here.

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:14):

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

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:14):

Fundamentally, your goal is to build a zig app that dynamically links to a libapp.so that is the roc application to load.

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:15):

The surgical linker will take that dynamically linked setup, add the roc code to it, and turn it into a statically linked setup.

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 19:17):

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?

view this post on Zulip Oskar Hahn (Mar 29 2024 at 22:38):

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.

view this post on Zulip Oskar Hahn (Mar 29 2024 at 22:48):

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.

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 23:02):

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

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 23:02):

Let me see if I can figure that out

view this post on Zulip Brendan Hansknecht (Mar 29 2024 at 23:11):

Oh, what if you add --output libapp.so might get roc to also set the lib name correctly

view this post on Zulip Richard Feldman (Mar 29 2024 at 23:15):

in terms of CLI design, I like the idea of having it be a sibling flag to --output

view this post on Zulip Richard Feldman (Mar 29 2024 at 23:15):

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

view this post on Zulip Richard Feldman (Mar 29 2024 at 23:16):

seems like you'd always want both, in the world where we're having hosts driving the builds

view this post on Zulip Oskar Hahn (Mar 29 2024 at 23:21):

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

view this post on Zulip Oskar Hahn (Mar 29 2024 at 23:28):

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

view this post on Zulip Isaac Van Doren (Mar 30 2024 at 01:02):

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.

view this post on Zulip Brendan Hansknecht (Mar 30 2024 at 02:12):

What does ldd on dynhost print. Does it show libapp.so?

view this post on Zulip Oskar Hahn (Mar 30 2024 at 05:57):

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.

view this post on Zulip Oskar Hahn (Mar 30 2024 at 06:23):

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 .rmfile.

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" }

view this post on Zulip Oskar Hahn (Mar 30 2024 at 07:13):

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:

https://github.com/roc-lang/roc/commit/60c33c81c3db625bc9a1f6900ae084390897fcb4#diff-cb0f45b3acd3b8c008a44f550c0399cebe2faab5b7f08e2ddbb6d488e30aa577R150

https://github.com/roc-lang/roc/commit/b4377d4d677fe109ac5d5344c6b61c5052e86938#diff-cb0f45b3acd3b8c008a44f550c0399cebe2faab5b7f08e2ddbb6d488e30aa577R138

view this post on Zulip Anton (Mar 30 2024 at 09:47):

Cached intel page

view this post on Zulip Brendan Hansknecht (Mar 30 2024 at 15:05):

Do we even need -soname output_path at all? Without it, I believe that it should default to the file name.

view this post on Zulip Anton (Mar 30 2024 at 15:32):

Let's give it a try!

view this post on Zulip Oskar Hahn (Mar 30 2024 at 15:33):

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.

view this post on Zulip Oskar Hahn (Mar 30 2024 at 15:34):

I have no idea, why the tests of the PR are failing. Would it be possible from one of you to have a look?

view this post on Zulip Anton (Mar 30 2024 at 15:48):

Sure :)

view this post on Zulip Anton (Mar 30 2024 at 17:06):

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

view this post on Zulip Oskar Hahn (Mar 31 2024 at 13:40):

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.

view this post on Zulip Oskar Hahn (Apr 01 2024 at 08:23):

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

view this post on Zulip Oskar Hahn (Apr 01 2024 at 10:45):

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

view this post on Zulip Oskar Hahn (Apr 01 2024 at 10:51):

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.

view this post on Zulip Anton (Apr 01 2024 at 12:00):

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?

view this post on Zulip Oskar Hahn (Apr 01 2024 at 14:31):

Sure, here it is.

Thank you for mergin the PR. It makes building zig platforms with dependencies easier

view this post on Zulip Notification Bot (Apr 01 2024 at 14:31):

Oskar Hahn has marked this topic as resolved.


Last updated: Jul 06 2025 at 12:14 UTC