Skip to content

Commit 1d371af

Browse files
authored
Refactor exception objects to share more implementation with structs. (bytecodealliance#11467)
* Refactor exception objects to share more implementation with structs. This PR updates the implementation of Wasm exception objects to share more layout-computation code with structs, except for a single fixed tag slot. The former is simply a nice refactor, and the latter is in preparation for full exception support, which will require accessing an exception's tag without knowing its layout dynamically at runtime. * Review feedback.
1 parent 30296bf commit 1d371af

File tree

17 files changed

+269
-227
lines changed

17 files changed

+269
-227
lines changed

crates/environ/src/compile/module_environ.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,13 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
354354
TypeRef::Tag(ty) => {
355355
let index = TypeIndex::from_u32(ty.func_type_idx);
356356
let signature = self.result.module.types[index];
357-
let tag = Tag { signature };
357+
let exception = self.types.define_exception_type_for_tag(
358+
signature.unwrap_module_type_index(),
359+
);
360+
let tag = Tag {
361+
signature,
362+
exception: EngineOrModuleTypeIndex::Module(exception),
363+
};
358364
self.result.module.num_imported_tags += 1;
359365
EntityType::Tag(tag)
360366
}
@@ -426,7 +432,10 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
426432
let sigindex = entry?.func_type_idx;
427433
let ty = TypeIndex::from_u32(sigindex);
428434
let interned_index = self.result.module.types[ty];
429-
self.result.module.push_tag(interned_index);
435+
let exception = self
436+
.types
437+
.define_exception_type_for_tag(interned_index.unwrap_module_type_index());
438+
self.result.module.push_tag(interned_index, exception);
430439
}
431440
}
432441

crates/environ/src/compile/module_types.rs

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use crate::{
22
EngineOrModuleTypeIndex, EntityRef, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex,
33
ModuleTypes, TypeConvert, TypeIndex, WasmArrayType, WasmCompositeInnerType, WasmCompositeType,
4-
WasmFuncType, WasmHeapType, WasmResult, WasmStructType, WasmSubType, wasm_unsupported,
4+
WasmExnType, WasmFieldType, WasmFuncType, WasmHeapType, WasmResult, WasmStorageType,
5+
WasmStructType, WasmSubType, wasm_unsupported,
6+
};
7+
use std::{
8+
borrow::Cow,
9+
collections::{HashMap, hash_map::Entry},
10+
ops::Index,
511
};
6-
use std::{borrow::Cow, collections::HashMap, ops::Index};
712
use wasmparser::{UnpackedIndex, Validator, ValidatorId};
813

