passes: implement control-flow pass (4 schemes + rotation)
FlowPass rebuilds one method per scheme picked from FlowConfig: state-spread (dense tableswitch dispatcher), computed-switch (sparse lookupswitch over a counter local), butterfly (branch inversion), exception-goto (dispatcher under try with a dead catch-all), plus opaque goto-splices inside every method and StackMapTable recomputation afterwards. Now in standard_pipeline (between constants and noise); --no-flow is an honest off-flag. Acceptance is real JVM, not shapes: per-scheme verify, rotation spread, calc-prints-45 execution, paranoid smoke on lorep-1.1.0 (131 methods rebuilt, no new failures over standard). Two verifier rules learned the hard way are now construction invariants: no inserted mark shares a bytecode offset with a foreign one (nop pads), and no frameless gap follows a terminal (dead blocks are opaque-entered and throw). Also fixes a latent frame-emitter bug the pass exposed: wide stack values were written as slot pairs ([Double, Top]) while javac writes one entry ([Double]); HotSpot rejects the pair. Stacks now collapse like locals (emit::stack_vars), with a regression test in classfile/tests/frames.rs.
This commit is contained in:
parent
39178420e4
commit
2172206e83
15 changed files with 1360 additions and 103 deletions
|
|
@ -35,7 +35,7 @@ pub(super) fn emit_frames(
|
|||
Some(previous) => target
|
||||
.checked_sub(previous)
|
||||
.and_then(|delta| delta.checked_sub(1))
|
||||
.ok_or_else(|| merger.invalid("кадры не по порядку"))?,
|
||||
.ok_or_else(|| merger.invalid("frames out of order"))?,
|
||||
};
|
||||
out.push(select(delta, &state.locals, &previous_locals, &state.stack));
|
||||
previous_locals = state.locals.clone();
|
||||
|
|
@ -54,13 +54,17 @@ fn select(
|
|||
// otherwise halves would produce `Append{[Top, Long]}`.
|
||||
let current = vars(locals);
|
||||
let base = vars(previous);
|
||||
// The operand stack needs the same collapsing: a `long`/`double` is one
|
||||
// entry (`javac` writes `[Double, Integer]`, not `[Double, Top, Integer]`);
|
||||
// an explicit half without its value is rejected by HotSpot.
|
||||
let stack = stack_vars(stack);
|
||||
if current == base {
|
||||
if stack.is_empty() {
|
||||
return StackMapFrame::Same {
|
||||
offset_delta: delta,
|
||||
};
|
||||
}
|
||||
if let [local] = stack {
|
||||
if let [local] = stack.as_slice() {
|
||||
return StackMapFrame::SameLocals {
|
||||
offset_delta: delta,
|
||||
local: local.clone(),
|
||||
|
|
@ -94,6 +98,30 @@ fn select(
|
|||
}
|
||||
}
|
||||
|
||||
/// Stack entries → values: `[Double, Top]` — one entry (like `javac`).
|
||||
///
|
||||
/// Unlike [`vars`], no trailing-`Top` trimming: every stack entry is live by
|
||||
/// construction, and dropping one would corrupt the depth.
|
||||
fn stack_vars(stack: &[VerificationType]) -> Vec<VerificationType> {
|
||||
let mut out = Vec::with_capacity(stack.len());
|
||||
let mut index = 0;
|
||||
while index < stack.len() {
|
||||
match &stack[index] {
|
||||
VerificationType::Long | VerificationType::Double
|
||||
if stack.get(index + 1) == Some(&VerificationType::Top) =>
|
||||
{
|
||||
out.push(stack[index].clone());
|
||||
index += 2;
|
||||
}
|
||||
other => {
|
||||
out.push(other.clone());
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Slots → variables: `[Long, Top]` — one entry; trailing `Top`s — gone.
|
||||
///
|
||||
/// Single `Top`s inside (holes) are kept: in `Full` they are a legal type, and we
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue