-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Interop with non-C ABIs in WebAssembly #16639
Comments
Rust has a similar option hidden behind an unstable flag - rust-lang/rust#83788. It seems though that the mapping from rust types to wasm types in their case is partly defined by llvm implementation details, which seems like something to avoid. Better to be super explicit and make the user explicitly map types like slices and options into wasm-friendly types. |
I like this proposal. What do you think, @Luukdegram? |
https://github.com/jamii/dida/blob/main/bindings/js_common.zig#L142 is an example of the kind of comptime wrapper I'm thinking of. That one just passes |
I like this proposal also. We've briefly touched on this topic during the compiler-meeting on 2021-11-25, though I never got around to writing a proper proposal for it. I'm excited @jamii did! Also, we should benefit from Zig being pre-1.0 and experiment with our own Wasm ABI definition, rather than attempting to interop with existing ones such as from Rust. So I'm in favor of @jamii's comment. I do have some questions regarding the proposed rules:
What about other scalar types, such as u8? Are they disallowed, or would they represent the smallest possible Wasm type?
Zig allows for arbitrary vector sizes using Also, I'd like to suggest looking into Wasm's component model, and especially its ABI. It currently doesn't support multi-value returns, but it's worthwhile to verify whether this can/will support the use case as described by the OP. |
I'd be inclined to push as much into libraries as possible, and focus the abi on allowing libraries to express every wasm type. Even the struct unpacking I proposed isn't entirely necessary. So a really minimal proposal:
The component model abi is mostly about serde of values in linear memory. I think that the only relevant part here is how some values are flattened into parameters. This code should be easy to write in comptime zig: MAX_FLAT_PARAMS = 16
MAX_FLAT_RESULTS = 1
def flatten_functype(ft, context):
flat_params = flatten_types(ft.param_types())
if len(flat_params) > MAX_FLAT_PARAMS:
flat_params = ['i32']
flat_results = flatten_types(ft.result_types())
if len(flat_results) > MAX_FLAT_RESULTS:
match context:
case 'lift':
flat_results = ['i32']
case 'lower':
flat_params += ['i32']
flat_results = []
return CoreFuncType(flat_params, flat_results)
def flatten_types(ts):
return [ft for t in ts for ft in flatten_type(t)]
def flatten_type(t):
match despecialize(t):
case Bool() : return ['i32']
case U8() | U16() | U32() : return ['i32']
case S8() | S16() | S32() : return ['i32']
case S64() | U64() : return ['i64']
case Float32() : return ['f32']
case Float64() : return ['f64']
case Char() : return ['i32']
case String() | List(_) : return ['i32', 'i32']
case Record(fields) : return flatten_record(fields)
case Variant(cases) : return flatten_variant(cases)
case Flags(labels) : return ['i32'] * num_i32_flags(labels)
case Own(_) | Borrow(_) : return ['i32']
def flatten_variant(cases):
flat = []
for c in cases:
if c.t is not None:
for i,ft in enumerate(flatten_type(c.t)):
if i < len(flat):
flat[i] = join(flat[i], ft)
else:
flat.append(ft)
return flatten_type(discriminant_type(cases)) + flat
def flatten_record(fields):
flat = []
for f in fields:
flat += flatten_type(f.t)
return flat
def join(a, b):
if a == b: return a
if (a == 'i32' and b == 'f32') or (a == 'f32' and b == 'i32'): return 'i32'
return 'i64' Once the spec supports multivalue we'll have When |
Is there any update on this? I have a project to which this would be useful and would be very interested in changes such as this, especially the multi-value returns.
I believe it does now. The github for the proposal is archived and the readme says it was accepted 4 years ago. |
Currently, functions exported from zig use the clang ABI when compiling to wasm. This is perfect for interop with other languages that use the same ABI.
For interop with weirder targets, it would be useful to be able to import/export functions for any type signature that wasm supports, rather than just the subset used by the clang ABI.
Currently in zig it's not possible to import or export a function if the wasm type has:
The clang ABI also passes structs/arrays with more than one field by reference, so it's not possible in zig to pass structs by value in imported/exported functions. This is only an annoyance for params because we can manually unpack the fields in most case, but there is no workaround for returning structs by value.
This will affect interop with any non-C ABI (eg AssemblyScript can export functions that zig cannot import). I ran into this when writing a toy language runtime that passes around type-tagged pointers by value as
(i32, i32)
:The clang ABI here requires passing both input and output by reference instead of by value. As mentioned above, we can work around this for the input by manually unpacking it, but there is currently no way to opt in to using multivalue returns from zig.
The workaround available at the moment is to generate a wasm wrapper which reverses the loads/stores generated by the clang ABI.
An ideal solution might look something like this:
The rules for
callconv(.Wasm)
would be:multivalue
is not in the target feature set, using structs and arrays in the return type is a compile-time error.Example:
The goal would be to provide the minimum features necessary to allow other ABIs to be expressed in zig via comptime wrappers or codegen. (Eg emulating wasm-bindgen, implementing the wasm component ABI, or writing the runtime for a non-C-like language).
I'm not sure how much of this would have to be supported by upstream llvm. Eg is there bytecode you can emit to get multivalue results, or is it only available via the experimental-mv flag? In the worst case, it might be possible to polyfill by compiling with
callconv(.C)
and then generating wrappers like the one I wrote above.The text was updated successfully, but these errors were encountered: