1 use super::{ArrayInit, GcCompiler};
2 use crate::bounds_checks::BoundsCheck;
3 use crate::func_environ::{Extension, FuncEnvironment};
4 use crate::translate::{Heap, HeapData, StructFieldsVec, TargetEnvironment};
5 use crate::trap::TranslateTrap;
6 use crate::{Reachability, TRAP_INTERNAL_ASSERT};
7 use cranelift_codegen::ir::immediates::Offset32;
8 use cranelift_codegen::ir::{BlockArg, ExceptionTableData, ExceptionTableItem};
9 use cranelift_codegen::{
10 cursor::FuncCursor,
11 ir::{self, InstBuilder, condcodes::IntCC},
12 };
13 use cranelift_entity::packed_option::ReservedValue;
14 use cranelift_frontend::FunctionBuilder;
15 use smallvec::{SmallVec, smallvec};
16 use wasmtime_environ::{
17 Collector, GcArrayLayout, GcLayout, GcStructLayout, I31_DISCRIMINANT, ModuleInternedTypeIndex,
18 PtrSize, TagIndex, TypeIndex, VMGcKind, WasmCompositeInnerType, WasmHeapTopType, WasmHeapType,
19 WasmRefType, WasmResult, WasmStorageType, WasmValType, wasm_unsupported,
20 };
21
22 #[cfg(feature = "gc-drc")]
23 mod drc;
24 #[cfg(feature = "gc-null")]
25 mod null;
26
27 /// Get the default GC compiler.
gc_compiler(func_env: &mut FuncEnvironment<'_>) -> WasmResult<Box<dyn GcCompiler>>28 pub fn gc_compiler(func_env: &mut FuncEnvironment<'_>) -> WasmResult<Box<dyn GcCompiler>> {
29 // If this function requires a GC compiler, that is not too bad of an
30 // over-approximation for it requiring a GC heap.
31 func_env.needs_gc_heap = true;
32
33 match func_env.tunables.collector {
34 #[cfg(feature = "gc-drc")]
35 Some(Collector::DeferredReferenceCounting) => Ok(Box::new(drc::DrcCompiler::default())),
36 #[cfg(not(feature = "gc-drc"))]
37 Some(Collector::DeferredReferenceCounting) => Err(wasm_unsupported!(
38 "the DRC collector is unavailable because the `gc-drc` feature \
39 was disabled at compile time",
40 )),
41
42 #[cfg(feature = "gc-null")]
43 Some(Collector::Null) => Ok(Box::new(null::NullCompiler::default())),
44 #[cfg(not(feature = "gc-null"))]
45 Some(Collector::Null) => Err(wasm_unsupported!(
46 "the null collector is unavailable because the `gc-null` feature \
47 was disabled at compile time",
48 )),
49
50 #[cfg(any(feature = "gc-drc", feature = "gc-null"))]
51 None => Err(wasm_unsupported!(
52 "support for GC types disabled at configuration time"
53 )),
54 #[cfg(not(any(feature = "gc-drc", feature = "gc-null")))]
55 None => Err(wasm_unsupported!(
56 "support for GC types disabled because no collector implementation \
57 was selected at compile time; enable one of the `gc-drc` or \
58 `gc-null` features",
59 )),
60 }
61 }
62
63 #[cfg_attr(
64 not(feature = "gc-drc"),
65 expect(dead_code, reason = "easier to define")
66 )]
unbarriered_load_gc_ref( builder: &mut FunctionBuilder, ty: WasmHeapType, ptr_to_gc_ref: ir::Value, flags: ir::MemFlags, ) -> WasmResult<ir::Value>67 fn unbarriered_load_gc_ref(
68 builder: &mut FunctionBuilder,
69 ty: WasmHeapType,
70 ptr_to_gc_ref: ir::Value,
71 flags: ir::MemFlags,
72 ) -> WasmResult<ir::Value> {
73 debug_assert!(ty.is_vmgcref_type());
74 let gc_ref = builder.ins().load(ir::types::I32, flags, ptr_to_gc_ref, 0);
75 if ty != WasmHeapType::I31 {
76 builder.declare_value_needs_stack_map(gc_ref);
77 }
78 Ok(gc_ref)
79 }
80
81 #[cfg_attr(
82 not(any(feature = "gc-drc", feature = "gc-null")),
83 expect(dead_code, reason = "easier to define")
84 )]
unbarriered_store_gc_ref( builder: &mut FunctionBuilder, ty: WasmHeapType, dst: ir::Value, gc_ref: ir::Value, flags: ir::MemFlags, ) -> WasmResult<()>85 fn unbarriered_store_gc_ref(
86 builder: &mut FunctionBuilder,
87 ty: WasmHeapType,
88 dst: ir::Value,
89 gc_ref: ir::Value,
90 flags: ir::MemFlags,
91 ) -> WasmResult<()> {
92 debug_assert!(ty.is_vmgcref_type());
93 builder.ins().store(flags, gc_ref, dst, 0);
94 Ok(())
95 }
96
97 /// Emit inline CLIF code that asserts an object's `VMGcKind` matches the
98 /// expected kind. Only emits code when `cfg(gc_zeal)` is enabled.
99 ///
100 /// `gc_ref` must be a non-null, non-i31 GC reference (i32 heap index).
emit_gc_kind_assert( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, gc_ref: ir::Value, expected_kind: VMGcKind, )101 fn emit_gc_kind_assert(
102 func_env: &mut FuncEnvironment<'_>,
103 builder: &mut FunctionBuilder<'_>,
104 gc_ref: ir::Value,
105 expected_kind: VMGcKind,
106 ) {
107 if !cfg!(gc_zeal) {
108 return;
109 }
110
111 func_env.trapz(builder, gc_ref, crate::TRAP_NULL_REFERENCE);
112
113 let kind_addr = func_env.prepare_gc_ref_access(
114 builder,
115 gc_ref,
116 BoundsCheck::StaticObjectField {
117 offset: wasmtime_environ::VM_GC_HEADER_KIND_OFFSET,
118 access_size: wasmtime_environ::VM_GC_KIND_SIZE,
119 object_size: wasmtime_environ::VM_GC_HEADER_SIZE,
120 },
121 );
122 let kind_and_reserved_bits = builder.ins().load(
123 ir::types::I32,
124 ir::MemFlags::trusted().with_readonly(),
125 kind_addr,
126 0,
127 );
128 let kind_mask = builder
129 .ins()
130 .iconst(ir::types::I32, i64::from(VMGcKind::MASK));
131 let actual_kind = builder.ins().band(kind_and_reserved_bits, kind_mask);
132
133 let expected_kind = builder
134 .ins()
135 .iconst(ir::types::I32, i64::from(expected_kind.as_u32()));
136
137 // NB: Do a subtype check rather than a strict equality check. See
138 // `VMGcKind::matches` for details.
139 let and = builder.ins().band(actual_kind, expected_kind);
140 let matches = builder.ins().icmp(IntCC::Equal, and, expected_kind);
141
142 builder.ins().trapz(matches, TRAP_INTERNAL_ASSERT);
143 }
144
145 /// Read a struct field or array element from its raw address in the GC heap.
146 ///
147 /// The given address MUST have already been bounds-checked via
148 /// `prepare_gc_ref_access`.
read_field_at_addr( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, ty: WasmStorageType, addr: ir::Value, extension: Option<Extension>, ) -> WasmResult<ir::Value>149 fn read_field_at_addr(
150 func_env: &mut FuncEnvironment<'_>,
151 builder: &mut FunctionBuilder<'_>,
152 ty: WasmStorageType,
153 addr: ir::Value,
154 extension: Option<Extension>,
155 ) -> WasmResult<ir::Value> {
156 assert_eq!(extension.is_none(), matches!(ty, WasmStorageType::Val(_)));
157 assert_eq!(
158 extension.is_some(),
159 matches!(ty, WasmStorageType::I8 | WasmStorageType::I16)
160 );
161
162 // Data inside GC objects is always little endian.
163 let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
164
165 let value = match ty {
166 WasmStorageType::I8 => builder.ins().load(ir::types::I8, flags, addr, 0),
167 WasmStorageType::I16 => builder.ins().load(ir::types::I16, flags, addr, 0),
168 WasmStorageType::Val(v) => match v {
169 WasmValType::I32 => builder.ins().load(ir::types::I32, flags, addr, 0),
170 WasmValType::I64 => builder.ins().load(ir::types::I64, flags, addr, 0),
171 WasmValType::F32 => builder.ins().load(ir::types::F32, flags, addr, 0),
172 WasmValType::F64 => builder.ins().load(ir::types::F64, flags, addr, 0),
173 WasmValType::V128 => builder.ins().load(ir::types::I8X16, flags, addr, 0),
174 WasmValType::Ref(r) => match r.heap_type.top() {
175 WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => {
176 gc_compiler(func_env)?
177 .translate_read_gc_reference(func_env, builder, r, addr, flags)?
178 }
179 WasmHeapTopType::Func => {
180 let expected_ty = match r.heap_type {
181 WasmHeapType::Func => ModuleInternedTypeIndex::reserved_value(),
182 WasmHeapType::ConcreteFunc(ty) => ty.unwrap_module_type_index(),
183 WasmHeapType::NoFunc => {
184 let null = builder.ins().iconst(func_env.pointer_type(), 0);
185 if !r.nullable {
186 // Because `nofunc` is uninhabited, and this
187 // reference is non-null, this is unreachable
188 // code. Unconditionally trap via conditional
189 // trap instructions to avoid inserting block
190 // terminators in the middle of this block.
191 builder.ins().trapz(null, TRAP_INTERNAL_ASSERT);
192 }
193 return Ok(null);
194 }
195 _ => unreachable!("not a function heap type"),
196 };
197 let expected_ty = builder
198 .ins()
199 .iconst(ir::types::I32, i64::from(expected_ty.as_bits()));
200
201 let vmctx = func_env.vmctx_val(&mut builder.cursor());
202
203 let func_ref_id = builder.ins().load(ir::types::I32, flags, addr, 0);
204 let get_interned_func_ref = func_env
205 .builtin_functions
206 .get_interned_func_ref(builder.func);
207
208 let call_inst = builder
209 .ins()
210 .call(get_interned_func_ref, &[vmctx, func_ref_id, expected_ty]);
211 builder.func.dfg.first_result(call_inst)
212 }
213 WasmHeapTopType::Cont => {
214 // TODO(#10248) GC integration for stack switching
215 return Err(wasmtime_environ::WasmError::Unsupported(
216 "Stack switching feature not compatible with GC, yet".to_string(),
217 ));
218 }
219 },
220 },
221 };
222
223 let value = match extension {
224 Some(Extension::Sign) => builder.ins().sextend(ir::types::I32, value),
225 Some(Extension::Zero) => builder.ins().uextend(ir::types::I32, value),
226 None => value,
227 };
228
229 Ok(value)
230 }
231
write_func_ref_at_addr( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, ref_type: WasmRefType, flags: ir::MemFlags, field_addr: ir::Value, func_ref: ir::Value, ) -> WasmResult<()>232 fn write_func_ref_at_addr(
233 func_env: &mut FuncEnvironment<'_>,
234 builder: &mut FunctionBuilder<'_>,
235 ref_type: WasmRefType,
236 flags: ir::MemFlags,
237 field_addr: ir::Value,
238 func_ref: ir::Value,
239 ) -> WasmResult<()> {
240 assert_eq!(ref_type.heap_type.top(), WasmHeapTopType::Func);
241
242 let vmctx = func_env.vmctx_val(&mut builder.cursor());
243
244 let intern_func_ref_for_gc_heap = func_env
245 .builtin_functions
246 .intern_func_ref_for_gc_heap(builder.func);
247
248 let func_ref = if ref_type.heap_type == WasmHeapType::NoFunc {
249 let null = builder.ins().iconst(func_env.pointer_type(), 0);
250 if !ref_type.nullable {
251 // Because `nofunc` is uninhabited, and this reference is
252 // non-null, this is unreachable code. Unconditionally trap
253 // via conditional trap instructions to avoid inserting
254 // block terminators in the middle of this block.
255 builder.ins().trapz(null, TRAP_INTERNAL_ASSERT);
256 }
257 null
258 } else {
259 func_ref
260 };
261
262 // Convert the raw `funcref` into a `FuncRefTableId` for use in the
263 // GC heap.
264 let call_inst = builder
265 .ins()
266 .call(intern_func_ref_for_gc_heap, &[vmctx, func_ref]);
267 let func_ref_id = builder.func.dfg.first_result(call_inst);
268 let func_ref_id = builder.ins().ireduce(ir::types::I32, func_ref_id);
269
270 // Store the id in the field.
271 builder.ins().store(flags, func_ref_id, field_addr, 0);
272
273 Ok(())
274 }
275
write_field_at_addr( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, field_ty: WasmStorageType, field_addr: ir::Value, new_val: ir::Value, ) -> WasmResult<()>276 fn write_field_at_addr(
277 func_env: &mut FuncEnvironment<'_>,
278 builder: &mut FunctionBuilder<'_>,
279 field_ty: WasmStorageType,
280 field_addr: ir::Value,
281 new_val: ir::Value,
282 ) -> WasmResult<()> {
283 // Data inside GC objects is always little endian.
284 let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
285
286 match field_ty {
287 WasmStorageType::I8 => {
288 builder.ins().istore8(flags, new_val, field_addr, 0);
289 }
290 WasmStorageType::I16 => {
291 builder.ins().istore16(flags, new_val, field_addr, 0);
292 }
293 WasmStorageType::Val(WasmValType::Ref(r)) if r.heap_type.top() == WasmHeapTopType::Func => {
294 write_func_ref_at_addr(func_env, builder, r, flags, field_addr, new_val)?;
295 }
296 WasmStorageType::Val(WasmValType::Ref(r)) => {
297 gc_compiler(func_env)?
298 .translate_write_gc_reference(func_env, builder, r, field_addr, new_val, flags)?;
299 }
300 WasmStorageType::Val(_) => {
301 assert_eq!(
302 builder.func.dfg.value_type(new_val).bytes(),
303 wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty)
304 );
305 builder.ins().store(flags, new_val, field_addr, 0);
306 }
307 }
308 Ok(())
309 }
310
translate_struct_new( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, struct_type_index: TypeIndex, fields: &[ir::Value], ) -> WasmResult<ir::Value>311 pub fn translate_struct_new(
312 func_env: &mut FuncEnvironment<'_>,
313 builder: &mut FunctionBuilder<'_>,
314 struct_type_index: TypeIndex,
315 fields: &[ir::Value],
316 ) -> WasmResult<ir::Value> {
317 gc_compiler(func_env)?.alloc_struct(func_env, builder, struct_type_index, &fields)
318 }
319
default_value( cursor: &mut FuncCursor, func_env: &FuncEnvironment<'_>, ty: &WasmStorageType, ) -> ir::Value320 fn default_value(
321 cursor: &mut FuncCursor,
322 func_env: &FuncEnvironment<'_>,
323 ty: &WasmStorageType,
324 ) -> ir::Value {
325 match ty {
326 WasmStorageType::I8 | WasmStorageType::I16 => cursor.ins().iconst(ir::types::I32, 0),
327 WasmStorageType::Val(v) => match v {
328 WasmValType::I32 => cursor.ins().iconst(ir::types::I32, 0),
329 WasmValType::I64 => cursor.ins().iconst(ir::types::I64, 0),
330 WasmValType::F32 => cursor.ins().f32const(0.0),
331 WasmValType::F64 => cursor.ins().f64const(0.0),
332 WasmValType::V128 => {
333 let c = cursor.func.dfg.constants.insert(vec![0; 16].into());
334 cursor.ins().vconst(ir::types::I8X16, c)
335 }
336 WasmValType::Ref(r) => {
337 assert!(r.nullable);
338 let (ty, needs_stack_map) = func_env.reference_type(r.heap_type);
339
340 // NB: The collector doesn't need to know about null references.
341 let _ = needs_stack_map;
342
343 cursor.ins().iconst(ty, 0)
344 }
345 },
346 }
347 }
348
translate_struct_new_default( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, struct_type_index: TypeIndex, ) -> WasmResult<ir::Value>349 pub fn translate_struct_new_default(
350 func_env: &mut FuncEnvironment<'_>,
351 builder: &mut FunctionBuilder<'_>,
352 struct_type_index: TypeIndex,
353 ) -> WasmResult<ir::Value> {
354 let interned_ty = func_env.module.types[struct_type_index].unwrap_module_type_index();
355 let struct_ty = func_env.types.unwrap_struct(interned_ty)?;
356 let fields = struct_ty
357 .fields
358 .iter()
359 .map(|f| default_value(&mut builder.cursor(), func_env, &f.element_type))
360 .collect::<StructFieldsVec>();
361 gc_compiler(func_env)?.alloc_struct(func_env, builder, struct_type_index, &fields)
362 }
363
translate_struct_get( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, struct_type_index: TypeIndex, field_index: u32, struct_ref: ir::Value, extension: Option<Extension>, ) -> WasmResult<ir::Value>364 pub fn translate_struct_get(
365 func_env: &mut FuncEnvironment<'_>,
366 builder: &mut FunctionBuilder<'_>,
367 struct_type_index: TypeIndex,
368 field_index: u32,
369 struct_ref: ir::Value,
370 extension: Option<Extension>,
371 ) -> WasmResult<ir::Value> {
372 log::trace!(
373 "translate_struct_get({struct_type_index:?}, {field_index:?}, {struct_ref:?}, {extension:?})"
374 );
375
376 // TODO: If we know we have a `(ref $my_struct)` here, instead of maybe a
377 // `(ref null $my_struct)`, we could omit the `trapz`. But plumbing that
378 // type info from `wasmparser` and through to here is a bit funky.
379 func_env.trapz(builder, struct_ref, crate::TRAP_NULL_REFERENCE);
380
381 emit_gc_kind_assert(func_env, builder, struct_ref, VMGcKind::StructRef);
382
383 let field_index = usize::try_from(field_index).unwrap();
384 let interned_type_index = func_env.module.types[struct_type_index].unwrap_module_type_index();
385
386 let struct_layout = func_env.struct_or_exn_layout(interned_type_index);
387 let struct_size = struct_layout.size;
388
389 let field_offset = struct_layout.fields[field_index].offset;
390 let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];
391 let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);
392 assert!(field_offset + field_size <= struct_size);
393
394 let field_addr = func_env.prepare_gc_ref_access(
395 builder,
396 struct_ref,
397 BoundsCheck::StaticObjectField {
398 offset: field_offset,
399 access_size: u8::try_from(field_size).unwrap(),
400 object_size: struct_size,
401 },
402 );
403
404 let result = read_field_at_addr(
405 func_env,
406 builder,
407 field_ty.element_type,
408 field_addr,
409 extension,
410 );
411 log::trace!("translate_struct_get(..) -> {result:?}");
412 result
413 }
414
translate_struct_set( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, struct_type_index: TypeIndex, field_index: u32, struct_ref: ir::Value, new_val: ir::Value, ) -> WasmResult<()>415 pub fn translate_struct_set(
416 func_env: &mut FuncEnvironment<'_>,
417 builder: &mut FunctionBuilder<'_>,
418 struct_type_index: TypeIndex,
419 field_index: u32,
420 struct_ref: ir::Value,
421 new_val: ir::Value,
422 ) -> WasmResult<()> {
423 log::trace!(
424 "translate_struct_set({struct_type_index:?}, {field_index:?}, struct_ref: {struct_ref:?}, new_val: {new_val:?})"
425 );
426
427 // TODO: See comment in `translate_struct_get` about the `trapz`.
428 func_env.trapz(builder, struct_ref, crate::TRAP_NULL_REFERENCE);
429
430 emit_gc_kind_assert(func_env, builder, struct_ref, VMGcKind::StructRef);
431
432 let field_index = usize::try_from(field_index).unwrap();
433 let interned_type_index = func_env.module.types[struct_type_index].unwrap_module_type_index();
434
435 let struct_layout = func_env.struct_or_exn_layout(interned_type_index);
436 let struct_size = struct_layout.size;
437
438 let field_offset = struct_layout.fields[field_index].offset;
439 let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];
440 let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);
441 assert!(field_offset + field_size <= struct_size);
442
443 let field_addr = func_env.prepare_gc_ref_access(
444 builder,
445 struct_ref,
446 BoundsCheck::StaticObjectField {
447 offset: field_offset,
448 access_size: u8::try_from(field_size).unwrap(),
449 object_size: struct_size,
450 },
451 );
452
453 write_field_at_addr(
454 func_env,
455 builder,
456 field_ty.element_type,
457 field_addr,
458 new_val,
459 )?;
460
461 log::trace!("translate_struct_set: finished");
462 Ok(())
463 }
464
translate_exn_unbox( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, tag_index: TagIndex, exn_ref: ir::Value, ) -> WasmResult<SmallVec<[ir::Value; 4]>>465 pub fn translate_exn_unbox(
466 func_env: &mut FuncEnvironment<'_>,
467 builder: &mut FunctionBuilder<'_>,
468 tag_index: TagIndex,
469 exn_ref: ir::Value,
470 ) -> WasmResult<SmallVec<[ir::Value; 4]>> {
471 log::trace!("translate_exn_unbox({tag_index:?}, {exn_ref:?})");
472
473 // We know that the `exn_ref` is not null because we reach this
474 // operation only in catch blocks, and throws are initiated from
475 // runtime code that checks for nulls first.
476
477 // Get the GcExceptionLayout associated with this tag's
478 // function type, and generate loads for each field.
479 let exception_ty_idx = func_env
480 .exception_type_from_tag(tag_index)
481 .unwrap_module_type_index();
482 let exception_ty = func_env.types.unwrap_exn(exception_ty_idx)?;
483 let exn_layout = func_env.struct_or_exn_layout(exception_ty_idx);
484 let exn_size = exn_layout.size;
485
486 // Gather accesses first because these require a borrow on
487 // `func_env`, which we later mutate below via
488 // `prepare_gc_ref_access()`.
489 let mut accesses: SmallVec<[_; 4]> = smallvec![];
490 for (field_ty, field_layout) in exception_ty.fields.iter().zip(exn_layout.fields.iter()) {
491 accesses.push((field_layout.offset, field_ty.element_type));
492 }
493
494 let mut result = smallvec![];
495 for (field_offset, field_ty) in accesses {
496 let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty);
497 assert!(field_offset + field_size <= exn_size);
498 let field_addr = func_env.prepare_gc_ref_access(
499 builder,
500 exn_ref,
501 BoundsCheck::StaticObjectField {
502 offset: field_offset,
503 access_size: u8::try_from(field_size).unwrap(),
504 object_size: exn_size,
505 },
506 );
507
508 let value = read_field_at_addr(func_env, builder, field_ty, field_addr, None)?;
509 result.push(value);
510 }
511
512 log::trace!("translate_exn_unbox(..) -> {result:?}");
513 Ok(result)
514 }
515
translate_exn_throw( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, tag_index: TagIndex, args: &[ir::Value], ) -> WasmResult<()>516 pub fn translate_exn_throw(
517 func_env: &mut FuncEnvironment<'_>,
518 builder: &mut FunctionBuilder<'_>,
519 tag_index: TagIndex,
520 args: &[ir::Value],
521 ) -> WasmResult<()> {
522 let (instance_id, defined_tag_id) = func_env.get_instance_and_tag(builder, tag_index);
523 let exnref = gc_compiler(func_env)?.alloc_exn(
524 func_env,
525 builder,
526 tag_index,
527 args,
528 instance_id,
529 defined_tag_id,
530 )?;
531 translate_exn_throw_ref(func_env, builder, exnref)
532 }
533
translate_exn_throw_ref( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, exnref: ir::Value, ) -> WasmResult<()>534 pub fn translate_exn_throw_ref(
535 func_env: &mut FuncEnvironment<'_>,
536 builder: &mut FunctionBuilder<'_>,
537 exnref: ir::Value,
538 ) -> WasmResult<()> {
539 let builtin = func_env.builtin_functions.throw_ref(builder.func);
540 let sig = builder.func.dfg.ext_funcs[builtin].signature;
541 let vmctx = func_env.vmctx_val(&mut builder.cursor());
542
543 // Generate a `try_call` with handlers from the current
544 // stack. This libcall is unique among libcall implementations of
545 // opcodes: we know the others will not throw, but `throw_ref`'s
546 // entire purpose is to throw. So if there are any handlers in the
547 // local function body, we need to attach them to this callsite
548 // like any other.
549 let continuation = builder.create_block();
550 let current_block = builder.current_block().unwrap();
551 builder.insert_block_after(continuation, current_block);
552 let continuation_call = builder.func.dfg.block_call(continuation, &[]);
553 let mut table_items = vec![ExceptionTableItem::Context(vmctx)];
554 for (tag, block) in func_env.stacks.handlers.handlers() {
555 let block_call = builder
556 .func
557 .dfg
558 .block_call(block, &[BlockArg::TryCallExn(0)]);
559 table_items.push(match tag {
560 Some(tag) => ExceptionTableItem::Tag(tag, block_call),
561 None => ExceptionTableItem::Default(block_call),
562 });
563 }
564 let etd = ExceptionTableData::new(sig, continuation_call, table_items);
565 let et = builder.func.dfg.exception_tables.push(etd);
566
567 builder.ins().try_call(builtin, &[vmctx, exnref], et);
568
569 builder.switch_to_block(continuation);
570 builder.seal_block(continuation);
571 func_env.trap(builder, crate::TRAP_UNREACHABLE);
572
573 Ok(())
574 }
575
translate_array_new( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder, array_type_index: TypeIndex, elem: ir::Value, len: ir::Value, ) -> WasmResult<ir::Value>576 pub fn translate_array_new(
577 func_env: &mut FuncEnvironment<'_>,
578 builder: &mut FunctionBuilder,
579 array_type_index: TypeIndex,
580 elem: ir::Value,
581 len: ir::Value,
582 ) -> WasmResult<ir::Value> {
583 log::trace!("translate_array_new({array_type_index:?}, {elem:?}, {len:?})");
584 let result = gc_compiler(func_env)?.alloc_array(
585 func_env,
586 builder,
587 array_type_index,
588 ArrayInit::Fill { elem, len },
589 )?;
590 log::trace!("translate_array_new(..) -> {result:?}");
591 Ok(result)
592 }
593
translate_array_new_default( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder, array_type_index: TypeIndex, len: ir::Value, ) -> WasmResult<ir::Value>594 pub fn translate_array_new_default(
595 func_env: &mut FuncEnvironment<'_>,
596 builder: &mut FunctionBuilder,
597 array_type_index: TypeIndex,
598 len: ir::Value,
599 ) -> WasmResult<ir::Value> {
600 log::trace!("translate_array_new_default({array_type_index:?}, {len:?})");
601
602 let interned_ty = func_env.module.types[array_type_index].unwrap_module_type_index();
603 let array_ty = func_env.types.unwrap_array(interned_ty)?;
604 let elem = default_value(&mut builder.cursor(), func_env, &array_ty.0.element_type);
605 let result = gc_compiler(func_env)?.alloc_array(
606 func_env,
607 builder,
608 array_type_index,
609 ArrayInit::Fill { elem, len },
610 )?;
611 log::trace!("translate_array_new_default(..) -> {result:?}");
612 Ok(result)
613 }
614
translate_array_new_fixed( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder, array_type_index: TypeIndex, elems: &[ir::Value], ) -> WasmResult<ir::Value>615 pub fn translate_array_new_fixed(
616 func_env: &mut FuncEnvironment<'_>,
617 builder: &mut FunctionBuilder,
618 array_type_index: TypeIndex,
619 elems: &[ir::Value],
620 ) -> WasmResult<ir::Value> {
621 log::trace!("translate_array_new_fixed({array_type_index:?}, {elems:?})");
622 let result = gc_compiler(func_env)?.alloc_array(
623 func_env,
624 builder,
625 array_type_index,
626 ArrayInit::Elems(elems),
627 )?;
628 log::trace!("translate_array_new_fixed(..) -> {result:?}");
629 Ok(result)
630 }
631
632 impl ArrayInit<'_> {
633 /// Get the length (as an `i32`-typed `ir::Value`) of these array elements.
634 #[cfg_attr(
635 not(any(feature = "gc-drc", feature = "gc-null")),
636 expect(dead_code, reason = "easier to define")
637 )]
len(self, pos: &mut FuncCursor) -> ir::Value638 fn len(self, pos: &mut FuncCursor) -> ir::Value {
639 match self {
640 ArrayInit::Fill { len, .. } => len,
641 ArrayInit::Elems(e) => {
642 let len = u32::try_from(e.len()).unwrap();
643 pos.ins().iconst(ir::types::I32, i64::from(len))
644 }
645 }
646 }
647
648 /// Initialize a newly-allocated array's elements.
649 #[cfg_attr(
650 not(any(feature = "gc-drc", feature = "gc-null")),
651 expect(dead_code, reason = "easier to define")
652 )]
initialize( self, func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, interned_type_index: ModuleInternedTypeIndex, base_size: u32, size: ir::Value, elems_addr: ir::Value, mut init_field: impl FnMut( &mut FuncEnvironment<'_>, &mut FunctionBuilder<'_>, WasmStorageType, ir::Value, ir::Value, ) -> WasmResult<()>, ) -> WasmResult<()>653 fn initialize(
654 self,
655 func_env: &mut FuncEnvironment<'_>,
656 builder: &mut FunctionBuilder<'_>,
657 interned_type_index: ModuleInternedTypeIndex,
658 base_size: u32,
659 size: ir::Value,
660 elems_addr: ir::Value,
661 mut init_field: impl FnMut(
662 &mut FuncEnvironment<'_>,
663 &mut FunctionBuilder<'_>,
664 WasmStorageType,
665 ir::Value,
666 ir::Value,
667 ) -> WasmResult<()>,
668 ) -> WasmResult<()> {
669 log::trace!(
670 "initialize_array({interned_type_index:?}, {base_size:?}, {size:?}, {elems_addr:?})"
671 );
672
673 assert!(!func_env.types[interned_type_index].composite_type.shared);
674 let array_ty = func_env.types[interned_type_index]
675 .composite_type
676 .inner
677 .unwrap_array();
678 let elem_ty = array_ty.0.element_type;
679 let elem_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&elem_ty);
680 let pointer_type = func_env.pointer_type();
681 let elem_size = builder.ins().iconst(pointer_type, i64::from(elem_size));
682 match self {
683 ArrayInit::Elems(elems) => {
684 let mut elem_addr = elems_addr;
685 for val in elems {
686 init_field(func_env, builder, elem_ty, elem_addr, *val)?;
687 elem_addr = builder.ins().iadd(elem_addr, elem_size);
688 }
689 }
690 ArrayInit::Fill { elem, len: _ } => {
691 // Compute the end address of the elements.
692 let base_size = builder.ins().iconst(pointer_type, i64::from(base_size));
693 let array_addr = builder.ins().isub(elems_addr, base_size);
694 let size = uextend_i32_to_pointer_type(builder, pointer_type, size);
695 let elems_end = builder.ins().iadd(array_addr, size);
696
697 emit_array_fill_impl(
698 func_env,
699 builder,
700 elems_addr,
701 elem_size,
702 elems_end,
703 |func_env, builder, elem_addr| {
704 init_field(func_env, builder, elem_ty, elem_addr, elem)
705 },
706 )?;
707 }
708 }
709 log::trace!("initialize_array: finished");
710 Ok(())
711 }
712 }
713
emit_array_fill_impl( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, elem_addr: ir::Value, elem_size: ir::Value, fill_end: ir::Value, mut emit_elem_write: impl FnMut( &mut FuncEnvironment<'_>, &mut FunctionBuilder<'_>, ir::Value, ) -> WasmResult<()>, ) -> WasmResult<()>714 fn emit_array_fill_impl(
715 func_env: &mut FuncEnvironment<'_>,
716 builder: &mut FunctionBuilder<'_>,
717 elem_addr: ir::Value,
718 elem_size: ir::Value,
719 fill_end: ir::Value,
720 mut emit_elem_write: impl FnMut(
721 &mut FuncEnvironment<'_>,
722 &mut FunctionBuilder<'_>,
723 ir::Value,
724 ) -> WasmResult<()>,
725 ) -> WasmResult<()> {
726 log::trace!(
727 "emit_array_fill_impl(elem_addr: {elem_addr:?}, elem_size: {elem_size:?}, fill_end: {fill_end:?})"
728 );
729
730 let pointer_ty = func_env.pointer_type();
731
732 assert_eq!(builder.func.dfg.value_type(elem_addr), pointer_ty);
733 assert_eq!(builder.func.dfg.value_type(elem_size), pointer_ty);
734 assert_eq!(builder.func.dfg.value_type(fill_end), pointer_ty);
735
736 // Loop to fill the elements, emitting the equivalent of the following
737 // pseudo-CLIF:
738 //
739 // current_block:
740 // ...
741 // jump loop_header_block(elem_addr)
742 //
743 // loop_header_block(elem_addr: i32):
744 // done = icmp eq elem_addr, fill_end
745 // brif done, continue_block, loop_body_block
746 //
747 // loop_body_block:
748 // emit_elem_write()
749 // next_elem_addr = iadd elem_addr, elem_size
750 // jump loop_header_block(next_elem_addr)
751 //
752 // continue_block:
753 // ...
754
755 let current_block = builder.current_block().unwrap();
756 let loop_header_block = builder.create_block();
757 let loop_body_block = builder.create_block();
758 let continue_block = builder.create_block();
759
760 builder.ensure_inserted_block();
761 builder.insert_block_after(loop_header_block, current_block);
762 builder.insert_block_after(loop_body_block, loop_header_block);
763 builder.insert_block_after(continue_block, loop_body_block);
764
765 // Current block: jump to the loop header block with the first element's
766 // address.
767 builder.ins().jump(loop_header_block, &[elem_addr.into()]);
768
769 // Loop header block: check if we're done, then jump to either the continue
770 // block or the loop body block.
771 builder.switch_to_block(loop_header_block);
772 builder.append_block_param(loop_header_block, pointer_ty);
773 log::trace!("emit_array_fill_impl: loop header");
774 func_env.translate_loop_header(builder)?;
775 let elem_addr = builder.block_params(loop_header_block)[0];
776 let done = builder.ins().icmp(IntCC::Equal, elem_addr, fill_end);
777 builder
778 .ins()
779 .brif(done, continue_block, &[], loop_body_block, &[]);
780
781 // Loop body block: write the value to the current element, compute the next
782 // element's address, and then jump back to the loop header block.
783 builder.switch_to_block(loop_body_block);
784 log::trace!("emit_array_fill_impl: loop body");
785 emit_elem_write(func_env, builder, elem_addr)?;
786 let next_elem_addr = builder.ins().iadd(elem_addr, elem_size);
787 builder
788 .ins()
789 .jump(loop_header_block, &[next_elem_addr.into()]);
790
791 // Continue...
792 builder.switch_to_block(continue_block);
793 log::trace!("emit_array_fill_impl: finished");
794 builder.seal_block(loop_header_block);
795 builder.seal_block(loop_body_block);
796 builder.seal_block(continue_block);
797 Ok(())
798 }
799
translate_array_fill( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, array_type_index: TypeIndex, array_ref: ir::Value, index: ir::Value, value: ir::Value, n: ir::Value, ) -> WasmResult<()>800 pub fn translate_array_fill(
801 func_env: &mut FuncEnvironment<'_>,
802 builder: &mut FunctionBuilder<'_>,
803 array_type_index: TypeIndex,
804 array_ref: ir::Value,
805 index: ir::Value,
806 value: ir::Value,
807 n: ir::Value,
808 ) -> WasmResult<()> {
809 log::trace!(
810 "translate_array_fill({array_type_index:?}, {array_ref:?}, {index:?}, {value:?}, {n:?})"
811 );
812
813 let len = translate_array_len(func_env, builder, array_ref)?;
814
815 // Check that the full range of elements we want to fill is within bounds.
816 let end_index = func_env.uadd_overflow_trap(builder, index, n, crate::TRAP_ARRAY_OUT_OF_BOUNDS);
817 let out_of_bounds = builder
818 .ins()
819 .icmp(IntCC::UnsignedGreaterThan, end_index, len);
820 func_env.trapnz(builder, out_of_bounds, crate::TRAP_ARRAY_OUT_OF_BOUNDS);
821
822 // Get the address of the first element we want to fill.
823 let interned_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();
824 let ArraySizeInfo {
825 obj_size,
826 one_elem_size,
827 base_size,
828 } = emit_array_size_info(func_env, builder, interned_type_index, len);
829 let offset_in_elems = builder.ins().imul(index, one_elem_size);
830 let obj_offset = builder.ins().iadd(base_size, offset_in_elems);
831 let elem_addr = func_env.prepare_gc_ref_access(
832 builder,
833 array_ref,
834 BoundsCheck::DynamicObjectField {
835 offset: obj_offset,
836 object_size: obj_size,
837 },
838 );
839
840 // Calculate the end address, just after the filled region.
841 let fill_size = builder.ins().imul(n, one_elem_size);
842 let fill_size = uextend_i32_to_pointer_type(builder, func_env.pointer_type(), fill_size);
843 let fill_end = builder.ins().iadd(elem_addr, fill_size);
844
845 let one_elem_size =
846 uextend_i32_to_pointer_type(builder, func_env.pointer_type(), one_elem_size);
847
848 let result = emit_array_fill_impl(
849 func_env,
850 builder,
851 elem_addr,
852 one_elem_size,
853 fill_end,
854 |func_env, builder, elem_addr| {
855 let elem_ty = func_env
856 .types
857 .unwrap_array(interned_type_index)?
858 .0
859 .element_type;
860 write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)
861 },
862 );
863 log::trace!("translate_array_fill(..) -> {result:?}");
864 result
865 }
866
translate_array_len( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder, array_ref: ir::Value, ) -> WasmResult<ir::Value>867 pub fn translate_array_len(
868 func_env: &mut FuncEnvironment<'_>,
869 builder: &mut FunctionBuilder,
870 array_ref: ir::Value,
871 ) -> WasmResult<ir::Value> {
872 log::trace!("translate_array_len({array_ref:?})");
873
874 func_env.trapz(builder, array_ref, crate::TRAP_NULL_REFERENCE);
875
876 let len_offset = gc_compiler(func_env)?.layouts().array_length_field_offset();
877 let len_field = func_env.prepare_gc_ref_access(
878 builder,
879 array_ref,
880 // Note: We can't bounds check the whole array object's size because we
881 // don't know its length yet. Chicken and egg problem.
882 BoundsCheck::StaticOffset {
883 offset: len_offset,
884 access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
885 },
886 );
887 let result = builder.ins().load(
888 ir::types::I32,
889 ir::MemFlags::trusted().with_readonly(),
890 len_field,
891 0,
892 );
893 log::trace!("translate_array_len(..) -> {result:?}");
894 Ok(result)
895 }
896
897 struct ArraySizeInfo {
898 /// The `i32` size of the whole array object, in bytes.
899 obj_size: ir::Value,
900
901 /// The `i32` size of each one of the array's elements, in bytes.
902 one_elem_size: ir::Value,
903
904 /// The `i32` size of the array's base object, in bytes. This is also the
905 /// offset from the start of the array object to its elements.
906 base_size: ir::Value,
907 }
908
909 /// Emit code to get the dynamic size (in bytes) of a whole array object, along
910 /// with some other related bits.
emit_array_size_info( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, array_type_index: ModuleInternedTypeIndex, array_len: ir::Value, ) -> ArraySizeInfo911 fn emit_array_size_info(
912 func_env: &mut FuncEnvironment<'_>,
913 builder: &mut FunctionBuilder<'_>,
914 array_type_index: ModuleInternedTypeIndex,
915 // `i32` value containing the array's length.
916 array_len: ir::Value,
917 ) -> ArraySizeInfo {
918 let array_layout = func_env.array_layout(array_type_index);
919
920 // Note that we check for overflow below because we can't trust the array's
921 // length: it came from inside the GC heap.
922 //
923 // We check for 32-bit multiplication overflow by performing a 64-bit
924 // multiplication and testing the high bits.
925 let one_elem_size = builder
926 .ins()
927 .iconst(ir::types::I64, i64::from(array_layout.elem_size));
928 let array_len = builder.ins().uextend(ir::types::I64, array_len);
929 let all_elems_size = builder.ins().imul(one_elem_size, array_len);
930
931 let high_bits = builder.ins().ushr_imm(all_elems_size, 32);
932 builder.ins().trapnz(high_bits, TRAP_INTERNAL_ASSERT);
933
934 let all_elems_size = builder.ins().ireduce(ir::types::I32, all_elems_size);
935 let base_size = builder
936 .ins()
937 .iconst(ir::types::I32, i64::from(array_layout.base_size));
938 let obj_size =
939 builder
940 .ins()
941 .uadd_overflow_trap(all_elems_size, base_size, TRAP_INTERNAL_ASSERT);
942
943 let one_elem_size = builder.ins().ireduce(ir::types::I32, one_elem_size);
944
945 ArraySizeInfo {
946 obj_size,
947 one_elem_size,
948 base_size,
949 }
950 }
951
952 /// Get the bounds-checked address of an element in an array.
953 ///
954 /// The emitted code will trap if `index >= array.length`.
955 ///
956 /// Returns the `ir::Value` containing the address of the `index`th element in
957 /// the array. You may read or write a value of the array's element type at this
958 /// address. You may not use it for any other kind of access, nor reuse this
959 /// value across GC safepoints.
array_elem_addr( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, array_type_index: ModuleInternedTypeIndex, array_ref: ir::Value, index: ir::Value, ) -> ir::Value960 fn array_elem_addr(
961 func_env: &mut FuncEnvironment<'_>,
962 builder: &mut FunctionBuilder<'_>,
963 array_type_index: ModuleInternedTypeIndex,
964 array_ref: ir::Value,
965 index: ir::Value,
966 ) -> ir::Value {
967 // First, assert that `index < array.length`.
968 //
969 // This check is visible at the Wasm-semantics level.
970 //
971 // TODO: We should emit spectre-safe bounds checks for array accesses (if
972 // configured) but we don't currently have a great way to do that here. The
973 // proper solution is to use linear memories to back GC heaps and reuse the
974 // code in `bounds_check.rs` to implement these bounds checks. That is all
975 // planned, but not yet implemented.
976
977 let len = translate_array_len(func_env, builder, array_ref).unwrap();
978
979 let in_bounds = builder.ins().icmp(IntCC::UnsignedLessThan, index, len);
980 func_env.trapz(builder, in_bounds, crate::TRAP_ARRAY_OUT_OF_BOUNDS);
981
982 // Compute the size (in bytes) of the whole array object.
983 let ArraySizeInfo {
984 obj_size,
985 one_elem_size,
986 base_size,
987 } = emit_array_size_info(func_env, builder, array_type_index, len);
988
989 // Compute the offset of the `index`th element within the array object.
990 //
991 // NB: no need to check for overflow here, since at this point we know that
992 // `len * elem_size + base_size` did not overflow and `i < len`.
993 let offset_in_elems = builder.ins().imul(index, one_elem_size);
994 let offset_in_array = builder.ins().iadd(offset_in_elems, base_size);
995
996 // Finally, use the object size and element offset we just computed to
997 // perform our implementation-internal bounds checks.
998 //
999 // Checking the whole object's size, rather than the `index`th element's
1000 // size allows these bounds checks to be deduplicated across repeated
1001 // accesses to the same array at different indices.
1002 //
1003 // This check should not be visible to Wasm, and serve to protect us from
1004 // our own implementation bugs. The goal is to keep any potential widgets
1005 // confined within the GC heap, and turn what would otherwise be a security
1006 // vulnerability into a simple bug.
1007 //
1008 // TODO: Ideally we should fold the first Wasm-visible bounds check into
1009 // this internal bounds check, so that we aren't performing multiple,
1010 // redundant bounds checks. But we should figure out how to do this in a way
1011 // that doesn't defeat the object-size bounds checking's deduplication
1012 // mentioned above.
1013 func_env.prepare_gc_ref_access(
1014 builder,
1015 array_ref,
1016 BoundsCheck::DynamicObjectField {
1017 offset: offset_in_array,
1018 object_size: obj_size,
1019 },
1020 )
1021 }
1022
translate_array_get( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder, array_type_index: TypeIndex, array_ref: ir::Value, index: ir::Value, extension: Option<Extension>, ) -> WasmResult<ir::Value>1023 pub fn translate_array_get(
1024 func_env: &mut FuncEnvironment<'_>,
1025 builder: &mut FunctionBuilder,
1026 array_type_index: TypeIndex,
1027 array_ref: ir::Value,
1028 index: ir::Value,
1029 extension: Option<Extension>,
1030 ) -> WasmResult<ir::Value> {
1031 log::trace!("translate_array_get({array_type_index:?}, {array_ref:?}, {index:?})");
1032
1033 emit_gc_kind_assert(func_env, builder, array_ref, VMGcKind::ArrayRef);
1034
1035 let array_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();
1036 let elem_addr = array_elem_addr(func_env, builder, array_type_index, array_ref, index);
1037
1038 let array_ty = func_env.types.unwrap_array(array_type_index)?;
1039 let elem_ty = array_ty.0.element_type;
1040
1041 let result = read_field_at_addr(func_env, builder, elem_ty, elem_addr, extension)?;
1042 log::trace!("translate_array_get(..) -> {result:?}");
1043 Ok(result)
1044 }
1045
translate_array_set( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder, array_type_index: TypeIndex, array_ref: ir::Value, index: ir::Value, value: ir::Value, ) -> WasmResult<()>1046 pub fn translate_array_set(
1047 func_env: &mut FuncEnvironment<'_>,
1048 builder: &mut FunctionBuilder,
1049 array_type_index: TypeIndex,
1050 array_ref: ir::Value,
1051 index: ir::Value,
1052 value: ir::Value,
1053 ) -> WasmResult<()> {
1054 log::trace!("translate_array_set({array_type_index:?}, {array_ref:?}, {index:?}, {value:?})");
1055
1056 emit_gc_kind_assert(func_env, builder, array_ref, VMGcKind::ArrayRef);
1057
1058 let array_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();
1059 let elem_addr = array_elem_addr(func_env, builder, array_type_index, array_ref, index);
1060
1061 let array_ty = func_env.types.unwrap_array(array_type_index)?;
1062 let elem_ty = array_ty.0.element_type;
1063
1064 write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)?;
1065
1066 log::trace!("translate_array_set: finished");
1067 Ok(())
1068 }
1069
translate_ref_test( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, test_ty: WasmRefType, val: ir::Value, val_ty: WasmRefType, ) -> WasmResult<ir::Value>1070 pub fn translate_ref_test(
1071 func_env: &mut FuncEnvironment<'_>,
1072 builder: &mut FunctionBuilder<'_>,
1073 test_ty: WasmRefType,
1074 val: ir::Value,
1075 val_ty: WasmRefType,
1076 ) -> WasmResult<ir::Value> {
1077 log::trace!("translate_ref_test({test_ty:?}, {val:?})");
1078
1079 // First special case: testing for references to bottom types.
1080 if test_ty.heap_type.is_bottom() {
1081 let result = if test_ty.nullable {
1082 // All null references (within the same type hierarchy) match null
1083 // references to the bottom type.
1084 func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?
1085 } else {
1086 // `ref.test` is always false for non-nullable bottom types, as the
1087 // bottom types are uninhabited.
1088 builder.ins().iconst(ir::types::I32, 0)
1089 };
1090 log::trace!("translate_ref_test(..) -> {result:?}");
1091 return Ok(result);
1092 }
1093
1094 // And because `ref.test heap_ty` is only valid on operands whose type is in
1095 // the same type hierarchy as `heap_ty`, if `heap_ty` is its hierarchy's top
1096 // type, we only need to worry about whether we are testing for nullability
1097 // or not.
1098 if test_ty.heap_type.is_top() {
1099 let result = if test_ty.nullable {
1100 builder.ins().iconst(ir::types::I32, 1)
1101 } else {
1102 let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;
1103 let zero = builder.ins().iconst(ir::types::I32, 0);
1104 let one = builder.ins().iconst(ir::types::I32, 1);
1105 builder.ins().select(is_null, zero, one)
1106 };
1107 log::trace!("translate_ref_test(..) -> {result:?}");
1108 return Ok(result);
1109 }
1110
1111 // `i31ref`s are a little interesting because they don't point to GC
1112 // objects; we test the bit pattern of the reference itself.
1113 if test_ty.heap_type == WasmHeapType::I31 {
1114 let i31_mask = builder.ins().iconst(
1115 ir::types::I32,
1116 i64::from(wasmtime_environ::I31_DISCRIMINANT),
1117 );
1118 let is_i31 = builder.ins().band(val, i31_mask);
1119 let result = if test_ty.nullable {
1120 let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;
1121 builder.ins().bor(is_null, is_i31)
1122 } else {
1123 is_i31
1124 };
1125 log::trace!("translate_ref_test(..) -> {result:?}");
1126 return Ok(result);
1127 }
1128
1129 // Otherwise, in the general case, we need to inspect our given object's
1130 // actual type, which also requires null-checking and i31-checking it.
1131
1132 let is_any_hierarchy = test_ty.heap_type.top() == WasmHeapTopType::Any;
1133
1134 let non_null_block = builder.create_block();
1135 let non_null_non_i31_block = builder.create_block();
1136 let continue_block = builder.create_block();
1137
1138 // Current block: check if the reference is null and branch appropriately.
1139 let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;
1140 let result_when_is_null = builder
1141 .ins()
1142 .iconst(ir::types::I32, test_ty.nullable as i64);
1143 builder.ins().brif(
1144 is_null,
1145 continue_block,
1146 &[result_when_is_null.into()],
1147 non_null_block,
1148 &[],
1149 );
1150
1151 // Non-null block: We know the GC ref is non-null, but we need to also check
1152 // for `i31` references that don't point to GC objects.
1153 builder.switch_to_block(non_null_block);
1154 log::trace!("translate_ref_test: non-null ref block");
1155 if is_any_hierarchy {
1156 let i31_mask = builder.ins().iconst(
1157 ir::types::I32,
1158 i64::from(wasmtime_environ::I31_DISCRIMINANT),
1159 );
1160 let is_i31 = builder.ins().band(val, i31_mask);
1161 // If it is an `i31`, then create the result value based on whether we
1162 // want `i31`s to pass the test or not.
1163 let result_when_is_i31 = builder.ins().iconst(
1164 ir::types::I32,
1165 matches!(
1166 test_ty.heap_type,
1167 WasmHeapType::Any | WasmHeapType::Eq | WasmHeapType::I31
1168 ) as i64,
1169 );
1170 builder.ins().brif(
1171 is_i31,
1172 continue_block,
1173 &[result_when_is_i31.into()],
1174 non_null_non_i31_block,
1175 &[],
1176 );
1177 } else {
1178 // If we aren't testing the `any` hierarchy, the reference cannot be an
1179 // `i31ref`. Jump directly to the non-null and non-i31 block; rely on
1180 // branch folding during lowering to clean this up.
1181 builder.ins().jump(non_null_non_i31_block, &[]);
1182 }
1183
1184 // Non-null and non-i31 block: Read the actual `VMGcKind` or
1185 // `VMSharedTypeIndex` out of the object's header and check whether it
1186 // matches the expected type.
1187 builder.switch_to_block(non_null_non_i31_block);
1188 log::trace!("translate_ref_test: non-null and non-i31 ref block");
1189 let check_header_kind = |func_env: &mut FuncEnvironment<'_>,
1190 builder: &mut FunctionBuilder,
1191 val: ir::Value,
1192 expected_kind: VMGcKind|
1193 -> ir::Value {
1194 let kind_addr = func_env.prepare_gc_ref_access(
1195 builder,
1196 val,
1197 BoundsCheck::StaticObjectField {
1198 offset: wasmtime_environ::VM_GC_HEADER_KIND_OFFSET,
1199 access_size: wasmtime_environ::VM_GC_KIND_SIZE,
1200 object_size: wasmtime_environ::VM_GC_HEADER_SIZE,
1201 },
1202 );
1203 let actual_kind = builder.ins().load(
1204 ir::types::I32,
1205 ir::MemFlags::trusted().with_readonly(),
1206 kind_addr,
1207 0,
1208 );
1209 let expected_kind = builder
1210 .ins()
1211 .iconst(ir::types::I32, i64::from(expected_kind.as_u32()));
1212 // Inline version of `VMGcKind::matches`.
1213 let and = builder.ins().band(actual_kind, expected_kind);
1214 let kind_matches = builder
1215 .ins()
1216 .icmp(ir::condcodes::IntCC::Equal, and, expected_kind);
1217 builder.ins().uextend(ir::types::I32, kind_matches)
1218 };
1219 let result = match test_ty.heap_type {
1220 WasmHeapType::Any
1221 | WasmHeapType::None
1222 | WasmHeapType::Extern
1223 | WasmHeapType::NoExtern
1224 | WasmHeapType::Func
1225 | WasmHeapType::NoFunc
1226 | WasmHeapType::Cont
1227 | WasmHeapType::NoCont
1228 | WasmHeapType::Exn
1229 | WasmHeapType::NoExn
1230 | WasmHeapType::I31 => unreachable!("handled top, bottom, and i31 types above"),
1231
1232 // For these abstract but non-top and non-bottom types, we check the
1233 // `VMGcKind` that is in the object's header.
1234 WasmHeapType::Eq => check_header_kind(func_env, builder, val, VMGcKind::EqRef),
1235 WasmHeapType::Struct => check_header_kind(func_env, builder, val, VMGcKind::StructRef),
1236 WasmHeapType::Array => check_header_kind(func_env, builder, val, VMGcKind::ArrayRef),
1237
1238 // For concrete types, we need to do a full subtype check between the
1239 // `VMSharedTypeIndex` in the object's header and the
1240 // `ModuleInternedTypeIndex` we have here.
1241 //
1242 // TODO: This check should ideally be done inline, but we don't have a
1243 // good way to access the `TypeRegistry`'s supertypes arrays from Wasm
1244 // code at the moment.
1245 WasmHeapType::ConcreteArray(ty)
1246 | WasmHeapType::ConcreteStruct(ty)
1247 | WasmHeapType::ConcreteExn(ty) => {
1248 let expected_interned_ty = ty.unwrap_module_type_index();
1249 let expected_shared_ty =
1250 func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);
1251
1252 let ty_addr = func_env.prepare_gc_ref_access(
1253 builder,
1254 val,
1255 BoundsCheck::StaticOffset {
1256 offset: wasmtime_environ::VM_GC_HEADER_TYPE_INDEX_OFFSET,
1257 access_size: func_env.offsets.size_of_vmshared_type_index(),
1258 },
1259 );
1260 let actual_shared_ty = builder.ins().load(
1261 ir::types::I32,
1262 ir::MemFlags::trusted().with_readonly(),
1263 ty_addr,
1264 0,
1265 );
1266
1267 func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)
1268 }
1269
1270 // Same as for concrete arrays and structs except that a `VMFuncRef`
1271 // doesn't begin with a `VMGcHeader` and is a raw pointer rather than GC
1272 // heap index.
1273 WasmHeapType::ConcreteFunc(ty) => {
1274 let expected_interned_ty = ty.unwrap_module_type_index();
1275 let expected_shared_ty =
1276 func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);
1277
1278 let actual_shared_ty = func_env.load_funcref_type_index(
1279 &mut builder.cursor(),
1280 ir::MemFlags::trusted().with_readonly(),
1281 val,
1282 );
1283
1284 func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)
1285 }
1286 WasmHeapType::ConcreteCont(_) => {
1287 // TODO(#10248) GC integration for stack switching
1288 return Err(wasmtime_environ::WasmError::Unsupported(
1289 "Stack switching feature not compatible with GC, yet".to_string(),
1290 ));
1291 }
1292 };
1293 builder.ins().jump(continue_block, &[result.into()]);
1294
1295 // Control flow join point with the result.
1296 builder.switch_to_block(continue_block);
1297 let result = builder.append_block_param(continue_block, ir::types::I32);
1298 log::trace!("translate_ref_test(..) -> {result:?}");
1299
1300 builder.seal_block(non_null_block);
1301 builder.seal_block(non_null_non_i31_block);
1302 builder.seal_block(continue_block);
1303
1304 Ok(result)
1305 }
1306
uextend_i32_to_pointer_type( builder: &mut FunctionBuilder, pointer_type: ir::Type, value: ir::Value, ) -> ir::Value1307 fn uextend_i32_to_pointer_type(
1308 builder: &mut FunctionBuilder,
1309 pointer_type: ir::Type,
1310 value: ir::Value,
1311 ) -> ir::Value {
1312 assert_eq!(builder.func.dfg.value_type(value), ir::types::I32);
1313 match pointer_type {
1314 ir::types::I32 => value,
1315 ir::types::I64 => builder.ins().uextend(ir::types::I64, value),
1316 _ => unreachable!(),
1317 }
1318 }
1319
1320 /// Emit CLIF to compute an array object's total size, given the dynamic length
1321 /// in its initialization.
1322 ///
1323 /// Traps if the size overflows.
1324 #[cfg_attr(
1325 not(any(feature = "gc-drc", feature = "gc-null")),
1326 expect(dead_code, reason = "easier to define")
1327 )]
emit_array_size( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, array_layout: &GcArrayLayout, len: ir::Value, ) -> ir::Value1328 fn emit_array_size(
1329 func_env: &mut FuncEnvironment<'_>,
1330 builder: &mut FunctionBuilder<'_>,
1331 array_layout: &GcArrayLayout,
1332 len: ir::Value,
1333 ) -> ir::Value {
1334 let base_size = builder
1335 .ins()
1336 .iconst(ir::types::I32, i64::from(array_layout.base_size));
1337
1338 // `elems_size = len * elem_size`
1339 //
1340 // Check for multiplication overflow and trap if it occurs, since that
1341 // means Wasm is attempting to allocate an array that is larger than our
1342 // implementation limits. (Note: there is no standard implementation
1343 // limit for array length beyond `u32::MAX`.)
1344 //
1345 // We implement this check by encoding our logically-32-bit operands as
1346 // i64 values, doing a 64-bit multiplication, and then checking the high
1347 // 32 bits of the multiplication's result. If the high 32 bits are not
1348 // all zeros, then the multiplication overflowed.
1349 debug_assert_eq!(builder.func.dfg.value_type(len), ir::types::I32);
1350 let len = builder.ins().uextend(ir::types::I64, len);
1351 let elems_size_64 = builder
1352 .ins()
1353 .imul_imm(len, i64::from(array_layout.elem_size));
1354 let high_bits = builder.ins().ushr_imm(elems_size_64, 32);
1355 func_env.trapnz(builder, high_bits, crate::TRAP_ALLOCATION_TOO_LARGE);
1356 let elems_size = builder.ins().ireduce(ir::types::I32, elems_size_64);
1357
1358 // And if adding the base size and elements size overflows, then the
1359 // allocation is too large.
1360 let size = func_env.uadd_overflow_trap(
1361 builder,
1362 base_size,
1363 elems_size,
1364 crate::TRAP_ALLOCATION_TOO_LARGE,
1365 );
1366
1367 size
1368 }
1369
1370 /// Common helper for struct-field initialization that can be reused across
1371 /// collectors.
1372 #[cfg_attr(
1373 not(any(feature = "gc-drc", feature = "gc-null")),
1374 expect(dead_code, reason = "easier to define")
1375 )]
initialize_struct_fields( func_env: &mut FuncEnvironment<'_>, builder: &mut FunctionBuilder<'_>, struct_ty: ModuleInternedTypeIndex, raw_ptr_to_struct: ir::Value, field_values: &[ir::Value], mut init_field: impl FnMut( &mut FuncEnvironment<'_>, &mut FunctionBuilder<'_>, WasmStorageType, ir::Value, ir::Value, ) -> WasmResult<()>, ) -> WasmResult<()>1376 fn initialize_struct_fields(
1377 func_env: &mut FuncEnvironment<'_>,
1378 builder: &mut FunctionBuilder<'_>,
1379 struct_ty: ModuleInternedTypeIndex,
1380 raw_ptr_to_struct: ir::Value,
1381 field_values: &[ir::Value],
1382 mut init_field: impl FnMut(
1383 &mut FuncEnvironment<'_>,
1384 &mut FunctionBuilder<'_>,
1385 WasmStorageType,
1386 ir::Value,
1387 ir::Value,
1388 ) -> WasmResult<()>,
1389 ) -> WasmResult<()> {
1390 let struct_layout = func_env.struct_or_exn_layout(struct_ty);
1391 let struct_size = struct_layout.size;
1392 let field_offsets: SmallVec<[_; 8]> = struct_layout.fields.iter().map(|f| f.offset).collect();
1393 assert_eq!(field_offsets.len(), field_values.len());
1394
1395 assert!(!func_env.types[struct_ty].composite_type.shared);
1396 let fields = match &func_env.types[struct_ty].composite_type.inner {
1397 WasmCompositeInnerType::Struct(s) => &s.fields,
1398 WasmCompositeInnerType::Exn(e) => &e.fields,
1399 _ => panic!("Not a struct or exception type"),
1400 };
1401
1402 let field_types: SmallVec<[_; 8]> = fields.iter().cloned().collect();
1403 assert_eq!(field_types.len(), field_values.len());
1404
1405 for ((ty, val), offset) in field_types.into_iter().zip(field_values).zip(field_offsets) {
1406 let size_of_access = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&ty.element_type);
1407 assert!(offset + size_of_access <= struct_size);
1408 let field_addr = builder.ins().iadd_imm(raw_ptr_to_struct, i64::from(offset));
1409 init_field(func_env, builder, ty.element_type, field_addr, *val)?;
1410 }
1411
1412 Ok(())
1413 }
1414
1415 impl FuncEnvironment<'_> {
gc_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcLayout1416 fn gc_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcLayout {
1417 // Lazily compute and cache the layout.
1418 if !self.ty_to_gc_layout.contains_key(&type_index) {
1419 let ty = &self.types[type_index].composite_type;
1420 let layout = gc_compiler(self)
1421 .unwrap()
1422 .layouts()
1423 .gc_layout(ty)
1424 .expect("should only call `FuncEnvironment::gc_layout` for GC types");
1425 self.ty_to_gc_layout.insert(type_index, layout);
1426 }
1427
1428 self.ty_to_gc_layout.get(&type_index).unwrap()
1429 }
1430
1431 /// Get the `GcArrayLayout` for the array type at the given `type_index`.
array_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcArrayLayout1432 fn array_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcArrayLayout {
1433 self.gc_layout(type_index).unwrap_array()
1434 }
1435
1436 /// Get the `GcStructLayout` for the struct or exception type at the given `type_index`.
struct_or_exn_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcStructLayout1437 fn struct_or_exn_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcStructLayout {
1438 let result = self.gc_layout(type_index).unwrap_struct();
1439 result
1440 }
1441
1442 /// Get or create the global for our GC heap's base pointer.
get_gc_heap_base_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue1443 fn get_gc_heap_base_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue {
1444 if let Some(base) = self.gc_heap_base {
1445 return base;
1446 }
1447
1448 let store_context_ptr = self.get_vmstore_context_ptr_global(func);
1449 let offset = self.offsets.ptr.vmstore_context_gc_heap_base();
1450
1451 let mut flags = ir::MemFlags::trusted();
1452 if !self
1453 .tunables
1454 .gc_heap_memory_type()
1455 .memory_may_move(self.tunables)
1456 {
1457 flags.set_readonly();
1458 flags.set_can_move();
1459 }
1460
1461 let base = func.create_global_value(ir::GlobalValueData::Load {
1462 base: store_context_ptr,
1463 offset: Offset32::new(offset.into()),
1464 global_type: self.pointer_type(),
1465 flags,
1466 });
1467
1468 self.gc_heap_base = Some(base);
1469 base
1470 }
1471
1472 /// Get the GC heap's base.
1473 #[cfg(any(feature = "gc-null", feature = "gc-drc"))]
get_gc_heap_base(&mut self, builder: &mut FunctionBuilder) -> ir::Value1474 fn get_gc_heap_base(&mut self, builder: &mut FunctionBuilder) -> ir::Value {
1475 let global = self.get_gc_heap_base_global(&mut builder.func);
1476 builder.ins().global_value(self.pointer_type(), global)
1477 }
1478
get_gc_heap_bound_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue1479 fn get_gc_heap_bound_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue {
1480 if let Some(bound) = self.gc_heap_bound {
1481 return bound;
1482 }
1483 let store_context_ptr = self.get_vmstore_context_ptr_global(func);
1484 let offset = self.offsets.ptr.vmstore_context_gc_heap_current_length();
1485 let bound = func.create_global_value(ir::GlobalValueData::Load {
1486 base: store_context_ptr,
1487 offset: Offset32::new(offset.into()),
1488 global_type: self.pointer_type(),
1489 flags: ir::MemFlags::trusted(),
1490 });
1491 self.gc_heap_bound = Some(bound);
1492 bound
1493 }
1494
1495 /// Get the GC heap's bound.
1496 #[cfg(feature = "gc-null")]
get_gc_heap_bound(&mut self, builder: &mut FunctionBuilder) -> ir::Value1497 fn get_gc_heap_bound(&mut self, builder: &mut FunctionBuilder) -> ir::Value {
1498 let global = self.get_gc_heap_bound_global(&mut builder.func);
1499 builder.ins().global_value(self.pointer_type(), global)
1500 }
1501
1502 /// Get or create the `Heap` for our GC heap.
get_gc_heap(&mut self, func: &mut ir::Function) -> Heap1503 fn get_gc_heap(&mut self, func: &mut ir::Function) -> Heap {
1504 if let Some(heap) = self.gc_heap {
1505 return heap;
1506 }
1507
1508 let base = self.get_gc_heap_base_global(func);
1509 let bound = self.get_gc_heap_bound_global(func);
1510 let memory = self.tunables.gc_heap_memory_type();
1511 let heap = self.heaps.push(HeapData {
1512 base,
1513 bound,
1514 memory,
1515 });
1516 self.gc_heap = Some(heap);
1517 heap
1518 }
1519
1520 /// Get the raw pointer of `gc_ref[offset]` bounds checked for an access of
1521 /// `size` bytes.
1522 ///
1523 /// The given `gc_ref` must be a non-null, non-i31 GC reference.
1524 ///
1525 /// If `check` is a `BoundsCheck::Object`, then it is the callers
1526 /// responsibility to ensure that `offset + access_size <= object_size`.
1527 ///
1528 /// Returns a raw pointer to `gc_ref[offset]` -- not a raw pointer to the GC
1529 /// object itself (unless `offset` happens to be `0`). This raw pointer may
1530 /// be used to read or write up to as many bytes as described by `bound`. Do
1531 /// NOT attempt accesses bytes outside of `bound`; that may lead to
1532 /// unchecked out-of-bounds accesses.
1533 ///
1534 /// This method is collector-agnostic.
prepare_gc_ref_access( &mut self, builder: &mut FunctionBuilder, gc_ref: ir::Value, bounds_check: BoundsCheck, ) -> ir::Value1535 fn prepare_gc_ref_access(
1536 &mut self,
1537 builder: &mut FunctionBuilder,
1538 gc_ref: ir::Value,
1539 bounds_check: BoundsCheck,
1540 ) -> ir::Value {
1541 log::trace!("prepare_gc_ref_access({gc_ref:?}, {bounds_check:?})");
1542 assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);
1543
1544 let gc_heap = self.get_gc_heap(&mut builder.func);
1545 let gc_heap = self.heaps[gc_heap].clone();
1546 let result = match crate::bounds_checks::bounds_check_and_compute_addr(
1547 builder,
1548 self,
1549 &gc_heap,
1550 gc_ref,
1551 bounds_check,
1552 crate::TRAP_INTERNAL_ASSERT,
1553 ) {
1554 Reachability::Reachable(v) => v,
1555 Reachability::Unreachable => {
1556 // We are now in unreachable code, but we don't want to plumb
1557 // through a bunch of `Reachability` through all of our callers,
1558 // so just assert we won't reach here and return `null`
1559 let null = builder.ins().iconst(self.pointer_type(), 0);
1560 builder.ins().trapz(null, crate::TRAP_INTERNAL_ASSERT);
1561 null
1562 }
1563 };
1564 log::trace!("prepare_gc_ref_access(..) -> {result:?}");
1565 result
1566 }
1567
1568 /// Emit checks (if necessary) for whether the given `gc_ref` is null or is
1569 /// an `i31ref`.
1570 ///
1571 /// Takes advantage of static information based on `ty` as to whether the GC
1572 /// reference is nullable or can ever be an `i31`.
1573 ///
1574 /// Returns an `ir::Value` that is an `i32` will be non-zero if the GC
1575 /// reference is null or is an `i31ref`; otherwise, it will be zero.
1576 ///
1577 /// This method is collector-agnostic.
1578 #[cfg_attr(
1579 not(feature = "gc-drc"),
1580 expect(dead_code, reason = "easier to define")
1581 )]
gc_ref_is_null_or_i31( &mut self, builder: &mut FunctionBuilder, ty: WasmRefType, gc_ref: ir::Value, ) -> ir::Value1582 fn gc_ref_is_null_or_i31(
1583 &mut self,
1584 builder: &mut FunctionBuilder,
1585 ty: WasmRefType,
1586 gc_ref: ir::Value,
1587 ) -> ir::Value {
1588 assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);
1589 assert!(ty.is_vmgcref_type_and_not_i31());
1590
1591 let might_be_i31 = match ty.heap_type {
1592 // If we are definitely dealing with an i31, we shouldn't be
1593 // emitting dynamic checks for it, and the caller shouldn't call
1594 // this function. Should have been caught by the assertion at the
1595 // start of the function.
1596 WasmHeapType::I31 => unreachable!(),
1597
1598 // Could potentially be an i31.
1599 WasmHeapType::Any | WasmHeapType::Eq => true,
1600
1601 // If it is definitely a struct, array, or uninhabited type, then it
1602 // is definitely not an i31.
1603 WasmHeapType::Array
1604 | WasmHeapType::ConcreteArray(_)
1605 | WasmHeapType::Struct
1606 | WasmHeapType::ConcreteStruct(_)
1607 | WasmHeapType::None => false,
1608
1609 // Despite being a different type hierarchy, this *could* be an
1610 // `i31` if it is the result of
1611 //
1612 // (extern.convert_any (ref.i31 ...))
1613 WasmHeapType::Extern => true,
1614
1615 // Can only ever be `null`.
1616 WasmHeapType::NoExtern => false,
1617
1618 WasmHeapType::Exn | WasmHeapType::ConcreteExn(_) | WasmHeapType::NoExn => false,
1619
1620 // Wrong type hierarchy, and also funcrefs are not GC-managed
1621 // types. Should have been caught by the assertion at the start of
1622 // the function.
1623 WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
1624 unreachable!()
1625 }
1626 WasmHeapType::Cont | WasmHeapType::ConcreteCont(_) | WasmHeapType::NoCont => {
1627 unreachable!()
1628 }
1629 };
1630
1631 match (ty.nullable, might_be_i31) {
1632 // This GC reference statically cannot be null nor an i31. (Let
1633 // Cranelift's optimizer const-propagate this value and erase any
1634 // unnecessary control flow resulting from branching on this value.)
1635 (false, false) => builder.ins().iconst(ir::types::I32, 0),
1636
1637 // This GC reference is always non-null, but might be an i31.
1638 (false, true) => builder.ins().band_imm(gc_ref, i64::from(I31_DISCRIMINANT)),
1639
1640 // This GC reference might be null, but can never be an i31.
1641 (true, false) => builder.ins().icmp_imm(IntCC::Equal, gc_ref, 0),
1642
1643 // Fully general case: this GC reference could be either null or an
1644 // i31.
1645 (true, true) => {
1646 let is_i31 = builder.ins().band_imm(gc_ref, i64::from(I31_DISCRIMINANT));
1647 let is_null = builder.ins().icmp_imm(IntCC::Equal, gc_ref, 0);
1648 let is_null = builder.ins().uextend(ir::types::I32, is_null);
1649 builder.ins().bor(is_i31, is_null)
1650 }
1651 }
1652 }
1653
1654 // Emit code to check whether `a <: b` for two `VMSharedTypeIndex`es.
is_subtype( &mut self, builder: &mut FunctionBuilder<'_>, a: ir::Value, b: ir::Value, ) -> ir::Value1655 pub(crate) fn is_subtype(
1656 &mut self,
1657 builder: &mut FunctionBuilder<'_>,
1658 a: ir::Value,
1659 b: ir::Value,
1660 ) -> ir::Value {
1661 log::trace!("is_subtype({a:?}, {b:?})");
1662
1663 let diff_tys_block = builder.create_block();
1664 let continue_block = builder.create_block();
1665
1666 // Current block: fast path for when `a == b`.
1667 log::trace!("is_subtype: fast path check for exact same types");
1668 let same_ty = builder.ins().icmp(IntCC::Equal, a, b);
1669 let same_ty = builder.ins().uextend(ir::types::I32, same_ty);
1670 builder.ins().brif(
1671 same_ty,
1672 continue_block,
1673 &[same_ty.into()],
1674 diff_tys_block,
1675 &[],
1676 );
1677
1678 // Different types block: fall back to the `is_subtype` libcall.
1679 builder.switch_to_block(diff_tys_block);
1680 log::trace!("is_subtype: slow path to do full `is_subtype` libcall");
1681 let is_subtype = self.builtin_functions.is_subtype(builder.func);
1682 let vmctx = self.vmctx_val(&mut builder.cursor());
1683 let call_inst = builder.ins().call(is_subtype, &[vmctx, a, b]);
1684 let result = builder.func.dfg.first_result(call_inst);
1685 builder.ins().jump(continue_block, &[result.into()]);
1686
1687 // Continue block: join point for the result.
1688 builder.switch_to_block(continue_block);
1689 let result = builder.append_block_param(continue_block, ir::types::I32);
1690 log::trace!("is_subtype(..) -> {result:?}");
1691
1692 builder.seal_block(diff_tys_block);
1693 builder.seal_block(continue_block);
1694
1695 result
1696 }
1697 }
1698