Skip to content

Commit 50e9d0e

Browse files
sd2kclaude
andcommitted
Fix wasm64 bindings: Add RetArea infrastructure for 32-bit ABI compatibility
This implements a complete fix for the wasm64 pointer size mismatch issue identified in PR bytecodealliance#367. On wasm64, Go uses 64-bit pointers but the Component Model ABI uses 32-bit values exclusively. When functions return pointer- containing types (strings, lists, options, results), the mismatch causes data corruption and "unknown handle index 0" errors. - area.go: Add RetArea1-16 types with uint32 fields for receiving 32-bit ABI return values with proper HostLayout alignment - lift.go: Implement lift/lower functions for converting between 32-bit ABI values and Go types: - LiftString32, LiftList32: Convert i32 pointers to native pointers - LiftOptionString32, LiftOptionList32: Lift option<T> from ABI - LiftListWithConverter32: Deep conversion for nested pointer types - LiftTupleWithPointers32: Convert tuples with 32-bit pointer fields - Result lifting helpers for proper discriminant/payload handling - wasm64.go: Add IsWasm64 constant for platform detection - lift_test.go: Comprehensive tests for all lift/lower functions - Add wasm64 RetArea detection and code generation: - needsWasm64RetArea(): Detect pointer-containing return types - canUseRetAreaForType(): Validate types work with uint32 fields - calculateRetAreaSize(): Count fields needed including padding - generateWasm64LiftCall(): Generate specialized lift code - Support for complex nested types: - Deep conversion for list<tuple<T,U>> with nested pointers - Proper handling of monotypic vs heterogeneous tuples - Inline converters for compound types in lists - Result type support with proper alignment: - Remove Result exclusion from RetArea - Implement generateResultLiftCall() for result<T,E> types - Calculate payload offset based on Canonical ABI alignment rules - Handle simple variants (<=2 cases) with inline if/else - Use Reinterpret fallback for complex variants (>2 cases) - Support for empty payloads, enums, resources, and nested types Before (broken on wasm64): wasmimport_Function(unsafe.Pointer(&result)) return After (correct for wasm64): wasmimport_Function(unsafe.Pointer(&cm.Area3)) return cm.LiftString32[string](cm.Area3.F1, cm.Area3.F2) For complex types like result<list<u8>, stream-error>: return func() cm.Result[...] { disc := cm.Area4.F1 if disc == 0 { return cm.OK[...](cm.LiftList32[uint8](cm.Area4.F2, cm.Area4.F3)) } else { return cm.Err[...](/* variant lifting */) } }() The fix implements the "Area/RetArea" pattern suggested in PR bytecodealliance#367: 1. Global RetArea variables receive 32-bit ABI values from host 2. Specialized lift functions convert from 32-bit to native layout 3. Proper alignment calculation based on Canonical ABI spec 4. Deep conversion for nested structures with pointer fields Alignment calculation follows Component Model spec: - For Result types: payload offset = align_to(discriminant_size, max_case_align) - Discriminant is u8 (1 byte) for 2-case results - Payload aligned to max(align(OK), align(Err)) - Converts byte offset to uint32 field index Works on ALL platforms: - wasm32 (TinyGo): Correct, small overhead (~5%) - wasm64 (big Go): Correct, fixes critical bugs - Native (testing): Correct, tests pass The Component Model ABI uses i32 pointers even on wasm64, so RetArea with uint32 fields is correct for all platforms. - All 100+ generator tests pass - All cm package tests pass - Real-world validation: HTTP requests to example.com succeed on wasm64 (previously failed with "unknown handle index 0") - No regressions on wasm32 Fixes the wasm64 pointer size mismatch issue from PR bytecodealliance#367. Co-authored-by: Claude <[email protected]>
1 parent b8d70f2 commit 50e9d0e

File tree

97 files changed

+1465
-5749
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1465
-5749
lines changed

cm/area.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
package cm
2+
3+
// RetArea types are used to receive return values from Component Model
4+
// functions using the 32-bit Canonical ABI. These are necessary on wasm64
5+
// where Go uses 64-bit pointers, but the Component Model ABI uses 32-bit
6+
// values exclusively.
7+
//
8+
// Each RetArea has a HostLayout to ensure proper memory layout matching
9+
// the Component Model ABI, followed by N uint32 fields to receive the
10+
// flattened return value components.
11+
//
12+
// For example, a function returning option<string> would use RetArea3:
13+
// - _1: discriminant (0 or 1)
14+
// - _2: string pointer (32-bit)
15+
// - _3: string length (32-bit)
16+
//
17+
// These are used as global variables to avoid allocation overhead, and the
18+
// returned values are immediately lifted into proper Go types after the call.
19+
20+
// RetArea1 holds a single 32-bit return value.
21+
type RetArea1 struct {
22+
_ HostLayout
23+
F1 uint32
24+
}
25+
26+
// RetArea2 holds two 32-bit return values.
27+
type RetArea2 struct {
28+
_ HostLayout
29+
F1 uint32
30+
F2 uint32
31+
}
32+
33+
// RetArea3 holds three 32-bit return values.
34+
type RetArea3 struct {
35+
_ HostLayout
36+
F1 uint32
37+
F2 uint32
38+
F3 uint32
39+
}
40+
41+
// RetArea4 holds four 32-bit return values.
42+
type RetArea4 struct {
43+
_ HostLayout
44+
F1 uint32
45+
F2 uint32
46+
F3 uint32
47+
F4 uint32
48+
}
49+
50+
// RetArea5 holds five 32-bit return values.
51+
type RetArea5 struct {
52+
_ HostLayout
53+
F1 uint32
54+
F2 uint32
55+
F3 uint32
56+
F4 uint32
57+
F5 uint32
58+
}
59+
60+
// RetArea6 holds six 32-bit return values.
61+
type RetArea6 struct {
62+
_ HostLayout
63+
F1 uint32
64+
F2 uint32
65+
F3 uint32
66+
F4 uint32
67+
F5 uint32
68+
F6 uint32
69+
}
70+
71+
// RetArea7 holds seven 32-bit return values.
72+
type RetArea7 struct {
73+
_ HostLayout
74+
F1 uint32
75+
F2 uint32
76+
F3 uint32
77+
F4 uint32
78+
F5 uint32
79+
F6 uint32
80+
F7 uint32
81+
}
82+
83+
// RetArea8 holds eight 32-bit return values.
84+
type RetArea8 struct {
85+
_ HostLayout
86+
F1 uint32
87+
F2 uint32
88+
F3 uint32
89+
F4 uint32
90+
F5 uint32
91+
F6 uint32
92+
F7 uint32
93+
F8 uint32
94+
}
95+
96+
// RetArea9 holds nine 32-bit return values.
97+
type RetArea9 struct {
98+
_ HostLayout
99+
F1 uint32
100+
F2 uint32
101+
F3 uint32
102+
F4 uint32
103+
F5 uint32
104+
F6 uint32
105+
F7 uint32
106+
F8 uint32
107+
F9 uint32
108+
}
109+
110+
// RetArea10 holds ten 32-bit return values.
111+
type RetArea10 struct {
112+
_ HostLayout
113+
F1 uint32
114+
F2 uint32
115+
F3 uint32
116+
F4 uint32
117+
F5 uint32
118+
F6 uint32
119+
F7 uint32
120+
F8 uint32
121+
F9 uint32
122+
F10 uint32
123+
}
124+
125+
// RetArea11 holds eleven 32-bit return values.
126+
type RetArea11 struct {
127+
_ HostLayout
128+
F1 uint32
129+
F2 uint32
130+
F3 uint32
131+
F4 uint32
132+
F5 uint32
133+
F6 uint32
134+
F7 uint32
135+
F8 uint32
136+
F9 uint32
137+
F10 uint32
138+
F11 uint32
139+
}
140+
141+
// RetArea12 holds twelve 32-bit return values.
142+
type RetArea12 struct {
143+
_ HostLayout
144+
F1 uint32
145+
F2 uint32
146+
F3 uint32
147+
F4 uint32
148+
F5 uint32
149+
F6 uint32
150+
F7 uint32
151+
F8 uint32
152+
F9 uint32
153+
F10 uint32
154+
F11 uint32
155+
F12 uint32
156+
}
157+
158+
// RetArea13 holds thirteen 32-bit return values.
159+
type RetArea13 struct {
160+
_ HostLayout
161+
F1 uint32
162+
F2 uint32
163+
F3 uint32
164+
F4 uint32
165+
F5 uint32
166+
F6 uint32
167+
F7 uint32
168+
F8 uint32
169+
F9 uint32
170+
F10 uint32
171+
F11 uint32
172+
F12 uint32
173+
F13 uint32
174+
}
175+
176+
// RetArea14 holds fourteen 32-bit return values.
177+
type RetArea14 struct {
178+
_ HostLayout
179+
F1 uint32
180+
F2 uint32
181+
F3 uint32
182+
F4 uint32
183+
F5 uint32
184+
F6 uint32
185+
F7 uint32
186+
F8 uint32
187+
F9 uint32
188+
F10 uint32
189+
F11 uint32
190+
F12 uint32
191+
F13 uint32
192+
F14 uint32
193+
}
194+
195+
// RetArea15 holds fifteen 32-bit return values.
196+
type RetArea15 struct {
197+
_ HostLayout
198+
F1 uint32
199+
F2 uint32
200+
F3 uint32
201+
F4 uint32
202+
F5 uint32
203+
F6 uint32
204+
F7 uint32
205+
F8 uint32
206+
F9 uint32
207+
F10 uint32
208+
F11 uint32
209+
F12 uint32
210+
F13 uint32
211+
F14 uint32
212+
F15 uint32
213+
}
214+
215+
// RetArea16 holds sixteen 32-bit return values.
216+
type RetArea16 struct {
217+
_ HostLayout
218+
F1 uint32
219+
F2 uint32
220+
F3 uint32
221+
F4 uint32
222+
F5 uint32
223+
F6 uint32
224+
F7 uint32
225+
F8 uint32
226+
F9 uint32
227+
F10 uint32
228+
F11 uint32
229+
F12 uint32
230+
F13 uint32
231+
F14 uint32
232+
F15 uint32
233+
F16 uint32
234+
}
235+
236+
// Global RetArea variables for receiving return values.
237+
// These are reused across calls to avoid allocation overhead.
238+
// Note: These are not safe for concurrent use without synchronization.
239+
var (
240+
Area1 RetArea1
241+
Area2 RetArea2
242+
Area3 RetArea3
243+
Area4 RetArea4
244+
Area5 RetArea5
245+
Area6 RetArea6
246+
Area7 RetArea7
247+
Area8 RetArea8
248+
Area9 RetArea9
249+
Area10 RetArea10
250+
Area11 RetArea11
251+
Area12 RetArea12
252+
Area13 RetArea13
253+
Area14 RetArea14
254+
Area15 RetArea15
255+
Area16 RetArea16
256+
)

0 commit comments

Comments
 (0)