Skip to content

Commit

Permalink
Implement the struct.* GC instructions
Browse files Browse the repository at this point in the history
This commit implements the `struct.*` instructions for the GC proposal.  These
instructions allow allocating new structs and getting and setting their
fields.

The implemented instructions are:

* `struct.new`
* `struct.new_default`
* `struct.get`
* `struct.get_s`
* `struct.get_u`
* `struct.set`

The `struct.new*` instructions are also allowed in constant expressions, but
support for that is not yet implemented.

Co-Authored-By: Trevor Elliott <[email protected]>
  • Loading branch information
fitzgen and elliottt committed Sep 18, 2024
1 parent c74a7b7 commit 98daafd
Show file tree
Hide file tree
Showing 22 changed files with 1,890 additions and 56 deletions.
86 changes: 78 additions & 8 deletions cranelift/wasm/src/code_translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
mod bounds_checks;

use super::{hash_map, HashMap};
use crate::environ::{FuncEnvironment, GlobalVariable};
use crate::environ::{FuncEnvironment, GlobalVariable, StructFieldsVec};
use crate::state::{ControlStackFrame, ElseData, FuncTranslationState};
use crate::translation_utils::{
block_with_params, blocktype_params_results, f32_translation, f64_translation,
Expand Down Expand Up @@ -2501,6 +2501,82 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
state.push1(val);
}

Operator::StructNew { struct_type_index } => {
let struct_type_index = TypeIndex::from_u32(*struct_type_index);
let arity = environ.struct_fields_len(struct_type_index)?;
let fields: StructFieldsVec = state.peekn(arity).iter().copied().collect();
state.popn(arity);
let struct_ref = environ.translate_struct_new(builder, struct_type_index, fields)?;
state.push1(struct_ref);
}

Operator::StructNewDefault { struct_type_index } => {
let struct_type_index = TypeIndex::from_u32(*struct_type_index);
let struct_ref = environ.translate_struct_new_default(builder, struct_type_index)?;
state.push1(struct_ref);
}

Operator::StructSet {
struct_type_index,
field_index,
} => {
let struct_type_index = TypeIndex::from_u32(*struct_type_index);
let val = state.pop1();
let struct_ref = state.pop1();
environ.translate_struct_set(
builder,
struct_type_index,
*field_index,
struct_ref,
val,
)?;
}

Operator::StructGetS {
struct_type_index,
field_index,
} => {
let struct_type_index = TypeIndex::from_u32(*struct_type_index);
let struct_ref = state.pop1();
let val = environ.translate_struct_get_s(
builder,
struct_type_index,
*field_index,
struct_ref,
)?;
state.push1(val);
}

Operator::StructGetU {
struct_type_index,
field_index,
} => {
let struct_type_index = TypeIndex::from_u32(*struct_type_index);
let struct_ref = state.pop1();
let val = environ.translate_struct_get_u(
builder,
struct_type_index,
*field_index,
struct_ref,
)?;
state.push1(val);
}

Operator::StructGet {
struct_type_index,
field_index,
} => {
let struct_type_index = TypeIndex::from_u32(*struct_type_index);
let struct_ref = state.pop1();
let val = environ.translate_struct_get(
builder,
struct_type_index,
*field_index,
struct_ref,
)?;
state.push1(val);
}

