Skip to content

wasm function import same name, different types results in runtime unreachable #23181

@scottredig

Description

@scottredig

Zig Version

0.14.0

Steps to Reproduce and Observed Behavior

Encountered while trying to update zig-javascript-bridge to 0.14.0: scottredig/zig-javascript-bridge#12

Importing two functions with the same name and module in WASM is valid behavior, but currently either hits a wasm error for calling a function with invalid argument types, or hit an unreachable that appears to be related to an invalid @bitcast.

Minimal repro example:

const std = @import("std");

const Float = struct {
    pub extern "foo" fn @"foo"(f32) void;
};
const Int = struct {
    pub extern "foo" fn @"foo"(i32) void;
};

export fn main() void {
    Float.foo(1);
    Int.foo(2);
}

With release small produces this wat file:

(module
  (type $t0 (func (param f32)))
  (type $t1 (func))
  (type $t2 (func (param i64)))
  (import "foo" "foo" (func $foo.foo (type $t0)))
  (func $main (export "main") (type $t1)
    (call $foo.foo
      (f32.const 0x1p+0 (;=1;)))
    (call $f2
      (i64.const 2)))
  (func $f2 (type $t2) (param $p0 i64)
    (unreachable))
  (memory $memory (export "memory") 16)
  (global $g0 (mut i32) (i32.const 1048576)))

As you can see, the main function will successfully call foo once, but the second call in unconditionally an unreachable.

Expected Behavior

One of two outcomes are reasonable here:

  • This works, as it did in 0.13.0.
  • This produces a compile error.

I personally advocate for it just working being the correct choice. Specifically, it should just create two different function imports with the same import name/module. The javascript environment will gladly import the same function but with different types. Since Javascript doesn't have static typing (and most number types become f64 in JS's type system anyways), it's perfectly legal to call the same Javascript functions with differently typed variables. I specifically encountered this in a function to log values, so really any value is legal here. Not allowing this behavior means more code/function variations on the javascript side, and possible incompatibility with WASM hosts which expect this behavior to be legal.

This also worked just fine in 0.13.0. This is the wat from the same source code but compiled by 0.13.0:

(module
  (type $t0 (func (param f32)))
  (type $t1 (func (param i32)))
  (type $t2 (func))
  (import "foo" "foo" (func $foo.foo (type $t0)))
  (import "foo" "foo" (func $foo.foo_1 (type $t1)))
  (func $main (export "main") (type $t2)
    (call $foo.foo
      (f32.const 0x1p+0 (;=1;)))
    (call $foo.foo_1
      (i32.const 2)))
  (memory $memory (export "memory") 16)
  (global $g0 (mut i32) (i32.const 1048576)))

Producing a compile error would also be reasonable, though not preferred. Just making one of the functions (really, all but the first analyzed) error when called in runtime is rather silly :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions