Skip to content

Commit 4cf464a

Browse files
committed
feat(jit): add tail side-entry link ABI
1 parent c40e4b1 commit 4cf464a

2 files changed

Lines changed: 332 additions & 3 deletions

File tree

src/vm/jit/native/lower.rs

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ use crate::vm::native::{
3737
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
3838
use cranelift_codegen::ir::immediates::Ieee64;
3939
use cranelift_codegen::ir::{
40-
Block, BlockArg, InstBuilder, MemFlags, StackSlot, StackSlotData, StackSlotKind, types,
40+
AbiParam, Block, BlockArg, InstBuilder, MemFlags, Signature, StackSlot, StackSlotData,
41+
StackSlotKind, types,
4142
};
42-
use cranelift_codegen::isa::OwnedTargetIsa;
43+
use cranelift_codegen::isa::{CallConv, OwnedTargetIsa};
4344
use cranelift_codegen::settings::{self, Configurable};
4445
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
4546
use cranelift_jit::{JITBuilder, JITModule};
@@ -50,6 +51,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
5051

5152
static CRANELIFT_TRACE_ID: AtomicU64 = AtomicU64::new(1);
5253
static CRANELIFT_JIT_ISA: OnceLock<Result<OwnedTargetIsa, String>> = OnceLock::new();
54+
static CRANELIFT_TAIL_ISA: OnceLock<Result<OwnedTargetIsa, String>> = OnceLock::new();
5355

5456
type TaggedConstants = (Box<[Value]>, HashMap<SsaValueId, usize>);
5557

@@ -78,6 +80,148 @@ impl TraceKeepAlive {
7880
}
7981
}
8082

83+
pub(crate) struct CompiledTailFunction {
84+
entry: *const u8,
85+
_keepalive: TraceKeepAlive,
86+
code: Vec<u8>,
87+
}
88+
89+
impl CompiledTailFunction {
90+
pub(crate) fn entry(&self) -> *const u8 {
91+
self.entry
92+
}
93+
94+
pub(crate) fn code_len(&self) -> usize {
95+
self.code.len()
96+
}
97+
}
98+
99+
fn tail_entry_signature(pointer_type: cranelift_codegen::ir::Type) -> Signature {
100+
let mut signature = Signature::new(CallConv::Tail);
101+
signature.params.push(AbiParam::new(pointer_type));
102+
signature.returns.push(AbiParam::new(types::I32));
103+
signature
104+
}
105+
106+
fn compile_standalone_native_function(
107+
prefix: &str,
108+
signature: impl FnOnce(cranelift_codegen::ir::Type, CallConv) -> Signature,
109+
lower: impl FnOnce(&mut FunctionBuilder<'_>, cranelift_codegen::ir::Type, CallConv) -> VmResult<()>,
110+
) -> VmResult<CompiledTailFunction> {
111+
let isa = native_tail_isa()?;
112+
let jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
113+
let mut module = JITModule::new(jit_builder);
114+
let pointer_type = module.target_config().pointer_type();
115+
let default_call_conv = module.target_config().default_call_conv;
116+
let mut ctx = module.make_context();
117+
ctx.func.signature = signature(pointer_type, default_call_conv);
118+
let function_id = CRANELIFT_TRACE_ID.fetch_add(1, Ordering::Relaxed);
119+
let function_name = format!("{prefix}_{function_id}");
120+
let func_id = module
121+
.declare_function(&function_name, Linkage::Local, &ctx.func.signature)
122+
.map_err(|err| VmError::JitNative(format!("declare {prefix} failed: {err}")))?;
123+
{
124+
let mut fb_ctx = FunctionBuilderContext::new();
125+
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fb_ctx);
126+
lower(&mut builder, pointer_type, default_call_conv)?;
127+
builder.seal_all_blocks();
128+
builder.finalize();
129+
}
130+
module
131+
.define_function(func_id, &mut ctx)
132+
.map_err(|err| VmError::JitNative(format!("define {prefix} failed: {err}")))?;
133+
let code_len = ctx
134+
.compiled_code()
135+
.ok_or_else(|| VmError::JitNative(format!("{prefix} produced no machine code")))?
136+
.code_buffer()
137+
.len();
138+
module.clear_context(&mut ctx);
139+
module
140+
.finalize_definitions()
141+
.map_err(|err| VmError::JitNative(format!("finalize {prefix} failed: {err}")))?;
142+
let module_entry = module.get_finalized_function(func_id);
143+
let code = unsafe { std::slice::from_raw_parts(module_entry, code_len).to_vec() };
144+
let keepalive = TraceKeepAlive::from_code(&code, Box::new([]))?;
145+
let entry = keepalive.entry();
146+
Ok(CompiledTailFunction {
147+
entry,
148+
_keepalive: keepalive,
149+
code,
150+
})
151+
}
152+
153+
pub(crate) fn compile_tail_status_body(status: i32) -> VmResult<CompiledTailFunction> {
154+
compile_standalone_native_function(
155+
"pd_vm_tail_status",
156+
|pointer_type, _| tail_entry_signature(pointer_type),
157+
move |builder, _, _| {
158+
let entry = builder.create_block();
159+
builder.append_block_params_for_function_params(entry);
160+
builder.switch_to_block(entry);
161+
let status = builder.ins().iconst(types::I32, i64::from(status));
162+
builder.ins().return_(&[status]);
163+
Ok(())
164+
},
165+
)
166+
}
167+
168+
pub(crate) fn compile_tail_side_link_body(
169+
slot_address: usize,
170+
deopt_status: i32,
171+
) -> VmResult<CompiledTailFunction> {
172+
compile_standalone_native_function(
173+
"pd_vm_tail_side_link",
174+
|pointer_type, _| tail_entry_signature(pointer_type),
175+
move |builder, pointer_type, _| {
176+
let entry = builder.create_block();
177+
let deopt = builder.create_block();
178+
let linked = builder.create_block();
179+
builder.append_block_params_for_function_params(entry);
180+
builder.switch_to_block(entry);
181+
let vm_ptr = builder.block_params(entry)[0];
182+
let slot_address = iconst_ptr_from_addr(builder, pointer_type, slot_address)?;
183+
let target = builder
184+
.ins()
185+
.load(pointer_type, MemFlags::new(), slot_address, 0);
186+
let is_null = builder.ins().icmp_imm(IntCC::Equal, target, 0);
187+
builder.ins().brif(is_null, deopt, &[], linked, &[]);
188+
189+
builder.switch_to_block(deopt);
190+
let status = builder.ins().iconst(types::I32, i64::from(deopt_status));
191+
builder.ins().return_(&[status]);
192+
193+
builder.switch_to_block(linked);
194+
let signature = builder.import_signature(tail_entry_signature(pointer_type));
195+
builder
196+
.ins()
197+
.return_call_indirect(signature, target, &[vm_ptr]);
198+
Ok(())
199+
},
200+
)
201+
}
202+
203+
pub(crate) fn compile_system_tail_wrapper(root_entry: *const u8) -> VmResult<CompiledTailFunction> {
204+
let root_entry = root_entry as usize;
205+
compile_standalone_native_function(
206+
"pd_vm_tail_wrapper",
207+
entry_signature,
208+
move |builder, pointer_type, _| {
209+
let entry = builder.create_block();
210+
builder.append_block_params_for_function_params(entry);
211+
builder.switch_to_block(entry);
212+
let vm_ptr = builder.block_params(entry)[0];
213+
let root_entry = iconst_ptr_from_addr(builder, pointer_type, root_entry)?;
214+
let signature = builder.import_signature(tail_entry_signature(pointer_type));
215+
let call = builder
216+
.ins()
217+
.call_indirect(signature, root_entry, &[vm_ptr]);
218+
let status = builder.inst_results(call)[0];
219+
builder.ins().return_(&[status]);
220+
Ok(())
221+
},
222+
)
223+
}
224+
81225
fn try_compile_ssa_trace(
82226
trace: &JitTrace,
83227
ssa: &SsaTrace,
@@ -4950,6 +5094,24 @@ pub(crate) fn compile_trace(
49505094
})
49515095
}
49525096

