Skip to content

Commit 289cc3c

Browse files
committed
fix(jit): guard inlined callable identity
1 parent dd1db5f commit 289cc3c

3 files changed

Lines changed: 102 additions & 4 deletions

File tree

src/vm/jit/inline.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,14 @@ pub(crate) fn classify_static_inline_candidate(
4848
return Err(InlineRejectReason::NonRootCaller);
4949
}
5050
let source_local = source_local.ok_or(InlineRejectReason::UnknownTarget)?;
51-
let binding = program
51+
let mut bindings = program
5252
.root_callable_bindings
5353
.iter()
54-
.find(|binding| binding.local_slot == u16::from(source_local))
55-
.ok_or(InlineRejectReason::UnknownTarget)?;
54+
.filter(|binding| binding.local_slot == u16::from(source_local));
55+
let binding = bindings.next().ok_or(InlineRejectReason::UnknownTarget)?;
56+
if bindings.next().is_some() {
57+
return Err(InlineRejectReason::PolymorphicTarget);
58+
}
5659
if caller_prototype_id == Some(binding.prototype_id) {
5760
return Err(InlineRejectReason::Recursive);
5861
}

src/vm/jit/recorder.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::fmt;
1+
use std::{fmt, sync::Arc};
22

3+
use crate::CallableValue;
34
use crate::builtins::BuiltinFunction;
45
use crate::vm::{OpCode, Program, Value, ValueType, checked_int_div};
56

@@ -1245,6 +1246,55 @@ pub(crate) fn record_trace_with_local_count(
12451246
if inline_frame.is_none()
12461247
&& let Ok(candidate) = candidate
12471248
{
1249+
let prototype = &program.callable_prototypes[candidate.prototype_id as usize];
1250+
let expected_callable = builder
1251+
.append_value_inst(
1252+
current_block,
1253+
ip,
1254+
SsaValueRepr::Tagged,
1255+
SsaInstKind::Constant(Value::Callable(Arc::new(CallableValue {
1256+
prototype_id: candidate.prototype_id,
1257+
kind: prototype.kind,
1258+
env: None,
1259+
}))),
1260+
)
1261+
.map_err(|err| TraceRecordError::InvalidIr(err.to_string()))?;
1262+
let callable_matches = builder
1263+
.append_value_inst(
1264+
current_block,
1265+
ip,
1266+
SsaValueRepr::Bool,
1267+
SsaInstKind::ValueCmpEq {
1268+
lhs: callable.value.id,
1269+
rhs: expected_callable.id,
1270+
},
1271+
)
1272+
.map_err(|err| TraceRecordError::InvalidIr(err.to_string()))?;
1273+
let identity_exit =
1274+
add_symbolic_exit(&mut builder, ip, &frame, inline_frame.as_ref());
1275+
let (guarded_block, guarded_frame, guard_args) = continue_with_inline_frame(
1276+
&mut builder,
1277+
&frame,
1278+
&mut inline_frame,
1279+
"inline_callable_identity",
1280+
)?;
1281+
builder
1282+
.set_terminator(
1283+
current_block,
1284+
SsaTerminator::BranchBool {
1285+
condition: callable_matches.id,
1286+
if_true: SsaBranchTarget::Block {
1287+
target: guarded_block,
1288+
args: guard_args,
1289+
},
1290+
if_false: SsaBranchTarget::Exit(identity_exit),
1291+
},
1292+
)
1293+
.map_err(|err| TraceRecordError::InvalidIr(err.to_string()))?;
1294+
current_block = guarded_block;
1295+
frame = guarded_frame;
1296+
op_names.push("inline_callable_identity_guard".to_string());
1297+
12481298
let operand_base = frame.stack.len() - usize::from(argc) - 1;
12491299
let mut operands = frame.stack.split_off(operand_base);
12501300
let _callable = operands.remove(0);

tests/jit/jit_tests.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6453,6 +6453,51 @@ fn trace_jit_inlines_static_leaf_in_root_loop() {
64536453
assert!(vm.dump_jit_info().contains("interpreter fallbacks: 0"));
64546454
}
64556455

6456+
#[test]
6457+
fn trace_jit_guards_static_inline_callable_identity() {
6458+
if !native_jit_supported() {
6459+
return;
6460+
}
6461+
let source = r#"
6462+
fn add_one(value: int) -> int { value + 1 }
6463+
fn add_ten(value: int) -> int { value + 10 }
6464+
let mut i = 0;
6465+
let mut total = 0;
6466+
while i < 100 {
6467+
total = add_one(total);
6468+
i = i + 1;
6469+
}
6470+
total;
6471+
"#;
6472+
let compiled = compile_source(source).expect("callable identity source should compile");
6473+
let bindings = compiled.program.root_callable_bindings.clone();
6474+
let replaced_slot = bindings.first().expect("add_one binding").local_slot;
6475+
let replacement_id = bindings.get(1).expect("add_ten binding").prototype_id;
6476+
let replacement_kind = compiled.program.callable_prototypes[replacement_id as usize].kind;
6477+
let mut vm = Vm::new(compiled.program.with_local_count(compiled.locals));
6478+
vm.set_local(
6479+
u8::try_from(replaced_slot).expect("root callable slot should fit u8"),
6480+
Value::Callable(Arc::new(vm::CallableValue {
6481+
prototype_id: replacement_id,
6482+
kind: replacement_kind,
6483+
env: None,
6484+
})),
6485+
)
6486+
.expect("callable replacement should succeed");
6487+
vm.set_jit_config(JitConfig {
6488+
enabled: true,
6489+
hot_loop_threshold: 1,
6490+
max_trace_len: 512,
6491+
});
6492+
6493+
assert_eq!(
6494+
vm.run().expect("identity guard source should run"),
6495+
VmStatus::Halted
6496+
);
6497+
assert_eq!(vm.stack(), &[Value::Int(1_000)]);
6498+
assert!(vm.jit_native_exec_count() > 0, "{}", vm.dump_jit_info());
6499+
}
6500+
64566501
#[test]
64576502
fn trace_jit_inlines_array_swap_leaf() {
64586503
if !native_jit_supported() {

0 commit comments

Comments
 (0)