Skip to content

Commit 2fa0267

Browse files
committed
feat(jit): transfer owned side-entry state
1 parent 4cf464a commit 2fa0267

3 files changed

Lines changed: 243 additions & 6 deletions

File tree

src/vm/jit/native/lower.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,122 @@ pub(crate) fn compile_system_tail_wrapper(root_entry: *const u8) -> VmResult<Com
222222
)
223223
}
224224

225+
fn tail_owned_entry_signature(pointer_type: cranelift_codegen::ir::Type) -> Signature {
226+
let mut signature = tail_entry_signature(pointer_type);
227+
signature.params.push(AbiParam::new(pointer_type));
228+
signature
229+
}
230+
231+
fn system_owned_entry_signature(
232+
pointer_type: cranelift_codegen::ir::Type,
233+
call_conv: CallConv,
234+
) -> Signature {
235+
let mut signature = entry_signature(pointer_type, call_conv);
236+
signature.params.push(AbiParam::new(pointer_type));
237+
signature
238+
}
239+
240+
pub(crate) fn compile_tail_owned_side_link_body(
241+
slot_address: usize,
242+
deopt_status: i32,
243+
) -> VmResult<CompiledTailFunction> {
244+
compile_standalone_native_function(
245+
"pd_vm_tail_owned_side_link",
246+
|pointer_type, _| tail_owned_entry_signature(pointer_type),
247+
move |builder, pointer_type, _| {
248+
let entry = builder.create_block();
249+
let deopt = builder.create_block();
250+
let linked = builder.create_block();
251+
builder.append_block_params_for_function_params(entry);
252+
builder.switch_to_block(entry);
253+
let vm_ptr = builder.block_params(entry)[0];
254+
let owned_slot = builder.block_params(entry)[1];
255+
let slot_address = iconst_ptr_from_addr(builder, pointer_type, slot_address)?;
256+
let target = builder
257+
.ins()
258+
.load(pointer_type, MemFlags::new(), slot_address, 0);
259+
let is_null = builder.ins().icmp_imm(IntCC::Equal, target, 0);
260+
builder.ins().brif(is_null, deopt, &[], linked, &[]);
261+
262+
builder.switch_to_block(deopt);
263+
let status = builder.ins().iconst(types::I32, i64::from(deopt_status));
264+
builder.ins().return_(&[status]);
265+
266+
builder.switch_to_block(linked);
267+
let signature = builder.import_signature(tail_owned_entry_signature(pointer_type));
268+
builder
269+
.ins()
270+
.return_call_indirect(signature, target, &[vm_ptr, owned_slot]);
271+
Ok(())
272+
},
273+
)
274+
}
275+
276+
pub(crate) fn compile_tail_owned_clear_body(success_status: i32) -> VmResult<CompiledTailFunction> {
277+
compile_standalone_native_function(
278+
"pd_vm_tail_owned_clear",
279+
|pointer_type, _| tail_owned_entry_signature(pointer_type),
280+
move |builder, pointer_type, call_conv| {
281+
let entry = builder.create_block();
282+
let success = builder.create_block();
283+
let failure = builder.create_block();
284+
builder.append_block_params_for_function_params(entry);
285+
builder.append_block_param(failure, types::I32);
286+
builder.switch_to_block(entry);
287+
let owned_slot = builder.block_params(entry)[1];
288+
let helper =
289+
iconst_ptr_from_addr(builder, pointer_type, clear_value_slot_entry_address())?;
290+
let helper_signature =
291+
builder.import_signature(value_slot_signature(pointer_type, call_conv));
292+
let call = builder
293+
.ins()
294+
.call_indirect(helper_signature, helper, &[owned_slot]);
295+
let helper_status = builder.inst_results(call)[0];
296+
let succeeded =
297+
builder
298+
.ins()
299+
.icmp_imm(IntCC::Equal, helper_status, i64::from(STATUS_CONTINUE));
300+
builder
301+
.ins()
302+
.brif(succeeded, success, &[], failure, &[helper_status.into()]);
303+
304+
builder.switch_to_block(success);
305+
let status = builder.ins().iconst(types::I32, i64::from(success_status));
306+
builder.ins().return_(&[status]);
307+
308+
builder.switch_to_block(failure);
309+
let status = builder.block_params(failure)[0];
310+
builder.ins().return_(&[status]);
311+
Ok(())
312+
},
313+
)
314+
}
315+
316+
pub(crate) fn compile_system_owned_tail_wrapper(
317+
root_entry: *const u8,
318+
) -> VmResult<CompiledTailFunction> {
319+
let root_entry = root_entry as usize;
320+
compile_standalone_native_function(
321+
"pd_vm_tail_owned_wrapper",
322+
system_owned_entry_signature,
323+
move |builder, pointer_type, _| {
324+
let entry = builder.create_block();
325+
builder.append_block_params_for_function_params(entry);
326+
builder.switch_to_block(entry);
327+
let vm_ptr = builder.block_params(entry)[0];
328+
let owned_slot = builder.block_params(entry)[1];
329+
let root_entry = iconst_ptr_from_addr(builder, pointer_type, root_entry)?;
330+
let signature = builder.import_signature(tail_owned_entry_signature(pointer_type));
331+
let call = builder
332+
.ins()
333+
.call_indirect(signature, root_entry, &[vm_ptr, owned_slot]);
334+
let status = builder.inst_results(call)[0];
335+
builder.ins().return_(&[status]);
336+
Ok(())
337+
},
338+
)
339+
}
340+
225341
fn try_compile_ssa_trace(
226342
trace: &JitTrace,
227343
ssa: &SsaTrace,

src/vm/jit/native/mod.rs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,47 @@ pub(super) fn compile_native_region(
188188
#[cfg(test)]
189189
mod tests {
190190
use super::lower::{
191-
compile_system_tail_wrapper, compile_tail_side_link_body, compile_tail_status_body,
191+
compile_system_owned_tail_wrapper, compile_system_tail_wrapper,
192+
compile_tail_owned_clear_body, compile_tail_owned_side_link_body,
193+
compile_tail_side_link_body, compile_tail_status_body,
192194
};
193195
use super::{
194196
InheritedStateAbiClass, NativeSideLinkSlot, SideEntryOwnership,
195197
classify_side_entry_materialization, classify_side_entry_repr, selected_codegen_backend,
196198
};
197-
use crate::ValueType;
198199
use crate::vm::jit::ir::{SsaMaterialization, SsaValueId, SsaValueRepr};
200+
use crate::{Value, ValueType};
201+
use std::sync::Arc;
202+
203+
#[cfg(target_os = "linux")]
204+
fn assert_executable_mapping_is_not_writable(entry: *const u8) {
205+
let address = entry as usize;
206+
let maps = std::fs::read_to_string("/proc/self/maps").expect("process maps should read");
207+
let mapping = maps
208+
.lines()
209+
.find(|line| {
210+
let Some((range, _)) = line.split_once(' ') else {
211+
return false;
212+
};
213+
let Some((start, end)) = range.split_once('-') else {
214+
return false;
215+
};
216+
let Ok(start) = usize::from_str_radix(start, 16) else {
217+
return false;
218+
};
219+
let Ok(end) = usize::from_str_radix(end, 16) else {
220+
return false;
221+
};
222+
start <= address && address < end
223+
})
224+
.expect("entry mapping should exist");
225+
let permissions = mapping
226+
.split_whitespace()
227+
.nth(1)
228+
.expect("mapping permissions should exist");
229+
assert!(permissions.contains('x'), "{mapping}");
230+
assert!(!permissions.contains('w'), "{mapping}");
231+
}
199232

200233
#[test]
201234
fn side_entry_abi_classifies_scalar_pointer_tagged_borrowed_and_owned_values() {
@@ -252,6 +285,8 @@ mod tests {
252285
.expect("tail root should compile");
253286
let wrapper =
254287
compile_system_tail_wrapper(root.entry()).expect("system wrapper should compile");
288+
#[cfg(target_os = "linux")]
289+
assert_executable_mapping_is_not_writable(wrapper.entry());
255290
assert!(child.code_len() > 0);
256291
assert!(root.code_len() > 0);
257292
assert!(wrapper.code_len() > 0);
@@ -271,6 +306,53 @@ mod tests {
271306
assert_eq!(unsafe { entry(std::ptr::null_mut()) }, DEOPT_STATUS);
272307
}
273308

309+
#[cfg(feature = "cranelift-jit")]
310+
#[test]
311+
fn trace_jit_side_entry_transfers_owned_values_once() {
312+
if selected_codegen_backend() != "native" {
313+
return;
314+
}
315+
const DEOPT_STATUS: i32 = 29;
316+
const CHILD_STATUS: i32 = 31;
317+
let slot = Box::new(NativeSideLinkSlot::new());
318+
let child =
319+
compile_tail_owned_clear_body(CHILD_STATUS).expect("owned child should compile");
320+
let root = compile_tail_owned_side_link_body(slot.address() as usize, DEOPT_STATUS)
321+
.expect("owned root should compile");
322+
let wrapper = compile_system_owned_tail_wrapper(root.entry())
323+
.expect("owned system wrapper should compile");
324+
let entry = unsafe {
325+
std::mem::transmute::<*const u8, unsafe extern "C" fn(*mut crate::Vm, *mut Value) -> i32>(
326+
wrapper.entry(),
327+
)
328+
};
329+
let backing = Arc::new("owned".to_string());
330+
let mut owned = Value::String(backing.clone());
331+
assert_eq!(Arc::strong_count(&backing), 2);
332+
333+
assert_eq!(
334+
unsafe { entry(std::ptr::null_mut(), &mut owned) },
335+
DEOPT_STATUS
336+
);
337+
assert!(matches!(owned, Value::String(_)));
338+
assert_eq!(Arc::strong_count(&backing), 2);
339+
340+
slot.publish(child.entry());
341+
assert_eq!(
342+
unsafe { entry(std::ptr::null_mut(), &mut owned) },
343+
CHILD_STATUS
344+
);
345+
assert!(matches!(owned, Value::Null));
346+
assert_eq!(Arc::strong_count(&backing), 1);
347+
348+
assert_eq!(
349+
unsafe { entry(std::ptr::null_mut(), &mut owned) },
350+
CHILD_STATUS
351+
);
352+
assert!(matches!(owned, Value::Null));
353+
assert_eq!(Arc::strong_count(&backing), 1);
354+
}
355+
274356
#[test]
275357
fn selected_backend_is_native() {
276358
#[cfg(feature = "cranelift-jit")]

src/vm/native/exec.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ impl ExecutableBuffer {
2525
std::ptr::copy_nonoverlapping(code.as_ptr(), ptr, code.len());
2626
});
2727
flush_instruction_cache(ptr, code.len());
28-
prepare_for_execution();
28+
if let Err(err) = protect_executable_buffer(ptr, code.len()) {
29+
free_executable(ptr, code.len());
30+
return Err(err);
31+
}
2932
}
3033

3134
Ok(Self {
@@ -67,15 +70,15 @@ impl Drop for ExecutableBuffer {
6770
#[cfg(windows)]
6871
unsafe fn alloc_executable(len: usize) -> VmResult<*mut u8> {
6972
use windows_sys::Win32::System::Memory::{
70-
MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE, VirtualAlloc,
73+
MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc,
7174
};
7275

7376
let ptr = unsafe {
7477
VirtualAlloc(
7578
std::ptr::null_mut(),
7679
len,
7780
MEM_COMMIT | MEM_RESERVE,
78-
PAGE_EXECUTE_READWRITE,
81+
PAGE_READWRITE,
7982
)
8083
} as *mut u8;
8184
if ptr.is_null() {
@@ -104,11 +107,15 @@ unsafe fn flush_instruction_cache(ptr: *mut u8, len: usize) {
104107

105108
#[cfg(unix)]
106109
unsafe fn alloc_executable(len: usize) -> VmResult<*mut u8> {
110+
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
111+
let prot = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC;
112+
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
113+
let prot = libc::PROT_READ | libc::PROT_WRITE;
107114
let ptr = unsafe {
108115
libc::mmap(
109116
std::ptr::null_mut(),
110117
len,
111-
libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
118+
prot,
112119
libc::MAP_PRIVATE | map_anon_flag() | map_jit_flag(),
113120
-1,
114121
0,
@@ -173,6 +180,38 @@ const fn map_jit_flag() -> i32 {
173180
0
174181
}
175182

183+
#[cfg(all(unix, target_os = "macos", target_arch = "aarch64"))]
184+
pub(crate) unsafe fn protect_executable_buffer(_ptr: *mut u8, _len: usize) -> VmResult<()> {
185+
if unsafe { libc::pthread_jit_write_protect_supported_np() } != 0 {
186+
unsafe { libc::pthread_jit_write_protect_np(1) };
187+
}
188+
Ok(())
189+
}
190+
191+
#[cfg(all(unix, not(all(target_os = "macos", target_arch = "aarch64"))))]
192+
pub(crate) unsafe fn protect_executable_buffer(ptr: *mut u8, len: usize) -> VmResult<()> {
193+
if unsafe { libc::mprotect(ptr.cast(), len, libc::PROT_READ | libc::PROT_EXEC) } != 0 {
194+
return Err(VmError::JitNative(format!(
195+
"mprotect failed for executable trace buffer: {}",
196+
std::io::Error::last_os_error()
197+
)));
198+
}
199+
Ok(())
200+
}
201+
202+
#[cfg(windows)]
203+
pub(crate) unsafe fn protect_executable_buffer(ptr: *mut u8, len: usize) -> VmResult<()> {
204+
use windows_sys::Win32::System::Memory::{PAGE_EXECUTE_READ, VirtualProtect};
205+
206+
let mut previous = 0;
207+
if unsafe { VirtualProtect(ptr.cast(), len, PAGE_EXECUTE_READ, &mut previous) } == 0 {
208+
return Err(VmError::JitNative(
209+
"VirtualProtect failed for executable trace buffer".to_string(),
210+
));
211+
}
212+
Ok(())
213+
}
214+
176215
#[cfg(all(unix, target_os = "macos", target_arch = "aarch64"))]
177216
pub(crate) unsafe fn prepare_for_execution() {
178217
if unsafe { libc::pthread_jit_write_protect_supported_np() } != 0 {

0 commit comments

Comments
 (0)