@@ -21,12 +21,15 @@ struct WasmEncodingBases {
2121 typed_first_func_index : u32 ,
2222 struct_local_idx : u32 ,
2323 eq_local_idx : u32 ,
24+ i31_local_idx : u32 ,
2425 typed_local_base : u32 ,
2526 struct_global_idx : u32 ,
2627 eq_global_idx : u32 ,
28+ i31_global_idx : u32 ,
2729 typed_global_base : u32 ,
2830 struct_table_idx : u32 ,
2931 eq_table_idx : u32 ,
32+ i31_table_idx : u32 ,
3033 typed_table_base : u32 ,
3134}
3235
@@ -118,6 +121,17 @@ impl GcOps {
118121 vec ! [ ] ,
119122 ) ;
120123
124+ // 6: `take_i31`
125+ //
126+ // Takes a `(ref null i31)` along with the guest's inline `i31.get_s`
127+ // and `i31.get_u` results, so the host can re-derive them and assert
128+ // that its view of the i31 matches the Wasm instructions'.
129+ let take_i31_type_idx = types. len ( ) ;
130+ types. ty ( ) . function (
131+ vec ! [ ValType :: Ref ( RefType :: I31REF ) , ValType :: I32 , ValType :: I32 ] ,
132+ vec ! [ ] ,
133+ ) ;
134+
121135 let struct_type_base: u32 = types. len ( ) ;
122136
123137 // Build the type-id-to-wasm-index map from the pre-computed
@@ -190,6 +204,7 @@ impl GcOps {
190204 imports. import ( "" , "make_refs" , EntityType :: Function ( 3 ) ) ;
191205 imports. import ( "" , "take_struct" , EntityType :: Function ( 4 ) ) ;
192206 imports. import ( "" , "take_eq" , EntityType :: Function ( 5 ) ) ;
207+ imports. import ( "" , "take_i31" , EntityType :: Function ( take_i31_type_idx) ) ;
193208
194209 // For each of our concrete struct types, define a function
195210 // import that takes an argument of that concrete type.
@@ -235,6 +250,15 @@ impl GcOps {
235250 shared : false ,
236251 } ) ;
237252
253+ let i31_table_idx = tables. len ( ) ;
254+ tables. table ( TableType {
255+ element_type : RefType :: I31REF ,
256+ minimum : u64:: from ( self . limits . table_size ) ,
257+ maximum : None ,
258+ table64 : false ,
259+ shared : false ,
260+ } ) ;
261+
238262 let typed_table_base = tables. len ( ) ;
239263 for i in 0 ..struct_count {
240264 let concrete = struct_type_base + i;
@@ -297,6 +321,17 @@ impl GcOps {
297321 } ) ,
298322 ) ;
299323
324+ // Add exactly one (ref.null i31) global.
325+ let i31_global_idx = globals. len ( ) ;
326+ globals. global (
327+ wasm_encoder:: GlobalType {
328+ val_type : ValType :: Ref ( RefType :: I31REF ) ,
329+ mutable : true ,
330+ shared : false ,
331+ } ,
332+ & ConstExpr :: ref_null ( wasm_encoder:: HeapType :: I31 ) ,
333+ ) ;
334+
300335 // Add one typed (ref <type>) global per struct type.
301336 let typed_global_base = globals. len ( ) ;
302337 for i in 0 ..struct_count {
@@ -343,7 +378,10 @@ impl GcOps {
343378 let eq_local_idx = struct_local_idx + 1 ;
344379 local_decls. push ( ( 1 , ValType :: Ref ( RefType :: EQREF ) ) ) ;
345380
346- let typed_local_base: u32 = eq_local_idx + 1 ;
381+ let i31_local_idx = eq_local_idx + 1 ;
382+ local_decls. push ( ( 1 , ValType :: Ref ( RefType :: I31REF ) ) ) ;
383+
384+ let typed_local_base: u32 = i31_local_idx + 1 ;
347385 for i in 0 ..struct_count {
348386 let concrete = struct_type_base + i;
349387 local_decls. push ( (
@@ -360,12 +398,15 @@ impl GcOps {
360398 typed_first_func_index,
361399 struct_local_idx,
362400 eq_local_idx,
401+ i31_local_idx,
363402 typed_local_base,
364403 struct_global_idx,
365404 eq_global_idx,
405+ i31_global_idx,
366406 typed_global_base,
367407 struct_table_idx,
368408 eq_table_idx,
409+ i31_table_idx,
369410 typed_table_base,
370411 } ;
371412
@@ -722,6 +763,79 @@ macro_rules! for_each_gc_op {
722763 type_index = type_index. checked_rem( num_types) ?;
723764 } ) ]
724765 StructSet { type_index: u32 , field_index: u32 } ,
766+
767+ #[ operands( [ ] ) ]
768+ #[ results( [ I31 ] ) ]
769+ NullI31 ,
770+
771+ #[ operands( [ ] ) ]
772+ #[ results( [ I31 ] ) ]
773+ #[ fixup( |_limits, _num_types| {
774+ // Any `i32` is a valid operand to `ref.i31` (it wraps to 31
775+ // bits), so no clamping is needed.
776+ } ) ]
777+ RefI31 { value: u32 } ,
778+
779+ #[ operands( [ Some ( I31 ) ] ) ]
780+ #[ results( [ ] ) ]
781+ I31LocalSet ,
782+
783+ #[ operands( [ ] ) ]
784+ #[ results( [ I31 ] ) ]
785+ I31LocalGet ,
786+
787+ #[ operands( [ Some ( I31 ) ] ) ]
788+ #[ results( [ ] ) ]
789+ I31GlobalSet ,
790+
791+ #[ operands( [ ] ) ]
792+ #[ results( [ I31 ] ) ]
793+ I31GlobalGet ,
794+
795+ #[ operands( [ Some ( I31 ) ] ) ]
796+ #[ results( [ ] ) ]
797+ #[ fixup( |limits, _num_types| {
798+ // Add one to make sure that out-of-bounds table accesses are
799+ // possible, but still rare.
800+ elem_index = elem_index % ( limits. table_size + 1 ) ;
801+ } ) ]
802+ I31TableSet { elem_index: u32 } ,
803+
804+ #[ operands( [ ] ) ]
805+ #[ results( [ I31 ] ) ]
806+ #[ fixup( |limits, _num_types| {
807+ // Add one to make sure that out-of-bounds table accesses are
808+ // possible, but still rare.
809+ elem_index = elem_index % ( limits. table_size + 1 ) ;
810+ } ) ]
811+ I31TableGet { elem_index: u32 } ,
812+
813+ #[ operands( [ Some ( I31 ) ] ) ]
814+ #[ results( [ ] ) ]
815+ TakeI31Call ,
816+
817+ #[ operands( [ Some ( I31 ) ] ) ]
818+ #[ results( [ ] ) ]
819+ I31GetS ,
820+
821+ #[ operands( [ Some ( I31 ) ] ) ]
822+ #[ results( [ ] ) ]
823+ I31GetU ,
824+
825+ #[ operands( [ Some ( Struct ( None ) ) ] ) ]
826+ #[ results( [ Eq ] ) ]
827+ StructRefAsEq ,
828+
829+ #[ operands( [ Some ( Struct ( Some ( type_index) ) ) ] ) ]
830+ #[ results( [ Eq ] ) ]
831+ #[ fixup( |_limits, num_types| {
832+ type_index = type_index. checked_rem( num_types) ?;
833+ } ) ]
834+ TypedStructRefAsEq { type_index: u32 } ,
835+
836+ #[ operands( [ Some ( I31 ) ] ) ]
837+ #[ results( [ Eq ] ) ]
838+ I31RefAsEq ,
725839 }
726840 } ;
727841}
@@ -913,6 +1027,7 @@ impl GcOp {
9131027 let make_refs_func_idx = 2 ;
9141028 let take_structref_idx = 3 ;
9151029 let take_eqref_idx = 4 ;
1030+ let take_i31_idx = 5 ;
9161031
9171032 match * self {
9181033 Self :: Gc => {
@@ -1229,6 +1344,84 @@ impl GcOp {
12291344 }
12301345 }
12311346 }
1347+ Self :: NullI31 => {
1348+ func. instruction ( & Instruction :: RefNull ( wasm_encoder:: HeapType :: I31 ) ) ;
1349+ }
1350+ Self :: RefI31 { value } => {
1351+ func. instruction ( & Instruction :: I32Const ( value. cast_signed ( ) ) ) ;
1352+ func. instruction ( & Instruction :: RefI31 ) ;
1353+ }
1354+ Self :: I31LocalGet => {
1355+ func. instruction ( & Instruction :: LocalGet ( encoding_bases. i31_local_idx ) ) ;
1356+ }
1357+ Self :: I31LocalSet => {
1358+ func. instruction ( & Instruction :: LocalSet ( encoding_bases. i31_local_idx ) ) ;
1359+ }
1360+ Self :: I31GlobalGet => {
1361+ func. instruction ( & Instruction :: GlobalGet ( encoding_bases. i31_global_idx ) ) ;
1362+ }
1363+ Self :: I31GlobalSet => {
1364+ func. instruction ( & Instruction :: GlobalSet ( encoding_bases. i31_global_idx ) ) ;
1365+ }
1366+ Self :: I31TableGet { elem_index } => {
1367+ func. instruction ( & Instruction :: I32Const ( elem_index. cast_signed ( ) ) ) ;
1368+ func. instruction ( & Instruction :: TableGet ( encoding_bases. i31_table_idx ) ) ;
1369+ }
1370+ Self :: I31TableSet { elem_index } => {
1371+ // Use i31_local_idx (i31ref) to temporarily store the value before table.set.
1372+ func. instruction ( & Instruction :: LocalSet ( encoding_bases. i31_local_idx ) ) ;
1373+ func. instruction ( & Instruction :: I32Const ( elem_index. cast_signed ( ) ) ) ;
1374+ func. instruction ( & Instruction :: LocalGet ( encoding_bases. i31_local_idx ) ) ;
1375+ func. instruction ( & Instruction :: TableSet ( encoding_bases. i31_table_idx ) ) ;
1376+ }
1377+ Self :: StructRefAsEq | Self :: TypedStructRefAsEq { .. } | Self :: I31RefAsEq => {
1378+ // Upcasting to `eqref` is implicit in Wasm subtyping: both
1379+ // `struct` and `i31` are subtypes of `eq`, so the value already
1380+ // on the stack is a valid `eqref` and no instruction is
1381+ // required. Only the abstract stack type changes (via
1382+ // `result_types`).
1383+ }
1384+ Self :: I31GetS | Self :: I31GetU => {
1385+ // `i31.get_s`/`i31.get_u` trap on a null reference, so guard
1386+ // against null: save the ref, test it, and only perform the
1387+ // get when non-null. The i32 result is not tracked on the
1388+ // abstract stack, so drop it.
1389+ func. instruction ( & Instruction :: LocalTee ( encoding_bases. i31_local_idx ) ) ;
1390+ func. instruction ( & Instruction :: RefIsNull ) ;
1391+ func. instruction ( & Instruction :: If ( wasm_encoder:: BlockType :: Empty ) ) ;
1392+ func. instruction ( & Instruction :: Else ) ;
1393+ func. instruction ( & Instruction :: LocalGet ( encoding_bases. i31_local_idx ) ) ;
1394+ if matches ! ( self , Self :: I31GetS ) {
1395+ func. instruction ( & Instruction :: I31GetS ) ;
1396+ } else {
1397+ func. instruction ( & Instruction :: I31GetU ) ;
1398+ }
1399+ func. instruction ( & Instruction :: Drop ) ;
1400+ func. instruction ( & Instruction :: End ) ;
1401+ }
1402+ Self :: TakeI31Call => {
1403+ // Differential check: pass the i31ref plus the guest's inline
1404+ // `i31.get_s` and `i31.get_u` results to the host, which
1405+ // re-derives them and asserts they match. The get instructions
1406+ // trap on null, so guard against it.
1407+ func. instruction ( & Instruction :: LocalTee ( encoding_bases. i31_local_idx ) ) ;
1408+ func. instruction ( & Instruction :: RefIsNull ) ;
1409+ func. instruction ( & Instruction :: If ( wasm_encoder:: BlockType :: Empty ) ) ;
1410+ // Null branch: `take_i31(null, 0, 0)`.
1411+ func. instruction ( & Instruction :: RefNull ( wasm_encoder:: HeapType :: I31 ) ) ;
1412+ func. instruction ( & Instruction :: I32Const ( 0 ) ) ;
1413+ func. instruction ( & Instruction :: I32Const ( 0 ) ) ;
1414+ func. instruction ( & Instruction :: Call ( take_i31_idx) ) ;
1415+ func. instruction ( & Instruction :: Else ) ;
1416+ // Non-null branch: `take_i31(ref, i31.get_s, i31.get_u)`.
1417+ func. instruction ( & Instruction :: LocalGet ( encoding_bases. i31_local_idx ) ) ;
1418+ func. instruction ( & Instruction :: LocalGet ( encoding_bases. i31_local_idx ) ) ;
1419+ func. instruction ( & Instruction :: I31GetS ) ;
1420+ func. instruction ( & Instruction :: LocalGet ( encoding_bases. i31_local_idx ) ) ;
1421+ func. instruction ( & Instruction :: I31GetU ) ;
1422+ func. instruction ( & Instruction :: Call ( take_i31_idx) ) ;
1423+ func. instruction ( & Instruction :: End ) ;
1424+ }
12321425 }
12331426 }
12341427}
0 commit comments