Operator::TryTable { .. } | Operator::ThrowRef => {
return Err(wasm_unsupported!(
"exception operators are not yet implemented"
Expand Down Expand Up @@ -2529,13 +2605,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
| Operator::ArrayFill { .. }
| Operator::ArrayCopy { .. }
| Operator::ArrayInitData { .. }
| Operator::ArrayInitElem { .. }
| Operator::StructNew { .. }
| Operator::StructNewDefault { .. }
| Operator::StructGetS { .. }
| Operator::StructGetU { .. }
| Operator::StructSet { .. }
| Operator::StructGet { .. } => {
| Operator::ArrayInitElem { .. } => {
return Err(wasm_unsupported!("GC operators are not yet implemented"));
}

Expand Down
2 changes: 1 addition & 1 deletion cranelift/wasm/src/environ/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
mod spec;

pub use crate::environ::spec::{
FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment,
FuncEnvironment, GlobalVariable, ModuleEnvironment, StructFieldsVec, TargetEnvironment,
};
59 changes: 59 additions & 0 deletions cranelift/wasm/src/environ/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use cranelift_codegen::ir::{self, InstBuilder, Type};
use cranelift_codegen::isa::{TargetFrontendConfig, TargetIsa};
use cranelift_entity::PrimaryMap;
use cranelift_frontend::FunctionBuilder;
use smallvec::SmallVec;
use std::boxed::Box;
use std::string::ToString;
use wasmparser::{FuncValidator, FunctionBody, Operator, ValidatorResources, WasmFeatures};
Expand Down Expand Up @@ -74,6 +75,9 @@ pub trait TargetEnvironment: TypeConvert {
fn reference_type(&self, ty: WasmHeapType) -> (ir::Type, bool);
}

/// A smallvec that holds the IR values for a struct's fields.
pub type StructFieldsVec = SmallVec<[ir::Value; 4]>;

/// Environment affecting the translation of a single WebAssembly function.
///
/// A `FuncEnvironment` trait object is required to translate a WebAssembly function to Cranelift
Expand Down Expand Up @@ -516,6 +520,61 @@ pub trait FuncEnvironment: TargetEnvironment {
i31ref: ir::Value,
) -> WasmResult<ir::Value>;

/// Get the number of fields in a struct type.
fn struct_fields_len(&mut self, struct_type_index: TypeIndex) -> WasmResult<usize>;

/// Translate a `struct.new` instruction.
fn translate_struct_new(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
fields: StructFieldsVec,
) -> WasmResult<ir::Value>;

/// Translate a `struct.new_default` instruction.
fn translate_struct_new_default(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
) -> WasmResult<ir::Value>;

/// Translate a `struct.set` instruction.
fn translate_struct_set(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
value: ir::Value,
) -> WasmResult<()>;

/// Translate a `struct.get` instruction.
fn translate_struct_get(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
) -> WasmResult<ir::Value>;

/// Translate a `struct.get_s` instruction.
fn translate_struct_get_s(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
) -> WasmResult<ir::Value>;

/// Translate a `struct.get_u` instruction.
fn translate_struct_get_u(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
) -> WasmResult<ir::Value>;

/// Emit code at the beginning of every wasm loop.
///
/// This can be used to insert explicit interrupt or safepoint checking at
Expand Down
4 changes: 3 additions & 1 deletion cranelift/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ mod state;
mod table;
mod translation_utils;

pub use crate::environ::{FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment};
pub use crate::environ::{
FuncEnvironment, GlobalVariable, ModuleEnvironment, StructFieldsVec, TargetEnvironment,
};
pub use crate::func_translator::FuncTranslator;
pub use crate::heap::{Heap, HeapData, HeapStyle};
pub use crate::module_translator::translate_module;
Expand Down
102 changes: 95 additions & 7 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use cranelift_frontend::FunctionBuilder;
use cranelift_frontend::Variable;
use cranelift_wasm::{
EngineOrModuleTypeIndex, FuncEnvironment as _, FuncIndex, FuncTranslationState, GlobalIndex,
GlobalVariable, Heap, HeapData, HeapStyle, IndexType, Memory, MemoryIndex, Table, TableData,
TableIndex, TableSize, TargetEnvironment, TypeIndex, WasmFuncType, WasmHeapTopType,
WasmHeapType, WasmResult,
GlobalVariable, Heap, HeapData, HeapStyle, IndexType, Memory, MemoryIndex, StructFieldsVec,
Table, TableData, TableIndex, TableSize, TargetEnvironment, TypeIndex, WasmCompositeType,
WasmFuncType, WasmHeapTopType, WasmHeapType, WasmResult, WasmValType,
};
use smallvec::SmallVec;
use std::mem;
Expand Down Expand Up @@ -84,11 +84,17 @@ wasmtime_environ::foreach_builtin_function!(declare_function_signatures);
/// The `FuncEnvironment` implementation for use by the `ModuleEnvironment`.
pub struct FuncEnvironment<'module_environment> {
isa: &'module_environment (dyn TargetIsa + 'module_environment),
module: &'module_environment Module,
types: &'module_environment ModuleTypesBuilder,
pub(crate) module: &'module_environment Module,
pub(crate) types: &'module_environment ModuleTypesBuilder,
wasm_func_ty: &'module_environment WasmFuncType,
sig_ref_to_ty: SecondaryMap<ir::SigRef, Option<&'module_environment WasmFuncType>>,

#[cfg(feature = "gc")]
pub(crate) ty_to_struct_layout: std::collections::HashMap<
wasmtime_environ::ModuleInternedTypeIndex,
wasmtime_environ::GcStructLayout,
>,

#[cfg(feature = "wmemcheck")]
translation: &'module_environment ModuleTranslation<'module_environment>,

Expand Down Expand Up @@ -175,6 +181,9 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
wasm_func_ty,
sig_ref_to_ty: SecondaryMap::default(),

#[cfg(feature = "gc")]
ty_to_struct_layout: std::collections::HashMap::new(),

heaps: PrimaryMap::default(),
tables: SecondaryMap::default(),
vmctx: None,
Expand Down Expand Up @@ -1839,6 +1848,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
builder: &mut FunctionBuilder,
i31ref: ir::Value,
) -> WasmResult<ir::Value> {
// TODO: If we knew we have a `(ref i31)` here, instead of maybe a `(ref
// null i31)`, we could omit the `trapz`. But plumbing that type info
// from `wasmparser` and through to here is a bit funky.
self.trapz(builder, i31ref, ir::TrapCode::NullI31Ref);
Ok(builder.ins().sshr_imm(i31ref, 1))
}
Expand All @@ -1848,10 +1860,86 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
builder: &mut FunctionBuilder,
i31ref: ir::Value,
) -> WasmResult<ir::Value> {
// TODO: If we knew we have a `(ref i31)` here, instead of maybe a `(ref
// null i31)`, we could omit the `trapz`. But plumbing that type info
// from `wasmparser` and through to here is a bit funky.
self.trapz(builder, i31ref, ir::TrapCode::NullI31Ref);
Ok(builder.ins().ushr_imm(i31ref, 1))
}

fn struct_fields_len(&mut self, struct_type_index: TypeIndex) -> WasmResult<usize> {
let ty = self.module.types[struct_type_index];
match &self.types[ty].composite_type {
WasmCompositeType::Struct(s) => Ok(s.fields.len()),
_ => unreachable!(),
}
}

fn translate_struct_new(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
fields: StructFieldsVec,
) -> WasmResult<ir::Value> {
gc::translate_struct_new(self, builder, struct_type_index, &fields)
}

fn translate_struct_new_default(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
) -> WasmResult<ir::Value> {
gc::translate_struct_new_default(self, builder, struct_type_index)
}

fn translate_struct_get(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
) -> WasmResult<ir::Value> {
gc::translate_struct_get(self, builder, struct_type_index, field_index, struct_ref)
}

fn translate_struct_get_s(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
) -> WasmResult<ir::Value> {
gc::translate_struct_get_s(self, builder, struct_type_index, field_index, struct_ref)
}

fn translate_struct_get_u(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
) -> WasmResult<ir::Value> {
gc::translate_struct_get_u(self, builder, struct_type_index, field_index, struct_ref)
}

fn translate_struct_set(
&mut self,
builder: &mut FunctionBuilder,
struct_type_index: TypeIndex,
field_index: u32,
struct_ref: ir::Value,
value: ir::Value,
) -> WasmResult<()> {
gc::translate_struct_set(
self,
builder,
struct_type_index,
field_index,
struct_ref,
value,
)
}

fn translate_ref_null(
&mut self,
mut pos: cranelift_codegen::cursor::FuncCursor,
Expand Down Expand Up @@ -1898,7 +1986,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
ty.is_vmgcref_type(),
"We only use GlobalVariable::Custom for VMGcRef types"
);
let cranelift_wasm::WasmValType::Ref(ty) = ty else {
let WasmValType::Ref(ty) = ty else {
unreachable!()
};

Expand Down Expand Up @@ -1926,7 +2014,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
ty.is_vmgcref_type(),
"We only use GlobalVariable::Custom for VMGcRef types"
);
let cranelift_wasm::WasmValType::Ref(ty) = ty else {
let WasmValType::Ref(ty) = ty else {
unreachable!()
};

Expand Down
Loading

0 comments on commit 98daafd

Please sign in to comment.