fix: Handle Wasm ValueTypes returned by imports - #141
Merged
Merged
Conversation
blaine-arcjet
force-pushed
the
phated/host-import-return-value
branch
from
December 19, 2025 18:29
6ab6e43 to
1d1a789
Compare
blaine-arcjet
force-pushed
the
phated/host-import-return-value
branch
from
January 2, 2026 21:23
5b3a384 to
4c7e1ce
Compare
blaine-arcjet
marked this pull request as ready for review
January 2, 2026 21:27
wooorm-arcjet
approved these changes
Jan 5, 2026
blaine-arcjet
force-pushed
the
phated/host-import-return-value
branch
from
January 5, 2026 16:04
4c7e1ce to
538bb51
Compare
blaine-arcjet
marked this pull request as draft
January 5, 2026 16:20
Contributor
Author
|
This caused some other problems in codegen so I'm drafting it. |
Take main's import codegen in full. This branch moved import handling out of func.rs into a separate imported_func.rs; since then main has kept imports in func.rs behind Direction::Import and added records, variants, options, enum lift and indirect parameters there, none of which the copy has. The import return types this branch set out to fix are addressed on top of main's structure in the following commits instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host import is registered with wazero's WithFunc, which takes the function's Wasm signature from its Go types: uint32 for i32, uint64 for i64, float32 for f32 and float64 for f64. The scalar lowering instructions produced the uint64 encoding api.Function.Call expects instead, so an import returning s32, s8, u8, s16, u16, f32 or f64 generated Go that did not compile, and one returning s64 or char made gravity panic. - I32FromS32, I32FromS16, I32FromU16, I32FromS8 and I32FromU8 emit uint32(x), and I64FromS64 emits uint64(x). Go's conversion sign-extends signed values, so uint64 of the result is the same value api.EncodeI32 produced when CallWasm passes it to an export. - CoreF32FromF32 and CoreF64FromF64 pass the float through for imports and keep api.EncodeF32/EncodeF64 for exports, since uint64(float) is a numeric conversion rather than a bit copy. - Implement S64FromI64, I32FromChar and CharFromI32, and resolve WIT char to Go rune. Lifting a char panics on a surrogate or a value above U+10FFFF, as the canonical ABI requires. The iface-method-returns-string example gains an import and export per scalar type, with Go tests that round-trip boundary values through the guest, alongside the get-u32 case from this pull request's earlier commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
arcjet-rei
marked this pull request as ready for review
October 3, 2026 01:10
arcjet-rei
approved these changes
Oct 3, 2026
arcjet-rei
left a comment
Contributor
There was a problem hiding this comment.
Vale @blaine-arcjet! Thanks for putting this together, and I hope this implementation meets with your approval.
arcjet-rei
added a commit
that referenced
this pull request
Oct 3, 2026
* fix: store and load every value type at its own width Values that pass through guest memory (record fields, list elements, a host import's result, an export's indirect parameters) were written and read with the wrong width or type: - F32Store and F64Store called WriteUint64Le. On the import side, where #141 made floats plain float32 and float64, the generated Go no longer compiled; on the export side, F32Store wrote 8 bytes for a 4-byte value and overwrote whatever followed it. Imports now use WriteFloat32Le/WriteFloat64Le and exports write the IEEE bits at the value's width. F32Load read 8 bytes as well; it now reads 4. - I32Store8 only accepted 0 and 1 and panicked otherwise, so a u8, s8, enum or option<u8> payload above 1 panicked. It now stores the low byte of any value. - I32Store16, I64Store, I32Load8S, I32Load16U and I32Load16S were todo!(), so u16, s16, u64, s64 and s8 values in memory crashed gravity. They are implemented, with the loads sharing a read_memory helper for the failed-read check. - Each store converts its operand explicitly, and the integer lifts use Go conversions instead of api.DecodeI32/DecodeU32, so they accept the uint64 CallWasm returns as well as the narrower values loads produce. The float lifts pass Go floats through on the import side. The new memory example passes a record of every scalar type (17 flattened values, so it always travels through memory) to an export, back from an export, to a host import and back from it, plus a 68-byte record whose trailing f32 sits directly before a string allocation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: round-trip IEEE special floats through memory Send +Inf, -Inf, NaN and -0 as the f32 and f64 fields through an export's result, a host import's parameter and a host import's result. The comparison treats any NaN as equal to any NaN, since reflect.DeepEqual cannot, and checks the sign so -0 is not mistaken for +0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This reworks Wasm imports to build functions with explicit Wasm ValueTypes based on the Wasm signature provided by the WIT tooling. This requires that we use the
stackprovided by Wazero but that makes using the variousapi.Decode*andapi.Encode*functions work correctly on Wasm imports.I believe this resolves some issues introduced by my changes in #96
Changes made to finish this pull request
blaine-arcjet has left Arcjet, so this pull request was completed with Claude Code. The last two commits replace the approach described above. The earlier commits stay in the branch history.
Why the approach changed. The original commits moved import handling out of
func.rsinto a separateimported_func.rsand built each host function from wazero's stack. Since then, main has kept import handling infunc.rsbehindDirection::Importand added records, variants, options, enum lifting and indirect parameters there, none of which the separate file has. At this branch's previous head, every scalar import return type exceptu32,boolandstringstill reachedtodo!("TODO(#9): handle return type"). Merging main into the branch therefore takes main'sfunc.rsin full and removesimported_func.rs, and the fix is built on main's structure.What was wrong on main. Gravity registers each host import with wazero's
WithFunc, which reads the function's Wasm signature from its Go types:uint32for i32,uint64for i64,float32andfloat64for the floats. The scalar lowering instructions produced theuint64encoding thatapi.Function.Callexpects instead. An import returnings32,s8,u8,s16,u16,f32orf64generated Go that did not compile, and one returnings64orcharmade gravity panic.What this changes.
uint32(x)anduint64(x). Go's conversion sign-extends signed values, so onceCallWasmwidens the result touint64, exports receive the same valueapi.EncodeI32produced.api.EncodeF32/EncodeF64for exports, becauseuint64(float)converts the number rather than copying its bits.S64FromI64,I32FromCharandCharFromI32are implemented, and WITcharmaps to Gorune. Lifting acharrejects surrogates and values above U+10FFFF.Tests. The
get-u32round-trip test from the original commits is kept. Theiface-method-returns-stringexample gains a host import and an export for each scalar type, with Go tests that send boundary values (minimum, maximum, -1, ±Inf, U+10FFFF) through the guest and back.Co-authored-by: 零Rei 212411920+arcjet-rei@users.noreply.github.com
🤖 Generated with Claude Code