Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webassembly linker does not import symbols #1622

Closed
josephg opened this issue Oct 2, 2018 · 8 comments · Fixed by #2193
Closed

Webassembly linker does not import symbols #1622

josephg opened this issue Oct 2, 2018 · 8 comments · Fixed by #2193
Milestone

Comments

@josephg
Copy link

josephg commented Oct 2, 2018

Related to #1570...

Consider this zig code:

extern fn inc(a: i32) i32;

export fn add(a: i32, b: i32) i32 {
    return inc(a) + b;
}

... Which expects the symbol inc to be imported from javascript (or whatever environment is embedding the wasm build).

It won't compile, because the linker is (correctly) identifying that it has no idea where inc will come from in the built artifact:

sephsmac:zig josephg$ zig build-lib --release-small --target-arch wasm32 wasm.zig 
lld: error: zig-cache/wasm.o: undefined symbol: inc

sephsmac:zig josephg$ zig build-exe --release-small --target-arch wasm32 wasm.zig 
lld: error: zig-cache/wasm.o: undefined symbol: inc

The wasm linker supports 2 option arguments for dealing with this:

sephsmac:zig josephg$ wasm-ld --help
OVERVIEW: LLVM Linker

USAGE: wasm-ld [options] <inputs>

OPTIONS:
  --allow-undefined-file=<value>
                         Allow symbols listed in <file> to be undefined in linked binary
  --allow-undefined      Allow undefined symbols in linked binary
...

So either this:

$ zig build-obj --release-small --target-arch wasm32 wasm.zig 
$ wasm-ld wasm.o -o xyz.wasm -O2 --no-entry --allow-undefined

or this:

$ zig build-obj --release-small --target-arch wasm32 wasm.zig 
$ echo 'inc' > symbols.txt 
$ wasm-ld wasm.o -o xyz -O2 --no-entry --allow-undefined-file=symbols.txt 
(module
...
  (import "env" "inc" (func $inc (type 0)))
  (func $add (type 2) (param i32 i32) (result i32)
    get_local 0
    call $inc
    get_local 1
    i32.add)
  (export "add" (func $add)))

How should zig import functions from JS? For now maybe just pass --allow-undefined to wasm-ld?

But that said, larger projects we want that link error if you forget to add a function. We could have extern "wasm" fn blah() void;, or something like that. Then compilation could drop all those function names into a text file and pass them via --allow-undefined-file= to the linker.

?? Thoughts?

@josephg josephg changed the title Webassembly linker does not imported symbols Webassembly linker does not import symbols Oct 2, 2018
@andrewrk
Copy link
Member

andrewrk commented Oct 3, 2018

Thanks for trying this out. I wonder, how is the linker expecting to find symbols? Normally you would pass in .o files or .a files which have the symbols, or for dynamically linked libraries it may make sense to allow undefined symbols. For webassembly, it seems that you should be able to pass in other webassembly modules to the linker to resolve the symbols. Does it let you do this?

If so then I think the export "foo" makes sense. That says that the symbol is expected to be found dynamically in a module named foo, and that would add it to the allow-undef list.

@josephg
Copy link
Author

josephg commented Oct 3, 2018

In a sense, all wasm modules are dynamically linked. But all the external symbols need to be injected from javascript (or from whatever environment embeds it:

const fs = require('fs')
const m = new WebAssembly.Module(fs.readFileSync('xyz'))
const env = {
  inc(x) { return x+1 }
}
const i = new WebAssembly.Instance(m, {env})
console.log(i.exports.add(1,2)) // returns 4

It looks like you can hook up multiple wasm binaries and link their symbols together if you compile libraries with --relocatable.

With wasm2.zig:

export fn inc(a: i32) i32 {
  return a+1;
}

You can build if you pass --relocatable to wasm-ld:

$ zig build-obj --release-small --target-arch wasm32 wasm2.zig 
$ wasm-ld wasm2.o -o wasm2r.wasm -O2 --relocatable
$ wasm-ld wasm.o wasm2r.wasm -o xyz -O2 --no-entry
(module
...
  (func $add (type 1) (param i32 i32) (result i32)
    get_local 0
    call $inc
    get_local 1
    i32.add)
  (func $inc (type 2) (param i32) (result i32)
    get_local 0
    i32.const 1
    i32.add)
...
  (export "add" (func $add))
  (export "inc" (func $inc)))

@josephg
Copy link
Author

josephg commented Oct 3, 2018

... Ideally you'd want to be able to make a library which imports a bunch of functions from javascript (eg cargo's web-sys crate ) and then you could link to that & call functions from zig. You still need the undefined symbol list when linking the final result though - the linker wants to know which functions your javascript runtime is expected to provide.

@andrewrk
Copy link
Member

andrewrk commented Oct 3, 2018

Here's what I think should happen:

  • zig build-lib --static passes the --relocatable flag. Also zig build-obj does this. The reason they both do the same thing is that I'm considering deprecating zig build-obj in favor of zig build-lib --static.
  • extern "wasm" as you mentioned, will mark the symbol as --allow-undefined.
  • --object foo will let you add relocatable webassembly modules to the linker line. I believe this already works. This can resolve extern symbols that are not expected to be found at runtime.

Sound good?

@andrewrk andrewrk added this to the 0.4.0 milestone Oct 3, 2018
@josephg
Copy link
Author

josephg commented Oct 4, 2018

Points 2 and 3 I agree with. That sounds good 👍

I'm not sure about deprecating build-obj - My spider sense is telling me that build-obj will be useful for something, or that there's probably things you can do with a set of object files that you can't do with a relocatable library. Creating a relocatable library certainly changes the contents - but I'm not sure if those changes matter.

@bnoordhuis thoughts?

sephsmac:zig josephg$ wasm2wat wasm.o 
(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (type (;1;) (func (param i32) (result i32)))
  (import "env" "__linear_memory" (memory (;0;) 0))
  (import "env" "__indirect_function_table" (table (;0;) 0 anyfunc))
  (import "env" "inc" (func (;0;) (type 1)))
  (func (;1;) (type 0) (param i32 i32) (result i32)
    get_local 0
    call 0
    get_local 1
    i32.add))

sephsmac:zig josephg$ wasm-ld wasm.o -o wasmolib.wasm --relocatable
sephsmac:zig josephg$ wasm2wat wasmolib.wasm 
(module
  (type (;0;) (func (param i32) (result i32)))
  (type (;1;) (func (param i32 i32) (result i32)))
  (import "env" "inc" (func $inc (type 0)))
  (func $add (type 1) (param i32 i32) (result i32)
    get_local 0
    call $inc
    get_local 1
    i32.add)
  (table (;0;) 1 1 anyfunc)
  (memory (;0;) 0))

@bnoordhuis
Copy link
Contributor

Andrew's proposal sounds good. I don't have a strong opinion on zig build-obj.

One thing about --relocatable though: it affects link-time dead code elimination since it disables --gc-sections and --merge-data-segments. Is that what people expect from zig build-lib --static?

(Not a leading question. I genuinely don't know.)

@andrewrk
Copy link
Member

andrewrk commented Oct 5, 2018

One thing about --relocatable though: it affects link-time dead code elimination since it disables --gc-sections and --merge-data-segments. Is that what people expect from zig build-lib --static?

Yes. When you build a static library it has all the symbols available for use, and then the linker does a final garbage collection when you link against the static library, omitting unused symbols.

@andrewrk
Copy link
Member

andrewrk commented Oct 5, 2018

@josephg regarding build-obj, the self hosted compiler puts a lot of effort into breaking up a compilation into many .o files to parallelize the build process. My thoughts here are that, in general, a static library accomplishes what an object file does (potentially even better since it allows garbage collection as described above; see also #54), plus a static library can have multiple object files in it, so that we can have this parallelized build process.

jedisct1 added a commit to jedisct1/zig that referenced this issue Dec 25, 2022
In ziglang#1622, when targeting WebAsembly, the --allow-undefined flag
became unconditionally added to the linker.

This is not always desirable.

First, this is error prone. Code with references to unkown symbols
will link just fine, but then fail at run-time.

This behavior is inconsistent with all other targets.

For freestanding wasm applications, and applications that only use
WASI, undefined references are better reported at compile-time.

This behavior is also inconsistent with clang itself. Autoconf and
cmake scripts checking for function presence think that all tested
functions exist, but then resulting application cannot run.

For example, this is one of the reasons compilation of Ruby 3.2.0
to WASI fails with zig cc, while it works out of the box with clang.
But all applications checking for symbol existence before compilation
are affected.

This reverts the behavior to the one Zig had before ziglang#1622, and
introduces an `import_symbols` flag to ignore undefined symbols,
assuming that the webassembly runtime will define them.
jedisct1 added a commit to jedisct1/zig that referenced this issue Dec 25, 2022
In ziglang#1622, when targeting WebAsembly, the --allow-undefined flag
became unconditionally added to the linker.

This is not always desirable.

First, this is error prone. Code with references to unkown symbols
will link just fine, but then fail at run-time.

This behavior is inconsistent with all other targets.

For freestanding wasm applications, and applications that only use
WASI, undefined references are better reported at compile-time.

This behavior is also inconsistent with clang itself. Autoconf and
cmake scripts checking for function presence think that all tested
functions exist, but then resulting application cannot run.

For example, this is one of the reasons compilation of Ruby 3.2.0
to WASI fails with zig cc, while it works out of the box with clang.
But all applications checking for symbol existence before compilation
are affected.

This reverts the behavior to the one Zig had before ziglang#1622, and
introduces an `import_symbols` flag to ignore undefined symbols,
assuming that the webassembly runtime will define them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants