Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 4 additions & 26 deletions majit/majit-macros/src/jit_interp/jitcode_lower/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3203,32 +3203,10 @@ pub(super) fn emit_promote_greens(lowerer: &mut Lowerer, config: &LowererConfig)
});
let reg = binding.reg;
let kind = binding.kind;
// jtransform.py:1707: emit `-live-` before each guard_value so the
// codewriter's per-marker liveness analysis records the alive set here.
lowerer.emit_op(
OpMeta::live_marker(),
quote::quote! { __builder.live_placeholder(); },
);
match kind {
BindingKind::Int => {
lowerer.emit_op(
OpMeta::linear(OpKind::GuardValue, vec![Register::int(reg)], vec![]),
quote::quote! { __builder.int_guard_value(#reg); },
);
}
BindingKind::Ref => {
lowerer.emit_op(
OpMeta::linear(OpKind::GuardValue, vec![Register::ref_(reg)], vec![]),
quote::quote! { __builder.ref_guard_value(#reg); },
);
}
BindingKind::Float => {
lowerer.emit_op(
OpMeta::linear(OpKind::GuardValue, vec![Register::float(reg)], vec![]),
quote::quote! { __builder.float_guard_value(#reg); },
);
}
}
// The `-live-` before each guard_value is what lets the codewriter's
// per-marker liveness analysis record the alive set at the guard; the
// pair is shared with `handle_recursive_call`'s promote_greens.
lowerer.emit_live_and_guard_value(kind, reg);
}
}

Expand Down
60 changes: 60 additions & 0 deletions majit/majit-macros/src/jit_interp/jitcode_lower/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,66 @@ pub(super) fn binop_f_emit_tokens(
quote! { __builder.record_binop_f(#dst, majit_ir::OpCode::#opcode, #lhs, #rhs); }
}

/// jtransform.py `_rewrite_symmetric`'s binding list, in this layer's operator
/// domain: the symmetric arithmetic and bitwise operators plus every
/// comparison (upstream binds `int_lt` .. `uint_ge` and `float_lt` ..
/// `float_ge`, and reaches the same function again as `_rewrite_equality`'s
/// fallthrough for `int_eq` / `int_ne` / `ptr_eq` / `ptr_ne`). `-`, the
/// divisions and the shifts are absent for the same reason they are absent
/// upstream: they are not symmetric.
pub(super) fn binop_is_symmetric(op: &BinOp) -> bool {
matches!(
op,
BinOp::Add(_)
| BinOp::Mul(_)
| BinOp::BitAnd(_)
| BinOp::BitOr(_)
| BinOp::BitXor(_)
| BinOp::And(_)
| BinOp::Or(_)
| BinOp::Eq(_)
| BinOp::Ne(_)
| BinOp::Lt(_)
| BinOp::Le(_)
| BinOp::Gt(_)
| BinOp::Ge(_)
)
}

/// jtransform.py `_rewrite_symmetric`'s `reversename` table: swapping the
/// operands of an ordered comparison mirrors it. Everything else keeps its
/// operator, which is upstream's `.get(op.opname, op.opname)` default.
pub(super) fn mirrored_compare_binop(op: &BinOp) -> BinOp {
match op {
BinOp::Lt(_) => BinOp::Gt(Default::default()),
BinOp::Le(_) => BinOp::Ge(Default::default()),
BinOp::Gt(_) => BinOp::Lt(Default::default()),
BinOp::Ge(_) => BinOp::Le(Default::default()),
other => *other,
}
}

/// `isinstance(arg, Constant)` plus the `arg.value` `_rewrite_equality` tests,
/// answered at the layer this lowering runs in: the operand is still a source
/// literal, not yet a flow-graph `Constant`. A `bool` literal counts because
/// its lowered register holds `0` / `1`, which is the same `lltype.Bool` the
/// upstream test sees.
pub(super) fn int_literal_value(expr: &syn::Expr) -> Option<i64> {
match expr {
syn::Expr::Group(group) => int_literal_value(&group.expr),
syn::Expr::Paren(paren) => int_literal_value(&paren.expr),
syn::Expr::Lit(lit) => match &lit.lit {
syn::Lit::Int(value) => value.base10_parse::<i64>().ok(),
syn::Lit::Bool(value) => Some(i64::from(value.value)),
_ => None,
},
syn::Expr::Unary(unary) if matches!(unary.op, syn::UnOp::Neg(_)) => {
int_literal_value(&unary.expr).and_then(i64::checked_neg)
}
_ => None,
}
}

pub(super) fn opcode_for_binop(op: &BinOp) -> Option<Ident> {
let name = match op {
BinOp::Add(_) => "IntAdd",
Expand Down
58 changes: 57 additions & 1 deletion majit/majit-macros/src/jit_interp/jitcode_lower/lower_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,16 @@ impl<'c> Lowerer<'c> {
nested_failure_reasons: Vec::new(),
opcode_var_name: self.opcode_var_name.clone(),
in_dispatch_arm_body: self.in_dispatch_arm_body,
dispatch_loop_label: self.dispatch_loop_label.clone(),
// Never inherited: inside a loop body an unlabelled `continue`
// binds to that loop, not to the dispatch back-edge. The
// spellings this block lowers itself -- a bare `continue` and one
// in an `if` branch -- are taken by `lower_loop_stmt` /
// `lower_loop_if` with `continue_label`; any other spelling falls
// back to `lower_stmt`, whose `Expr::Continue` arm emits a jump to
// `dispatch_loop_label`. Carrying the dispatch label in would aim
// that jump at the dispatch head instead of this loop. Cleared, it
// refuses there.
dispatch_loop_label: None,
pc_pinned: self.pc_pinned,
// Never inherited: a loop body statement is not the arm body's
// tail, so a `return` inside it must be rejected, not lowered.
Expand Down Expand Up @@ -1093,6 +1102,53 @@ mod unroll_binding_tests {
);
}

/// A `continue` inside a `match` in a loop body binds to that loop, not
/// to the dispatch back-edge. `lower_loop_stmt` / `lower_loop_if` take
/// the direct and `if`-branch spellings with the loop's own labels; a
/// `match` arm falls back to `lower_stmt`, whose `Expr::Continue` arm
/// answered it with a jump to `dispatch_loop_label`. On the pre-fix
/// lowerer this `while` lowers and its stream names the dispatch head —
/// one loop out from the loop the source wrote.
#[test]
fn a_continue_in_a_match_inside_a_loop_never_targets_the_dispatch_head() {
let mut lowerer = Lowerer::new(None);
lowerer.dispatch_loop_label = Some(syn::Ident::new(
"__l_dispatch",
proc_macro2::Span::call_site(),
));
let stmt: Stmt = syn::parse_quote! { let flag = 1; };
let Stmt::Local(local) = stmt else {
unreachable!("parse_quote produced the requested let statement")
};
assert!(lowerer.lower_local(&local).is_some());

let expr: syn::ExprWhile = syn::parse_quote! {
while flag {
match flag {
1 => continue,
_ => {},
}
}
};
let lowered = lowerer.lower_while_loop(&expr);

let emitted = lowerer
.statements
.iter()
.map(|tokens| tokens.to_string())
.collect::<Vec<_>>()
.join("\n");
assert!(
!emitted.contains("__l_dispatch"),
"the inner loop's `continue` must not jump to the dispatch head:\n{emitted}"
);
assert!(
lowered.is_none(),
"a `continue` this loop cannot spell must refuse the loop, not \
retarget it:\n{emitted}"
);
}

#[test]
fn typed_local_binds_like_the_unannotated_spelling() {
let mut lowerer = Lowerer::new(None);
Expand Down
Loading
Loading