diff --git a/crates/noxis-classfile/src/format/write/code/frames.rs b/crates/noxis-classfile/src/format/write/code/frames.rs index 9f23677..20f5c7a 100644 --- a/crates/noxis-classfile/src/format/write/code/frames.rs +++ b/crates/noxis-classfile/src/format/write/code/frames.rs @@ -1,7 +1,9 @@ -//! Кадры `StackMapTable` при записи: вариант в вариант, дельты как есть. +//! Кадры `StackMapTable` при записи: вариант в вариант, дельты по инструкциям. //! -//! Наш IR хранит кадры в номерах инструкций (как backend после чтения) — -//! backend сам пересчитает их в байты при `to_bytes`. +//! Наш IR хранит `offset_delta` в номерах инструкций; backend при `to_bytes` +//! сам пересчитает их в байты. Компактный формат (`Same`/`SameLocals`) годен +//! только когда **байтовая** дельта ≤ 63 — иначе нужен расширенный вариант. +//! Выбор делаем по байтовой дельте: байты считаются от layout текущего метода. use ristretto_classfile::attributes::{StackFrame as RtFrame, VerificationType as RtType}; @@ -9,37 +11,70 @@ use super::super::pool::Pool; use crate::error::ClassfileError; use crate::format::{StackMapFrame, VerificationType}; +/// Байтовое смещение начала инструкции (для расчёта дельт кадров). +type ByteOf<'a> = &'a (dyn Fn(u32) -> Result + 'a); + /// Кадры IR → кадры backend'а (индексные дельты — как есть). pub(super) fn convert_frames( frames: &[StackMapFrame], pool: &mut Pool, + byte_of: ByteOf<'_>, ) -> Result, ClassfileError> { - frames - .iter() - .map(|frame| build_frame(frame, pool)) - .collect() + let mut out = Vec::with_capacity(frames.len()); + let mut first = true; + let mut last_instruction: u32 = 0; + let mut last_byte: u16 = 0; + for frame in frames { + let delta = frame_delta(frame); + let instruction = if first { + delta + } else { + last_instruction + delta + 1 + }; + let byte = byte_of(instruction)?; + // Байтовая дельта относительно конца предыдущего кадра. + let byte_delta = if first { + byte + } else { + byte.checked_sub(last_byte) + .and_then(|d| d.checked_sub(1)) + .ok_or_else(|| ClassfileError::Write("кадры не по порядку".into()))? + }; + out.push(build_frame(frame, pool, byte_delta)?); + last_instruction = instruction; + last_byte = byte; + first = false; + } + Ok(out) } -fn build_frame(frame: &StackMapFrame, pool: &mut Pool) -> Result { +/// `byte_delta` — истинная байтовая дельта; компактный вид годен лишь при ≤ 63. +fn build_frame( + frame: &StackMapFrame, + pool: &mut Pool, + byte_delta: u16, +) -> Result { let delta = frame_delta(frame); - let offset = u16::try_from(delta) + let extended = u16::try_from(delta) .map_err(|_| ClassfileError::Write(format!("дельта кадра {delta} вне u16")))?; Ok(match frame { StackMapFrame::Same { .. } => { - if delta <= 63 { + if byte_delta <= 63 { + // Backend при verify()/to_bytes читает frame_type как дельту по + // ИНСТРУКЦИЯМ и сам пересчитает её в байты (≤ 63 — влезает). RtFrame::SameFrame { frame_type: delta as u8, } } else { RtFrame::SameFrameExtended { frame_type: 251, - offset_delta: offset, + offset_delta: extended, } } } StackMapFrame::SameLocals { local, .. } => { let stack = vec![vtype(local, pool)?]; - if delta <= 63 { + if byte_delta <= 63 { RtFrame::SameLocals1StackItemFrame { frame_type: 64 + delta as u8, stack, @@ -47,14 +82,14 @@ fn build_frame(frame: &StackMapFrame, pool: &mut Pool) -> Result RtFrame::ChopFrame { frame_type: 251u8.wrapping_sub(*count), - offset_delta: offset, + offset_delta: extended, }, StackMapFrame::Append { locals, .. } => { let count = u8::try_from(locals.len()).map_err(|_| { @@ -67,13 +102,13 @@ fn build_frame(frame: &StackMapFrame, pool: &mut Pool) -> Result RtFrame::FullFrame { frame_type: 255, - offset_delta: offset, + offset_delta: extended, locals: types(locals, pool)?, stack: types(stack, pool)?, }, diff --git a/crates/noxis-classfile/src/format/write/code/mod.rs b/crates/noxis-classfile/src/format/write/code/mod.rs index 0ecd58b..d621396 100644 --- a/crates/noxis-classfile/src/format/write/code/mod.rs +++ b/crates/noxis-classfile/src/format/write/code/mod.rs @@ -55,7 +55,7 @@ pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result