Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3995,6 +3995,14 @@ impl Bindgen for FunctionBindgen<'_, '_> {
self.load("uint8_t *", *offset, operands, results)
}
Instruction::LengthLoad { offset } => self.load("size_t", *offset, operands, results),
Instruction::LiftNamedFromMemory { .. } => unreachable!(
"LiftNamedFromMemory is only emitted by generators that implement \
Bindgen::lift_helper_name, which this generator does not"
),
Instruction::LowerNamedToMemory { .. } => unreachable!(
"LowerNamedToMemory is only emitted by generators that implement \
Bindgen::lower_helper_name, which this generator does not"
),
Instruction::I32Store { offset } => self.store("int32_t", *offset, operands),
Instruction::I64Store { offset } => self.store("int64_t", *offset, operands),
Instruction::F32Store { offset } => self.store("float", *offset, operands),
Expand Down
158 changes: 155 additions & 3 deletions crates/core/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ def_instruction! {
/// Like `I32Load` or `I64Load`, but for loading array length values.
LengthLoad { offset: ArchitectureSize } : [1] => [1],

/// Pops a base pointer from the stack, calls a pre-generated, shared
/// per-type lift helper function to read and lift the named aggregate
/// type `ty` stored at the constant `offset` from that pointer, and
/// pushes the lifted value.
///
/// This is used to "outline" the canonical-ABI lift of large named
/// record/variant types into shared helper functions instead of
/// inlining the full recursive lift at every use site. Keeping each
/// generated function small avoids the super-linear native-compile cost
/// of a single huge function.
LiftNamedFromMemory { ty: TypeId, offset: ArchitectureSize } : [1] => [1],

/// Pops a value to lower and then a base pointer from the stack, calls a
/// pre-generated, shared per-type lower helper function to write the
/// named aggregate type `ty` at the constant `offset` from that
/// pointer, producing no result.
///
/// This is the lower-side counterpart of `LiftNamedFromMemory`: it
/// outlines the canonical-ABI "write to memory" of large named
/// record/variant types into shared helper functions instead of
/// inlining the full recursive lower at every use site. The value
/// operand is `operands[0]` and the base pointer is `operands[1]`.
LowerNamedToMemory { ty: TypeId, offset: ArchitectureSize } : [2] => [0],

/// Pops a pointer from the stack and then an `i32` value.
/// Stores the value in little-endian at the pointer specified plus the
/// constant `offset`.
Expand Down Expand Up @@ -791,6 +815,32 @@ pub trait Bindgen {
/// "canonical" form for lists. This dictates whether the `ListCanonLower`
/// and `ListCanonLift` instructions are used or not.
fn is_list_canonical(&self, resolve: &Resolve, element: &Type) -> bool;

/// Returns the name of a pre-generated, shared lift helper function for the
/// named aggregate type `id`, if one exists.
///
/// When this returns `Some`, the canonical-ABI lift of that type (in
/// `read_from_memory`) is "outlined" into a call to the named helper
/// (emitted as `Instruction::LiftNamedFromMemory`) instead of being inlined
/// recursively. Generators that do not implement helper outlining should
/// return `None` (the default).
fn lift_helper_name(&self, resolve: &Resolve, id: TypeId) -> Option<String> {
let _ = (resolve, id);
None
}

/// Returns the name of a pre-generated, shared lower helper function for
/// the named aggregate type `id`, if one exists.
///
/// When this returns `Some`, the canonical-ABI lower of that type (in
/// `write_to_memory`) is "outlined" into a call to the named helper
/// (emitted as `Instruction::LowerNamedToMemory`) instead of being inlined
/// recursively. Generators that do not implement helper outlining should
/// return `None` (the default).
fn lower_helper_name(&self, resolve: &Resolve, id: TypeId) -> Option<String> {
let _ = (resolve, id);
None
}
}

/// Generates an abstract sequence of instructions which represents this
Expand Down Expand Up @@ -860,6 +910,67 @@ pub fn lift_from_memory<B: Bindgen>(
generator.stack.pop().unwrap()
}

/// Like [`lift_from_memory`], but used to generate the *body* of an outlined
/// lift helper for the named type `skip_outline_root`.
///
/// The root type itself is lifted inline (one level deep) while nested named
/// aggregate types are outlined into calls to their own shared helpers (see
/// [`Bindgen::lift_helper_name`] and [`Instruction::LiftNamedFromMemory`]).
///
/// `skip_outline_root` must be the id of the named aggregate (record/variant)
/// being lifted, and `ty` must be exactly `Type::Id(skip_outline_root)` (not an
/// alias to it) so that the single-shot inline of the root in `read_from_memory`
/// lands on the intended type.
pub fn lift_from_memory_root<B: Bindgen>(
resolve: &Resolve,
bindgen: &mut B,
address: B::Operand,
ty: &Type,
skip_outline_root: TypeId,
) -> B::Operand {
assert!(
matches!(ty, Type::Id(id) if *id == skip_outline_root),
"lift_from_memory_root requires `ty` to be `Type::Id(skip_outline_root)`, \
not an alias or other type"
);
let mut generator = Generator::new(resolve, bindgen);
generator.lift_outline_root = Some(skip_outline_root);
generator.read_from_memory(ty, address, Default::default());
generator.stack.pop().unwrap()
}

/// Like [`lower_to_memory`], but used to generate the *body* of an outlined
/// lower helper for the named type `skip_outline_root`.
///
/// The root type itself is lowered inline (one level deep) while nested named
/// aggregate types are outlined into calls to their own shared helpers (see
/// [`Bindgen::lower_helper_name`] and [`Instruction::LowerNamedToMemory`]).
///
/// `skip_outline_root` must be the id of the named aggregate (record/variant)
/// being lowered, and `ty` must be exactly `Type::Id(skip_outline_root)` (not
/// an alias to it) so that the single-shot inline of the root in
/// `write_to_memory` lands on the intended type.
pub fn lower_to_memory_root<B: Bindgen>(
resolve: &Resolve,
bindgen: &mut B,
address: B::Operand,
value: B::Operand,
ty: &Type,
skip_outline_root: TypeId,
) {
assert!(
matches!(ty, Type::Id(id) if *id == skip_outline_root),
"lower_to_memory_root requires `ty` to be `Type::Id(skip_outline_root)`, \
not an alias or other type"
);
let mut generator = Generator::new(resolve, bindgen);
generator.realloc = Some(Realloc::Export("cabi_realloc"));
generator.lower_outline_root = Some(skip_outline_root);
generator.stack.push(value);
generator.write_to_memory(ty, address, Default::default());
debug_assert!(generator.stack.is_empty());
}

/// Used in a similar manner as the `Interface::call` function except is
/// used to generate the `post-return` callback for `func`.
///
Expand Down Expand Up @@ -1001,6 +1112,16 @@ struct Generator<'a, B: Bindgen> {
stack: Vec<B::Operand>,
return_pointer: Option<B::Operand>,
realloc: Option<Realloc>,
/// When generating the body of an outlined lift helper for a named type,
/// this holds that type's id so its *own* top-level lift is inlined (one
/// level) while nested named aggregates are still outlined into their own
/// helpers. `None` everywhere else (so all eligible named types outline).
lift_outline_root: Option<TypeId>,
/// Like `lift_outline_root`, but for the lower side: when generating the
/// body of an outlined lower helper for a named type, this holds that
/// type's id so its own top-level lower is inlined (one level) while
/// nested named aggregates are still outlined. `None` everywhere else.
lower_outline_root: Option<TypeId>,
}