5097+
fn native_tail_isa() -> VmResult<OwnedTargetIsa> {
5098+
let cached = CRANELIFT_TAIL_ISA.get_or_init(|| {
5099+
let mut flag_builder = settings::builder();
5100+
flag_builder
5101+
.set("opt_level", "speed")
5102+
.map_err(|err| format!("failed to set cranelift opt_level: {err}"))?;
5103+
flag_builder
5104+
.set("preserve_frame_pointers", "true")
5105+
.map_err(|err| format!("failed to preserve tail-call frame pointers: {err}"))?;
5106+
let isa_builder = cranelift_native::builder()
5107+
.map_err(|err| format!("failed to build native tail ISA: {err}"))?;
5108+
isa_builder
5109+
.finish(settings::Flags::new(flag_builder))
5110+
.map_err(|err| format!("failed to finalize cranelift tail ISA: {err}"))
5111+
});
5112+
cached.clone().map_err(VmError::JitNative)
5113+
}
5114+
49535115
fn native_isa(profile: NativeCompileProfile) -> VmResult<OwnedTargetIsa> {
49545116
let cached = match profile {
49555117
NativeCompileProfile::Jit => &CRANELIFT_JIT_ISA,

src/vm/jit/native/mod.rs

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#[cfg(not(feature = "cranelift-jit"))]
33
use super::super::super::VmError;
44
use super::super::super::VmResult;
5+
use super::ir::{SsaMaterialization, SsaValueRepr};
6+
use crate::ValueType;
7+
use std::sync::atomic::{AtomicPtr, Ordering};
58

69
pub(crate) use crate::vm::native::{
710
NativeInterruptSettings, STATUS_CONTINUE, STATUS_ERROR, STATUS_HALTED, STATUS_LINKED_CONTINUE,
@@ -30,6 +33,88 @@ impl TraceLoweringKind {
3033
}
3134
}
3235

36+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37+
pub(crate) enum SideEntryOwnership {
38+
Borrowed,
39+
Owned,
40+
}
41+
42+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43+
pub(crate) enum InheritedStateAbiClass {
44+
ScalarInt,
45+
ScalarFloat,
46+
ScalarBool,
47+
HeapPointer {
48+
tag: ValueType,
49+
ownership: SideEntryOwnership,
50+
},
51+
Tagged(SideEntryOwnership),
52+
}
53+
54+
pub(crate) fn classify_side_entry_repr(
55+
repr: SsaValueRepr,
56+
ownership: SideEntryOwnership,
57+
) -> InheritedStateAbiClass {
58+
match repr {
59+
SsaValueRepr::I64 => InheritedStateAbiClass::ScalarInt,
60+
SsaValueRepr::F64 => InheritedStateAbiClass::ScalarFloat,
61+
SsaValueRepr::Bool => InheritedStateAbiClass::ScalarBool,
62+
SsaValueRepr::HeapPtr(tag) => InheritedStateAbiClass::HeapPointer { tag, ownership },
63+
SsaValueRepr::Tagged => InheritedStateAbiClass::Tagged(ownership),
64+
}
65+
}
66+
67+
pub(crate) fn classify_side_entry_materialization(
68+
materialization: &SsaMaterialization,
69+
) -> InheritedStateAbiClass {
70+
match materialization {
71+
SsaMaterialization::Value(_) => {
72+
InheritedStateAbiClass::Tagged(SideEntryOwnership::Borrowed)
73+
}
74+
SsaMaterialization::BoxInt(_) => InheritedStateAbiClass::ScalarInt,
75+
SsaMaterialization::BoxFloat(_) => InheritedStateAbiClass::ScalarFloat,
76+
SsaMaterialization::BoxBool(_) => InheritedStateAbiClass::ScalarBool,
77+
SsaMaterialization::BoxHeapPtr { tag, .. } => InheritedStateAbiClass::HeapPointer {
78+
tag: *tag,
79+
ownership: SideEntryOwnership::Owned,
80+
},
81+
}
82+
}
83+
84+
pub(crate) struct NativeSideLinkSlot {
85+
entry: AtomicPtr<u8>,
86+
}
87+
88+
impl NativeSideLinkSlot {
89+
pub(crate) const fn new() -> Self {
90+
Self {
91+
entry: AtomicPtr::new(std::ptr::null_mut()),
92+
}
93+
}
94+
95+
pub(crate) fn target(&self) -> *mut u8 {
96+
self.entry.load(Ordering::Acquire)
97+
}
98+
99+
pub(crate) fn publish(&self, entry: *const u8) {
100+
self.entry.store(entry.cast_mut(), Ordering::Release);
101+
}
102+
103+
pub(crate) fn clear(&self) {
104+
self.entry.store(std::ptr::null_mut(), Ordering::Release);
105+
}
106+
107+
pub(crate) fn address(&self) -> *mut *mut u8 {
108+
self.entry.as_ptr()
109+
}
110+
}
111+
112+
impl Default for NativeSideLinkSlot {
113+
fn default() -> Self {
114+
Self::new()
115+
}
116+
}
117+
33118
#[cfg(feature = "cranelift-jit")]
34119
pub(crate) use lower::{CompiledTrace, TraceKeepAlive};
35120

@@ -102,7 +187,89 @@ pub(super) fn compile_native_region(
102187

103188
#[cfg(test)]
104189
mod tests {
105-
use super::selected_codegen_backend;
190+
use super::lower::{
191+
compile_system_tail_wrapper, compile_tail_side_link_body, compile_tail_status_body,
192+
};
193+
use super::{
194+
InheritedStateAbiClass, NativeSideLinkSlot, SideEntryOwnership,
195+
classify_side_entry_materialization, classify_side_entry_repr, selected_codegen_backend,
196+
};
197+
use crate::ValueType;
198+
use crate::vm::jit::ir::{SsaMaterialization, SsaValueId, SsaValueRepr};
199+
200+
#[test]
201+
fn side_entry_abi_classifies_scalar_pointer_tagged_borrowed_and_owned_values() {
202+
let value = SsaValueId::new(7);
203+
assert_eq!(
204+
classify_side_entry_materialization(&SsaMaterialization::BoxInt(value)),
205+
InheritedStateAbiClass::ScalarInt
206+
);
207+
assert_eq!(
208+
classify_side_entry_materialization(&SsaMaterialization::BoxFloat(value)),
209+
InheritedStateAbiClass::ScalarFloat
210+
);
211+
assert_eq!(
212+
classify_side_entry_materialization(&SsaMaterialization::BoxBool(value)),
213+
InheritedStateAbiClass::ScalarBool
214+
);
215+
assert_eq!(
216+
classify_side_entry_materialization(&SsaMaterialization::Value(value)),
217+
InheritedStateAbiClass::Tagged(SideEntryOwnership::Borrowed)
218+
);
219+
assert_eq!(
220+
classify_side_entry_materialization(&SsaMaterialization::BoxHeapPtr {
221+
value,
222+
tag: ValueType::String,
223+
}),
224+
InheritedStateAbiClass::HeapPointer {
225+
tag: ValueType::String,
226+
ownership: SideEntryOwnership::Owned,
227+
}
228+
);
229+
assert_eq!(
230+
classify_side_entry_repr(
231+
SsaValueRepr::HeapPtr(ValueType::Array),
232+
SideEntryOwnership::Borrowed,
233+
),
234+
InheritedStateAbiClass::HeapPointer {
235+
tag: ValueType::Array,
236+
ownership: SideEntryOwnership::Borrowed,
237+
}
238+
);
239+
}
240+
241+
#[cfg(feature = "cranelift-jit")]
242+
#[test]
243+
fn trace_jit_side_link_slot_switches_between_deopt_and_child() {
244+
if selected_codegen_backend() != "native" {
245+
return;
246+
}
247+
const DEOPT_STATUS: i32 = 17;
248+
const CHILD_STATUS: i32 = 23;
249+
let slot = Box::new(NativeSideLinkSlot::new());
250+
let child = compile_tail_status_body(CHILD_STATUS).expect("tail child should compile");
251+
let root = compile_tail_side_link_body(slot.address() as usize, DEOPT_STATUS)
252+
.expect("tail root should compile");
253+
let wrapper =
254+
compile_system_tail_wrapper(root.entry()).expect("system wrapper should compile");
255+
assert!(child.code_len() > 0);
256+
assert!(root.code_len() > 0);
257+
assert!(wrapper.code_len() > 0);
258+
let entry = unsafe {
259+
std::mem::transmute::<*const u8, unsafe extern "C" fn(*mut crate::Vm) -> i32>(
260+
wrapper.entry(),
261+
)
262+
};
263+
264+
assert!(slot.target().is_null());
265+
assert_eq!(unsafe { entry(std::ptr::null_mut()) }, DEOPT_STATUS);
266+
slot.publish(child.entry());
267+
assert_eq!(slot.target().cast_const(), child.entry());
268+
assert_eq!(unsafe { entry(std::ptr::null_mut()) }, CHILD_STATUS);
269+
slot.clear();
270+
assert!(slot.target().is_null());
271+
assert_eq!(unsafe { entry(std::ptr::null_mut()) }, DEOPT_STATUS);
272+
}
106273

107274
#[test]
108275
fn selected_backend_is_native() {

0 commit comments

Comments
 (0)