///|
/// How a hidden safepoint describes GC roots already present in its arguments.
#derive(Debug, Eq)
pub(all) enum StackMapArgumentRoots {
Fixed(Int32)
I32ConstantOperand(Int32)
AllocationOperands
}
///|
pub struct HiddenSafepointAbi {
priv symbol : @native.ExternalSymbol
priv argument_roots : StackMapArgumentRoots
priv owner : Int32?
}
///|
pub fn HiddenSafepointAbi::new(
symbol : @native.ExternalSymbol,
argument_roots : StackMapArgumentRoots,
owner? : Int32,
) -> HiddenSafepointAbi {
{ symbol, argument_roots, owner, }
}
///|
pub struct CallerRootScopeAbi {
priv push_symbol : @native.ExternalSymbol
priv pop_symbol : @native.ExternalSymbol
}
///|
pub fn CallerRootScopeAbi::new(
push_symbol : @native.ExternalSymbol,
pop_symbol : @native.ExternalSymbol,
) -> CallerRootScopeAbi {
{ push_symbol, pop_symbol, }
}
///|
/// Optional embedding-owned link protocol for the existing 32-byte stack frame.
/// A non-null active word admits only the current, attached invocation. Other
/// states retain the checked helpers. Root and mask storage remain compiler-owned.
/// The header contains previous/roots/mask pointers at 0/8/16, an i32 count at
/// 24 and a zero reserved word at 28. All pointer fields use the 64-bit native ABI.
pub(all) struct InlineFunctionRootFrameLayout {
execution_offset : Int32
active_offset : Int32
head_offset : Int32
}
///|
pub struct FunctionRootFrameAbi {
priv enter_symbol : @native.ExternalSymbol
priv leave_symbol : @native.ExternalSymbol
priv environment_parameter : Int32
priv preserves_mask : Bool
priv inline_layout : InlineFunctionRootFrameLayout?
}
///|
/// Stack-owned ABI: 32-byte opaque header, root words and precise mask words.
/// `preserves_mask` promises that enter/leave helpers, collectors and reentry
/// never modify compiler-owned mask bytes. Parking must preserve those bytes.
/// Unknown providers keep complete writes. Returns-twice continuations and
/// unknown CFG entries always discard the builder's previous mask knowledge.
pub fn FunctionRootFrameAbi::new(
enter_symbol : @native.ExternalSymbol,
leave_symbol : @native.ExternalSymbol,
environment_parameter~ : Int32 = 0,
preserves_mask~ : Bool = false,
inline_layout~ : InlineFunctionRootFrameLayout? = None,
) -> FunctionRootFrameAbi {
{
enter_symbol,
leave_symbol,
environment_parameter,
preserves_mask,
inline_layout,
}
}
///|
pub struct CallAbiElaboration {
priv scoped_function_frame : FunctionRootFrameAbi?
priv scoped_root_limit : Int32
priv root_scope : CallerRootScopeAbi?
priv function_frame : FunctionRootFrameAbi?
priv hidden_safepoints : Array[HiddenSafepointAbi]
}
///|
/// A positive `scoped_root_limit` opts into balanced call-site use of the same
/// stack-owned frame protocol after producer cost admission. The provider must
/// preserve the complete frame through collection, parking and nonlocal unwind.
/// Zero keeps the existing whole-function lifetime.
pub fn CallAbiElaboration::new(
root_scope? : CallerRootScopeAbi,
function_frame? : FunctionRootFrameAbi,
hidden_safepoints~ : Array[HiddenSafepointAbi] = [],
scoped_root_limit~ : Int32 = 0,
) -> CallAbiElaboration {
{
root_scope,
function_frame,
scoped_function_frame: None,
hidden_safepoints: hidden_safepoints.copy(),
scoped_root_limit,
}
}
///|
fn CallAbiElaboration::hidden_safepoint(
self : CallAbiElaboration,
call : @native.NativeCall,
) -> HiddenSafepointAbi? {
guard call.callee is External(symbol) else { return None }
for hidden in self.hidden_safepoints {
if hidden.symbol == symbol {
return Some(hidden)
}
}
None
}
///|
fn root_scope_push_call(symbol : @native.ExternalSymbol) -> @native.NativeCall {
@native.NativeCall::new(
External(symbol),
@native.Signature::new([Ptr64, Ptr64, I32], []),
Platform,
@native.CallBehavior::new(ReadWrite, true, false, false, false),
)
}
///|
fn root_scope_pop_call(symbol : @native.ExternalSymbol) -> @native.NativeCall {
@native.NativeCall::new(
External(symbol),
@native.Signature::new([Ptr64], []),
Platform,
@native.CallBehavior::new(ReadWrite, true, false, false, false),
)
}
///|
#derive(Debug, Eq)
pub suberror DirectLoweringError {
InvalidCallAbi(message~ : String)
}
///|
pub impl Show for DirectLoweringError with fn output(self, logger) {
logger.write_string(Repr(self).to_string())
}
///|
/// Use the existing stack-owned frame only around collecting calls for a
/// producer-admitted sparse live set. Larger or unknown sets keep the normal
/// definition-once frame. Runtime unwinding and parking use the same protocol.
pub fn CallAbiElaboration::for_root_capacity(
self : CallAbiElaboration,
capacity : Int32,
) -> CallAbiElaboration {
if capacity > 0 &&
capacity <= self.scoped_root_limit &&
self.function_frame is Some(frame) {
{ ..self, function_frame: None, scoped_function_frame: Some(frame), }
} else {
self
}
}
///|
/// Whether definitions need persistent shadow slots for this function.
pub fn CallAbiElaboration::has_function_frame(
self : CallAbiElaboration,
) -> Bool {
self.function_frame is Some(_)
}