const MAX_FLAT_PARAMS: usize = 16;
Expand All @@ -1016,6 +1137,8 @@ impl<'a, B: Bindgen> Generator<'a, B> {
stack: Vec::new(),
return_pointer: None,
realloc: None,
lift_outline_root: None,
lower_outline_root: None,
}
}

Expand Down Expand Up @@ -1969,7 +2092,21 @@ impl<'a, B: Bindgen> Generator<'a, B> {
Type::String => self.write_list_to_memory(ty, addr, offset),
Type::ErrorContext => self.lower_and_emit(ty, addr, &I32Store { offset }),

Type::Id(id) => match &self.resolve.types[id].kind {
Type::Id(id) => {
// Outline the lower of large named aggregate types into shared
// helper functions instead of inlining the full recursive
// lower here. `lower_outline_root` is `Some(id)` only while
// generating that type's own helper body, in which case its
// top level is inlined (one level) and nested types still
// outline.
let is_outline_root = self.lower_outline_root == Some(id);
self.lower_outline_root = None;
if !is_outline_root && self.bindgen.lower_helper_name(self.resolve, id).is_some() {
self.stack.push(addr);
self.emit(&Instruction::LowerNamedToMemory { ty: id, offset });
return;
}
match &self.resolve.types[id].kind {
TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset),
TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset),
// Maps have the same linear memory layout as list<tuple<K, V>>.
Expand Down Expand Up @@ -2083,6 +2220,7 @@ impl<'a, B: Bindgen> Generator<'a, B> {
id,
});
}
}
},
}
}
Expand Down Expand Up @@ -2177,7 +2315,20 @@ impl<'a, B: Bindgen> Generator<'a, B> {
Type::String => self.read_list_from_memory(ty, addr, offset),
Type::ErrorContext => self.emit_and_lift(ty, addr, &I32Load { offset }),

Type::Id(id) => match &self.resolve.types[id].kind {
Type::Id(id) => {
// Outline the lift of large named aggregate types into shared
// helper functions instead of inlining the full recursive lift
// here. `lift_outline_root` is `Some(id)` only while generating
// that type's own helper body, in which case its top level is
// inlined (one level) and nested types still outline.
let is_outline_root = self.lift_outline_root == Some(id);
self.lift_outline_root = None;
if !is_outline_root && self.bindgen.lift_helper_name(self.resolve, id).is_some() {
self.stack.push(addr);
self.emit(&Instruction::LiftNamedFromMemory { ty: id, offset });
return;
}
match &self.resolve.types[id].kind {
TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset),

TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset),
Expand Down Expand Up @@ -2284,7 +2435,8 @@ impl<'a, B: Bindgen> Generator<'a, B> {
id,
});
}
},
}
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions crates/cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3520,6 +3520,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> {
abi::Instruction::LengthLoad { offset } => {
self.load("size_t", *offset, operands, results)
}
abi::Instruction::LiftNamedFromMemory { .. } => unreachable!(
"LiftNamedFromMemory is only emitted by generators that implement \
Bindgen::lift_helper_name, which this generator does not"
),
abi::Instruction::LowerNamedToMemory { .. } => unreachable!(
"LowerNamedToMemory is only emitted by generators that implement \
Bindgen::lower_helper_name, which this generator does not"
),
abi::Instruction::PointerStore { offset } => {
let ptr_type = self.r#gen.r#gen.opts.ptr_type();
self.store(ptr_type, *offset, operands)
Expand Down
8 changes: 8 additions & 0 deletions crates/csharp/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ impl Bindgen for FunctionBindgen<'_, '_> {
offset = offset.size_wasm32()
))
}
Instruction::LiftNamedFromMemory { .. } => unreachable!(
"LiftNamedFromMemory is only emitted by generators that implement \
Bindgen::lift_helper_name, which this generator does not"
),
Instruction::LowerNamedToMemory { .. } => unreachable!(
"LowerNamedToMemory is only emitted by generators that implement \
Bindgen::lower_helper_name, which this generator does not"
),
Instruction::PointerLoad { offset } => results.push(format!(
"new global::System.Span<nint>((void*)((byte*){} + {offset}), 1)[0]",
operands[0],
Expand Down
8 changes: 8 additions & 0 deletions crates/go/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,14 @@ return {results}"
Instruction::LengthLoad { offset } => {
load(self, results, &operands[0], offset, "uint32", &|v| v)
}
Instruction::LiftNamedFromMemory { .. } => unreachable!(
"LiftNamedFromMemory is only emitted by generators that implement \
Bindgen::lift_helper_name, which this generator does not"
),
Instruction::LowerNamedToMemory { .. } => unreachable!(
"LowerNamedToMemory is only emitted by generators that implement \
Bindgen::lower_helper_name, which this generator does not"
),
Instruction::PointerLoad { offset } => {
load(self, results, &operands[0], offset, "uint32", &|v| {
format!("uintptr({v})")
Expand Down
Loading