914
/// A type marking the start of a recursion group's definition.
@@ -37,6 +42,12 @@ pub struct ModuleTypesBuilder {
3742
/// trampoline type for a given function type if we've already interned one.
3843
trampoline_types: HashMap<WasmFuncType, ModuleInternedTypeIndex>,
3944

45+
/// An interning map for exception types corresponding to function
46+
/// types used by tags. Tags are nominal, but the underlying
47+
/// Wasmtime types describe only the object layout and so are
48+
/// structural.
49+
exception_types: HashMap<ModuleInternedTypeIndex, ModuleInternedTypeIndex>,
50+
4051
/// A map from already-interned `wasmparser` types to their corresponding
4152
/// Wasmtime type.
4253
wasmparser_to_wasmtime: HashMap<wasmparser::types::CoreTypeId, ModuleInternedTypeIndex>,
@@ -56,6 +67,7 @@ impl ModuleTypesBuilder {
5667
validator_id: validator.id(),
5768
types: ModuleTypes::default(),
5869
trampoline_types: HashMap::default(),
70+
exception_types: HashMap::default(),
5971
wasmparser_to_wasmtime: HashMap::default(),
6072
already_seen: HashMap::default(),
6173
defining_rec_group: None,
@@ -301,6 +313,50 @@ impl ModuleTypesBuilder {
301313
module_interned_index
302314
}
303315

316+
/// Define a new exception type when we see a function type used
317+
/// in a tag.
318+
///
319+
/// The returned `ModuleInternedTypeIndex` gives us a Wasmtime
320+
/// type which corresponds to the exception object layout, but
321+
/// note that these types do not exist in the Wasm spec: at the
322+
/// Wasm level, only function types exist (and tags and exception
323+
/// instructions reference them). For implementation reasons, we
324+
/// need a separate type to describe the exception object layout,
325+
/// and this registers and provides that type.
326+
pub fn define_exception_type_for_tag(
327+
&mut self,
328+
for_func_ty: ModuleInternedTypeIndex,
329+
) -> ModuleInternedTypeIndex {
330+
match self.exception_types.entry(for_func_ty) {
331+
Entry::Occupied(o) => *o.get(),
332+
Entry::Vacant(v) => {
333+
let fields = self.types[for_func_ty]
334+
.unwrap_func()
335+
.params()
336+
.iter()
337+
.map(|valtype| WasmFieldType {
338+
element_type: WasmStorageType::Val(*valtype),
339+
mutable: false,
340+
})
341+
.collect();
342+
let idx = self.types.push(WasmSubType {
343+
is_final: true,
344+
supertype: None,
345+
composite_type: WasmCompositeType {
346+
inner: WasmCompositeInnerType::Exn(WasmExnType {
347+
func_ty: EngineOrModuleTypeIndex::Module(for_func_ty),
348+
fields,
349+
}),
350+
shared: false,
351+
},
352+
});
353+
let next = self.types.next_ty();
354+
self.types.push_rec_group(idx..next);
355+
*v.insert(idx)
356+
}
357+
}
358+
}
359+
304360
/// Returns the result [`ModuleTypes`] of this builder.
305361
pub fn finish(self) -> ModuleTypes {
306362
self.types
@@ -347,10 +403,7 @@ impl ModuleTypesBuilder {
347403
if composite_type.shared {
348404
return Err(wasm_unsupported!("shared structs are not yet implemented"));
349405
}
350-
match &composite_type.inner {
351-
WasmCompositeInnerType::Struct(s) => Ok(s),
352-
_ => unreachable!(),
353-
}
406+
Ok(composite_type.inner.unwrap_struct())
354407
}
355408

356409
/// Get and unwrap a [`WasmArrayType`] for the given array index.
@@ -367,10 +420,45 @@ impl ModuleTypesBuilder {
367420
if composite_type.shared {
368421
return Err(wasm_unsupported!("shared arrays are not yet implemented"));
369422
}
370-
match &composite_type.inner {
371-
WasmCompositeInnerType::Array(a) => Ok(a),
372-
_ => unreachable!(),
423+
Ok(composite_type.inner.unwrap_array())
424+
}
425+
426+
/// Get and unwrap a [`WasmExnType`] for the given exception-type index.
427+
///
428+
/// # Panics
429+
///
430+
/// Panics if the unwrapped type is not an exception type.
431+
///
432+
/// # Errors
433+
///
434+
/// For now, fails with an unsupported error if the type is shared.
435+
pub fn unwrap_exn(&self, interned_ty: ModuleInternedTypeIndex) -> WasmResult<&WasmExnType> {
436+
let composite_type = &self.types[interned_ty].composite_type;
437+
if composite_type.shared {
438+
return Err(wasm_unsupported!(
439+
"shared exceptions are not yet implemented"
440+
));
441+
}
442+
Ok(composite_type.inner.unwrap_exn())
443+
}
444+
445+
/// Get and unwrap a [`WasmFuncType`] for the given function-type index.
446+
///
447+
/// # Panics
448+
///
449+
/// Panics if the unwrapped type is not a function type.
450+
///
451+
/// # Errors
452+
///
453+
/// For now, fails with an unsupported error if the type is shared.
454+
pub fn unwrap_func(&self, interned_ty: ModuleInternedTypeIndex) -> WasmResult<&WasmFuncType> {
455+
let composite_type = &self.types[interned_ty].composite_type;
456+
if composite_type.shared {
457+
return Err(wasm_unsupported!(
458+
"shared functions are not yet implemented"
459+
));
373460
}
461+
Ok(composite_type.inner.unwrap_func())
374462
}
375463
}
376464

crates/environ/src/gc.rs

Lines changed: 22 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -175,30 +175,30 @@ fn common_struct_layout(
175175
size,
176176
align,
177177
fields,
178+
is_exception: false,
178179
}
179180
}
180181

181182
/// Common code to define a GC exception object's layout, given the
182183
/// size and alignment of the collector's GC header and its expected
183184
/// offset of the array length field.
184185
#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
185-
fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcExceptionLayout {
186+
fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcStructLayout {
186187
assert!(header_size >= crate::VM_GC_HEADER_SIZE);
187188
assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
188189

189190
// Compute a struct layout, with extra header size for the
190191
// `(instance_idx, tag_idx)` fields.
191-
let tag_offset = header_size;
192192
assert!(header_align >= 8);
193193
let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();
194194

195195
let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
196196

197-
GcExceptionLayout {
197+
GcStructLayout {
198198
size,
199199
align,
200-
tag_offset,
201200
fields,
201+
is_exception: true,
202202
}
203203
}
204204

@@ -211,6 +211,12 @@ pub trait GcTypeLayouts {
211211
/// element type.
212212
fn array_length_field_offset(&self) -> u32;
213213

214+
/// The offset of an exception object's tag reference.
215+
///
216+
/// This msut be the same for all exception objects in the heap,
217+
/// regardless of their specific signature.
218+
fn exception_tag_offset(&self) -> u32;
219+
214220
/// Get this collector's layout for the given composite type.
215221
///
216222
/// Returns `None` if the type is a function type, as functions are not
@@ -235,7 +241,7 @@ pub trait GcTypeLayouts {
235241
fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;
236242

237243
/// Get this collector's layout for the given exception type.
238-
fn exn_layout(&self, ty: &WasmExnType) -> GcExceptionLayout;
244+
fn exn_layout(&self, ty: &WasmExnType) -> GcStructLayout;
239245
}
240246

241247
/// The layout of a GC-managed object.
@@ -244,11 +250,8 @@ pub enum GcLayout {
244250
/// The layout of a GC-managed array object.
245251
Array(GcArrayLayout),
246252

247-
/// The layout of a GC-managed struct object.
253+
/// The layout of a GC-managed struct or exception object.
248254
Struct(GcStructLayout),
249-
250-
/// The layout of a GC-managed exception object.
251-
Exception(GcExceptionLayout),
252255
}
253256

254257
impl From<GcArrayLayout> for GcLayout {
@@ -263,12 +266,6 @@ impl From<GcStructLayout> for GcLayout {
263266
}
264267
}
265268

266-
impl From<GcExceptionLayout> for GcLayout {
267-
fn from(layout: GcExceptionLayout) -> Self {
268-
Self::Exception(layout)
269-
}
270-
}
271-
272269
impl GcLayout {
273270
/// Get the underlying `GcStructLayout`, or panic.
274271
#[track_caller]
@@ -287,15 +284,6 @@ impl GcLayout {
287284
_ => panic!("GcLayout::unwrap_array on non-array GC layout"),
288285
}
289286
}
290-
291-
/// Get the underlying `GcExceptionLayout`, or panic.
292-
#[track_caller]
293-
pub fn unwrap_exception(&self) -> &GcExceptionLayout {
294-
match self {
295-
Self::Exception(e) => e,
296-
_ => panic!("GcLayout::unwrap_exception on a non-exception GC layout"),
297-
}
298-
}
299287
}
300288

301289
/// The layout of a GC-managed array.
@@ -352,7 +340,7 @@ impl GcArrayLayout {
352340
}
353341
}
354342

355-
/// The layout for a GC-managed struct type.
343+
/// The layout for a GC-managed struct type or exception type.
356344
///
357345
/// This layout is only valid for use with the GC runtime that created it. It is
358346
/// not valid to use one GC runtime's layout with another GC runtime, doing so
@@ -361,6 +349,12 @@ impl GcArrayLayout {
361349
///
362350
/// All offsets are from the start of the object; that is, the size of the GC
363351
/// header (for example) is included in the offset.
352+
///
353+
/// Note that these are reused between structs and exceptions to avoid
354+
/// unnecessary code duplication. In both cases, the objects are
355+
/// tuples of typed fields with a certain size. The only difference in
356+
/// practice is that an exception object also carries a tag reference
357+
/// (at a fixed offset as per `GcTypeLayouts::exception_tag_offset`).
364358
#[derive(Clone, Debug)]
365359
pub struct GcStructLayout {
366360
/// The size (in bytes) of this struct.
@@ -372,6 +366,9 @@ pub struct GcStructLayout {
372366
/// The fields of this struct. The `i`th entry contains information about
373367
/// the `i`th struct field's layout.
374368
pub fields: Vec<GcStructLayoutField>,
369+
370+
/// Whether this is an exception object layout.
371+
pub is_exception: bool,
375372
}
376373

377374
impl GcStructLayout {
@@ -397,41 +394,6 @@ pub struct GcStructLayoutField {
397394
pub is_gc_ref: bool,
398395
}
399396

400-
/// The layout for a GC-managed exception object.
401-
///
402-
/// This layout is only valid for use with the GC runtime that created it. It is
403-
/// not valid to use one GC runtime's layout with another GC runtime, doing so
404-
/// is memory safe but will lead to general incorrectness like panics and wrong
405-
/// results.
406-
///
407-
/// All offsets are from the start of the object; that is, the size of the GC
408-
/// header (for example) is included in the offset.
409-
#[derive(Clone, Debug)]
410-
pub struct GcExceptionLayout {
411-
/// The size (in bytes) of this struct.
412-
pub size: u32,
413-
414-
/// The alignment (in bytes) of this struct.
415-
pub align: u32,
416-
417-
/// The offset of the VMTagImport pointer.
418-
pub tag_offset: u32,
419-
420-
/// The fields of this exception object. The `i`th entry contains
421-
/// information about the `i`th parameter in the associated tag
422-
/// type.
423-
pub fields: Vec<GcStructLayoutField>,
424-
}
425-
426-
impl GcExceptionLayout {
427-
/// Get a `core::alloc::Layout` for an exception object of this type.
428-
pub fn layout(&self) -> Layout {
429-
let size = usize::try_from(self.size).unwrap();
430-
let align = usize::try_from(self.align).unwrap();
431-
Layout::from_size_align(size, align).unwrap()
432-
}
433-
}
434-
435397
/// The kind of an object in a GC heap.
436398
///
437399
/// Note that this type is accessed from Wasm JIT code.

crates/environ/src/gc/drc.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub const HEADER_ALIGN: u32 = 8;
1111
/// The offset of the length field in a `VMDrcArrayHeader`.
1212
pub const ARRAY_LENGTH_OFFSET: u32 = HEADER_SIZE;
1313

14+
/// The offset of the tag fields in an exception header.
15+
pub const EXCEPTION_TAG_OFFSET: u32 = HEADER_SIZE;
16+
1417
/// The bit within a `VMDrcHeader`'s reserved bits that is the mark
1518
/// bit. Collectively, this bit in all the heap's objects' headers implements
1619
/// the precise-stack-roots set.
@@ -29,6 +32,10 @@ impl GcTypeLayouts for DrcTypeLayouts {
2932
ARRAY_LENGTH_OFFSET
3033
}
3134

35+
fn exception_tag_offset(&self) -> u32 {
36+
EXCEPTION_TAG_OFFSET
37+
}
38+
3239
fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout {
3340
common_array_layout(ty, HEADER_SIZE, HEADER_ALIGN, ARRAY_LENGTH_OFFSET)
3441
}
@@ -37,7 +44,7 @@ impl GcTypeLayouts for DrcTypeLayouts {
3744
common_struct_layout(ty, HEADER_SIZE, HEADER_ALIGN)
3845
}
3946

40-
fn exn_layout(&self, ty: &WasmExnType) -> GcExceptionLayout {
47+
fn exn_layout(&self, ty: &WasmExnType) -> GcStructLayout {
4148
common_exn_layout(ty, HEADER_SIZE, HEADER_ALIGN)
4249
}
4350
}

0 commit comments

Comments
 (0)