Skip to content

Commit dd1db5f

Browse files
committed
fix(jit): preserve owned values across native writes
1 parent 8f7a4ab commit dd1db5f

4 files changed

Lines changed: 139 additions & 29 deletions

File tree

src/vm/jit/native/lower.rs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ use crate::vm::native::{
2727
non_yielding_i64_host_call_signature, non_yielding_scalar_host_call_entry_address,
2828
non_yielding_scalar_host_call_signature, pack_shared_signature, regex_match_entry_address,
2929
regex_match_signature, regex_replace_entry_address, regex_replace_signature,
30-
restore_active_sparse_exit_state_entry_address, restore_virtual_frame_entry_address,
31-
restore_virtual_frame_signature, shared_array_from_buffer_entry_address,
32-
shared_bytes_from_buffer_entry_address, shared_string_from_buffer_entry_address,
33-
sparse_restore_exit_signature, string_binary_transform_signature,
34-
string_contains_entry_address, string_contains_signature, string_lower_ascii_entry_address,
35-
string_replace_literal_entry_address, string_replace_signature,
36-
string_split_literal_entry_address, string_unary_transform_signature, to_string_entry_address,
37-
type_of_entry_address, value_eq_entry_address, value_eq_signature, value_len_entry_address,
38-
value_len_signature, value_slot_signature, write_heap_value_to_slot_entry_address,
39-
zero_bytes_entry_address,
30+
replace_value_in_slot_entry_address, restore_active_sparse_exit_state_entry_address,
31+
restore_virtual_frame_entry_address, restore_virtual_frame_signature,
32+
shared_array_from_buffer_entry_address, shared_bytes_from_buffer_entry_address,
33+
shared_string_from_buffer_entry_address, sparse_restore_exit_signature,
34+
string_binary_transform_signature, string_contains_entry_address, string_contains_signature,
35+
string_lower_ascii_entry_address, string_replace_literal_entry_address,
36+
string_replace_signature, string_split_literal_entry_address, string_unary_transform_signature,
37+
to_string_entry_address, type_of_entry_address, value_eq_entry_address, value_eq_signature,
38+
value_len_entry_address, value_len_signature, value_slot_signature,
39+
write_heap_value_to_slot_entry_address, zero_bytes_entry_address,
4040
};
4141
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
4242
use cranelift_codegen::ir::immediates::Ieee64;
@@ -667,7 +667,8 @@ fn try_compile_ssa_trace(
667667
};
668668
let frame_state_ref = b.import_signature(frame_state_sig);
669669
let deopt_refs = SsaDeoptHelperRefs {
670-
clone_value_ref: b.import_signature(clone_value_sig),
670+
clone_value_ref: b.import_signature(clone_value_sig.clone()),
671+
replace_value_ref: b.import_signature(clone_value_sig),
671672
value_eq_ref: b.import_signature(value_eq_sig),
672673
value_len_ref: b.import_signature(value_len_sig),
673674
non_yielding_host_call_ref: b.import_signature(non_yielding_host_call_sig),
@@ -693,6 +694,7 @@ fn try_compile_ssa_trace(
693694
};
694695
let deopt_addrs = SsaDeoptHelperAddrs {
695696
clone_value: clone_value_to_slot_entry_address(),
697+
replace_value: replace_value_in_slot_entry_address(),
696698
value_eq: value_eq_entry_address(),
697699
value_len: value_len_entry_address(),
698700
non_yielding_host_call: non_yielding_host_call_entry_address(),
@@ -1104,6 +1106,7 @@ enum SsaExitAction {
11041106
#[derive(Clone, Copy)]
11051107
struct SsaDeoptHelperRefs {
11061108
clone_value_ref: cranelift_codegen::ir::SigRef,
1109+
replace_value_ref: cranelift_codegen::ir::SigRef,
11071110
value_eq_ref: cranelift_codegen::ir::SigRef,
11081111
value_len_ref: cranelift_codegen::ir::SigRef,
11091112
non_yielding_host_call_ref: cranelift_codegen::ir::SigRef,
@@ -1130,6 +1133,7 @@ struct SsaDeoptHelperRefs {
11301133
#[derive(Clone, Copy)]
11311134
struct SsaDeoptHelperAddrs {
11321135
clone_value: usize,
1136+
replace_value: usize,
11331137
value_eq: usize,
11341138
value_len: usize,
11351139
non_yielding_host_call: usize,
@@ -2080,8 +2084,8 @@ fn lower_ssa_inst(
20802084
b,
20812085
exit_block,
20822086
pointer_type,
2083-
helper_refs.clone_value_ref,
2084-
helper_addrs.clone_value,
2087+
helper_refs.replace_value_ref,
2088+
helper_addrs.replace_value,
20852089
&[out, values[input]],
20862090
)?;
20872091
out
@@ -4701,6 +4705,7 @@ fn lower_ssa_exit_block(
47014705
active_local_base,
47024706
layout.value.size,
47034707
);
4708+
let mut cloned_local_addrs = HashMap::new();
47044709
for (local_index, materialization) in
47054710
exit.locals
47064711
.iter()
@@ -4709,13 +4714,6 @@ fn lower_ssa_exit_block(
47094714
exit.dirty_locals[local_index].then_some((local_index, materialization))
47104715
})
47114716
{
4712-
let index = b.ins().iconst(
4713-
pointer_type,
4714-
i64::try_from(local_index).map_err(|_| {
4715-
VmError::JitNative("SSA dirty local index out of range".to_string())
4716-
})?,
4717-
);
4718-
let dst_addr = ssa_value_addr(b, pointer_type, vm_locals_ptr, index, layout.value.size);
47194717
let clone_before_clear = match materialization {
47204718
SsaMaterialization::BoxHeapPtr { .. } => true,
47214719
SsaMaterialization::Value(value) => !moved_owned_values.contains(value),
@@ -4725,6 +4723,25 @@ fn lower_ssa_exit_block(
47254723
let temp_slot = ssa_create_value_stack_slot(b, layout.value.size)?;
47264724
let temp_addr = b.ins().stack_addr(pointer_type, temp_slot, 0);
47274725
ssa_materialize_slot(b, materialize_ctx, materialization, temp_addr, "local")?;
4726+
cloned_local_addrs.insert(local_index, temp_addr);
4727+
}
4728+
}
4729+
for (local_index, materialization) in
4730+
exit.locals
4731+
.iter()
4732+
.enumerate()
4733+
.filter_map(|(local_index, materialization)| {
4734+
exit.dirty_locals[local_index].then_some((local_index, materialization))
4735+
})
4736+
{
4737+
let index = b.ins().iconst(
4738+
pointer_type,
4739+
i64::try_from(local_index).map_err(|_| {
4740+
VmError::JitNative("SSA dirty local index out of range".to_string())
4741+
})?,
4742+
);
4743+
let dst_addr = ssa_value_addr(b, pointer_type, vm_locals_ptr, index, layout.value.size);
4744+
if let Some(temp_addr) = cloned_local_addrs.get(&local_index).copied() {
47284745
clear_owned_value_temp_slot(b, pointer_type, deopt_refs, deopt_addrs, dst_addr)?;
47294746
ssa_copy_value_bytes(b, temp_addr, dst_addr, layout.value.size);
47304747
ssa_store_tag(b, layout.value, temp_addr, layout.value.null_tag);

src/vm/native/bridge.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ pub(crate) fn clone_value_to_slot_entry_address() -> usize {
264264
pd_vm_native_clone_value_to_slot as *const () as usize
265265
}
266266

267+
pub(crate) fn replace_value_in_slot_entry_address() -> usize {
268+
pd_vm_native_replace_value_in_slot as *const () as usize
269+
}
270+
267271
pub(crate) fn init_null_value_slot_entry_address() -> usize {
268272
pd_vm_native_init_null_value_slot as *const () as usize
269273
}
@@ -636,6 +640,25 @@ pub(crate) extern "C" fn pd_vm_native_clone_value_to_slot(
636640
STATUS_CONTINUE
637641
}
638642

643+
pub(crate) extern "C" fn pd_vm_native_replace_value_in_slot(
644+
dst: *mut Value,
645+
src: *const Value,
646+
) -> i32 {
647+
if dst.is_null() || src.is_null() {
648+
store_bridge_error(VmError::JitNative(
649+
"native replace-value helper received null slot pointer".to_string(),
650+
));
651+
return STATUS_ERROR;
652+
}
653+
654+
unsafe {
655+
let replacement = (*src).clone();
656+
let previous = std::ptr::replace(dst, replacement);
657+
drop(previous);
658+
}
659+
STATUS_CONTINUE
660+
}
661+
639662
pub(crate) extern "C" fn pd_vm_native_init_null_value_slot(dst: *mut Value) -> i32 {
640663
if dst.is_null() {
641664
store_bridge_error(VmError::JitNative(
@@ -2421,4 +2444,17 @@ mod tests {
24212444
assert_eq!(result.get(&Value::Int(2)), Some(&Value::Int(20)));
24222445
assert!(!Arc::ptr_eq(&result, &alias));
24232446
}
2447+
2448+
#[test]
2449+
fn replace_value_slot_clones_before_dropping_an_aliasing_destination() {
2450+
let shared = Arc::new(vec![Value::Int(1)]);
2451+
let mut slot = Value::Array(shared.clone());
2452+
let slot_ptr = &mut slot as *mut Value;
2453+
2454+
let status = pd_vm_native_replace_value_in_slot(slot_ptr, slot_ptr);
2455+
2456+
assert_eq!(status, STATUS_CONTINUE);
2457+
assert_eq!(Arc::strong_count(&shared), 2);
2458+
assert!(matches!(slot, Value::Array(_)));
2459+
}
24242460
}

src/vm/native/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ pub(crate) use bridge::{
2222
map_iter_take_key_entry_address, map_iter_take_value_entry_address, map_set_entry_address,
2323
non_yielding_host_call_entry_address, non_yielding_i64_host_call_entry_address,
2424
non_yielding_scalar_host_call_entry_address, regex_match_entry_address,
25-
regex_replace_entry_address, restore_active_exit_state_entry_address,
26-
restore_active_sparse_exit_state_entry_address, restore_exit_state_entry_address,
27-
restore_sparse_exit_state_entry_address, restore_virtual_frame_entry_address,
28-
shared_array_from_buffer_entry_address, shared_bytes_from_buffer_entry_address,
29-
shared_string_from_buffer_entry_address, store_bridge_error, string_contains_entry_address,
30-
string_lower_ascii_entry_address, string_replace_literal_entry_address,
31-
string_split_literal_entry_address, take_bridge_error, to_string_entry_address,
32-
type_of_entry_address, value_eq_entry_address, value_len_entry_address,
33-
write_heap_value_to_slot_entry_address, zero_bytes_entry_address,
25+
regex_replace_entry_address, replace_value_in_slot_entry_address,
26+
restore_active_exit_state_entry_address, restore_active_sparse_exit_state_entry_address,
27+
restore_exit_state_entry_address, restore_sparse_exit_state_entry_address,
28+
restore_virtual_frame_entry_address, shared_array_from_buffer_entry_address,
29+
shared_bytes_from_buffer_entry_address, shared_string_from_buffer_entry_address,
30+
store_bridge_error, string_contains_entry_address, string_lower_ascii_entry_address,
31+
string_replace_literal_entry_address, string_split_literal_entry_address, take_bridge_error,
32+
to_string_entry_address, type_of_entry_address, value_eq_entry_address,
33+
value_len_entry_address, write_heap_value_to_slot_entry_address, zero_bytes_entry_address,
3434
};
3535
#[cfg(feature = "cranelift-jit")]
3636
pub(crate) use codegen::{

tests/jit/jit_tests.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3342,6 +3342,63 @@ fn trace_jit_restores_tagged_heap_locals_on_ssa_exit() {
33423342
);
33433343
}
33443344

3345+
#[test]
3346+
fn trace_jit_snapshots_borrowed_tagged_locals_before_exit_writes() {
3347+
if !native_jit_supported() {
3348+
return;
3349+
}
3350+
3351+
let mut bc = BytecodeBuilder::new();
3352+
bc.ldc(0);
3353+
bc.stloc(0);
3354+
bc.ldc(1);
3355+
bc.stloc(1);
3356+
bc.ldc(2);
3357+
bc.stloc(2);
3358+
let root_ip = bc.position();
3359+
bc.ldloc(2);
3360+
bc.ldc(4);
3361+
bc.clt();
3362+
let guard_ip = bc.position();
3363+
bc.brfalse(0);
3364+
bc.ldloc(0);
3365+
bc.ldloc(1);
3366+
bc.stloc(0);
3367+
bc.stloc(1);
3368+
bc.ldloc(2);
3369+
bc.ldc(3);
3370+
bc.add();
3371+
bc.stloc(2);
3372+
bc.br(root_ip);
3373+
let exit_ip = bc.position();
3374+
bc.ldloc(1);
3375+
bc.ret();
3376+
3377+
let mut code = bc.finish();
3378+
patch_branch_target(&mut code, guard_ip, exit_ip);
3379+
let program = Program::new(
3380+
vec![
3381+
Value::string("left"),
3382+
Value::string("right"),
3383+
Value::Int(0),
3384+
Value::Int(1),
3385+
Value::Int(5),
3386+
],
3387+
code,
3388+
)
3389+
.with_local_count(3);
3390+
let mut vm = Vm::new(program);
3391+
vm.set_jit_config(JitConfig {
3392+
enabled: true,
3393+
hot_loop_threshold: 1,
3394+
max_trace_len: 512,
3395+
});
3396+
3397+
assert_eq!(vm.run().expect("vm should run"), VmStatus::Halted);
3398+
assert_eq!(vm.stack(), &[Value::string("left")]);
3399+
assert!(vm.jit_native_exec_count() > 0, "{}", vm.dump_jit_info());
3400+
}
3401+
33453402
#[test]
33463403
fn trace_jit_restores_array_and_map_locals_on_ssa_exit() {
33473404
if !native_jit_supported() {

0 commit comments

Comments
 (0)