//! Operands: constants, arithmetic, arrays, calls. use ristretto_classfile::attributes::{ArrayType, Instruction as RtInsn}; use super::super::pool::Pool; use crate::error::ClassfileError; use crate::insn::{Arith, ArrayKind, Const, Convert, InvokeKind, Prim}; /// A constant: short forms (`iconst`, `bipush`) + `ldc_w`/`ldc2_w` through the pool. pub(super) fn int_const(value: &Const, pool: &mut Pool) -> Result { Ok(match value { Const::Int(v) => match *v { -1 => RtInsn::Iconst_m1, 0 => RtInsn::Iconst_0, 1 => RtInsn::Iconst_1, 2 => RtInsn::Iconst_2, 3 => RtInsn::Iconst_3, 4 => RtInsn::Iconst_4, 5 => RtInsn::Iconst_5, v if i8::try_from(v).is_ok() => RtInsn::Bipush(v as i8), v if i16::try_from(v).is_ok() => RtInsn::Sipush(v as i16), _ => RtInsn::Ldc_w(pool.constant(value)?), }, Const::Long(0) => RtInsn::Lconst_0, Const::Long(1) => RtInsn::Lconst_1, Const::Long(_) => RtInsn::Ldc2_w(pool.constant(value)?), Const::Float(v) if *v == 0.0 => RtInsn::Fconst_0, Const::Float(v) if *v == 1.0 => RtInsn::Fconst_1, Const::Float(v) if *v == 2.0 => RtInsn::Fconst_2, Const::Float(_) => RtInsn::Ldc_w(pool.constant(value)?), Const::Double(v) if *v == 0.0 => RtInsn::Dconst_0, Const::Double(v) if *v == 1.0 => RtInsn::Dconst_1, Const::Double(_) => RtInsn::Ldc2_w(pool.constant(value)?), // JVMS §4.4.10: a `CONSTANT_Dynamic`'s category comes from its own // descriptor, not from being `Dynamic` - `J`/`D` are category 2 // (`ldc2_w`), everything else (references, `I`/`F`/...) is // category 1 (`ldc_w`). Using `ldc2_w` unconditionally here was a // bug: a condy-encrypted `String` would push the wrong stack shape. Const::Dynamic { name, descriptor, bootstrap, } => { let index = pool.dynamic(*bootstrap, name, descriptor)?; if matches!(descriptor.as_str(), "J" | "D") { RtInsn::Ldc2_w(index) } else { RtInsn::Ldc_w(index) } } _ => RtInsn::Ldc_w(pool.constant(value)?), }) } pub(super) fn array_load(kind: ArrayKind) -> RtInsn { match kind { ArrayKind::Boolean | ArrayKind::Byte => RtInsn::Baload, ArrayKind::Char => RtInsn::Caload, ArrayKind::Short => RtInsn::Saload, ArrayKind::Int => RtInsn::Iaload, ArrayKind::Long => RtInsn::Laload, ArrayKind::Float => RtInsn::Faload, ArrayKind::Double => RtInsn::Daload, ArrayKind::Reference => RtInsn::Aaload, } } pub(super) fn array_store(kind: ArrayKind) -> RtInsn { match kind { ArrayKind::Boolean | ArrayKind::Byte => RtInsn::Bastore, ArrayKind::Char => RtInsn::Castore, ArrayKind::Short => RtInsn::Sastore, ArrayKind::Int => RtInsn::Iastore, ArrayKind::Long => RtInsn::Lastore, ArrayKind::Float => RtInsn::Fastore, ArrayKind::Double => RtInsn::Dastore, ArrayKind::Reference => RtInsn::Aastore, } } pub(super) fn array_type(kind: ArrayKind) -> Result { Ok(match kind { ArrayKind::Boolean => ArrayType::Boolean, ArrayKind::Char => ArrayType::Char, ArrayKind::Float => ArrayType::Float, ArrayKind::Double => ArrayType::Double, ArrayKind::Byte => ArrayType::Byte, ArrayKind::Short => ArrayType::Short, ArrayKind::Int => ArrayType::Int, ArrayKind::Long => ArrayType::Long, ArrayKind::Reference => { return Err(ClassfileError::Write( "newarray не создаёт ссылочные массивы (нужен anewarray)".into(), )); } }) } pub(super) fn binary(prim: Prim, op: Arith) -> Result { use Arith::{Add, And, Div, Mul, Neg, Or, Rem, Shl, Shr, Sub, Ushr, Xor}; Ok(match (prim, op) { (Prim::Int, Add) => RtInsn::Iadd, (Prim::Int, Sub) => RtInsn::Isub, (Prim::Int, Mul) => RtInsn::Imul, (Prim::Int, Div) => RtInsn::Idiv, (Prim::Int, Rem) => RtInsn::Irem, (Prim::Int, Neg) => RtInsn::Ineg, (Prim::Int, And) => RtInsn::Iand, (Prim::Int, Or) => RtInsn::Ior, (Prim::Int, Xor) => RtInsn::Ixor, (Prim::Int, Shl) => RtInsn::Ishl, (Prim::Int, Shr) => RtInsn::Ishr, (Prim::Int, Ushr) => RtInsn::Iushr, (Prim::Long, Add) => RtInsn::Ladd, (Prim::Long, Sub) => RtInsn::Lsub, (Prim::Long, Mul) => RtInsn::Lmul, (Prim::Long, Div) => RtInsn::Ldiv, (Prim::Long, Rem) => RtInsn::Lrem, (Prim::Long, Neg) => RtInsn::Lneg, (Prim::Long, And) => RtInsn::Land, (Prim::Long, Or) => RtInsn::Lor, (Prim::Long, Xor) => RtInsn::Lxor, (Prim::Long, Shl) => RtInsn::Lshl, (Prim::Long, Shr) => RtInsn::Lshr, (Prim::Long, Ushr) => RtInsn::Lushr, (Prim::Float, Add) => RtInsn::Fadd, (Prim::Float, Sub) => RtInsn::Fsub, (Prim::Float, Mul) => RtInsn::Fmul, (Prim::Float, Div) => RtInsn::Fdiv, (Prim::Float, Rem) => RtInsn::Frem, (Prim::Float, Neg) => RtInsn::Fneg, (Prim::Double, Add) => RtInsn::Dadd, (Prim::Double, Sub) => RtInsn::Dsub, (Prim::Double, Mul) => RtInsn::Dmul, (Prim::Double, Div) => RtInsn::Ddiv, (Prim::Double, Rem) => RtInsn::Drem, (Prim::Double, Neg) => RtInsn::Dneg, (prim, op) => { return Err(ClassfileError::Write(format!( "недопустимая комбинация {prim:?}/{op:?}" ))); } }) } pub(super) fn cmp(prim: Prim, nan_great: bool) -> Result { Ok(match (prim, nan_great) { (Prim::Long, false) => RtInsn::Lcmp, (Prim::Float, false) => RtInsn::Fcmpl, (Prim::Float, true) => RtInsn::Fcmpg, (Prim::Double, false) => RtInsn::Dcmpl, (Prim::Double, true) => RtInsn::Dcmpg, (prim, nan_great) => { return Err(ClassfileError::Write(format!( "недопустимое сравнение {prim:?}/{nan_great}" ))); } }) } pub(super) fn convert_op(op: Convert) -> RtInsn { match op { Convert::I2L => RtInsn::I2l, Convert::I2F => RtInsn::I2f, Convert::I2D => RtInsn::I2d, Convert::I2B => RtInsn::I2b, Convert::I2C => RtInsn::I2c, Convert::I2S => RtInsn::I2s, Convert::L2I => RtInsn::L2i, Convert::L2F => RtInsn::L2f, Convert::L2D => RtInsn::L2d, Convert::F2I => RtInsn::F2i, Convert::F2L => RtInsn::F2l, Convert::F2D => RtInsn::F2d, Convert::D2I => RtInsn::D2i, Convert::D2L => RtInsn::D2l, Convert::D2F => RtInsn::D2f, } } /// `invoke*` family: the `itf` flag is authoritative (a Static/Special /// call may still target an interface method - `List.of`), so the pool /// entry kind follows it, not the opcode alone. pub(super) fn invoke( kind: InvokeKind, owner: &str, name: &str, descriptor: &str, itf: bool, pool: &mut Pool, ) -> Result { let itf = itf || matches!(kind, InvokeKind::Interface); Ok(match kind { InvokeKind::Virtual => { RtInsn::Invokevirtual(pool.method_ref(owner, name, descriptor, itf)?) } InvokeKind::Special => { RtInsn::Invokespecial(pool.method_ref(owner, name, descriptor, itf)?) } InvokeKind::Static => RtInsn::Invokestatic(pool.method_ref(owner, name, descriptor, itf)?), InvokeKind::Interface => { let index = pool.method_ref(owner, name, descriptor, itf)?; RtInsn::Invokeinterface(index, arg_count(descriptor)?) } }) } /// The `count` byte for `invokeinterface`: argument slots + `objectref`. fn arg_count(descriptor: &str) -> Result { let params = descriptor .split_once('(') .and_then(|(_, rest)| rest.split_once(')')) .map(|(params, _)| params) .ok_or_else(|| ClassfileError::Write(format!("кривой дескриптор {descriptor}")))?; let mut slots = 1u32; let mut chars = params.chars(); while let Some(char) = chars.next() { match char { 'B' | 'C' | 'F' | 'I' | 'S' | 'Z' => slots += 1, 'J' | 'D' => slots += 2, 'L' => { if !skip_to(chars.by_ref(), ';') { return Err(bad_descriptor(descriptor)); } slots += 1; } '[' => { while chars.as_str().starts_with('[') { chars.next(); } match chars.next() { Some('L') => { if !skip_to(chars.by_ref(), ';') { return Err(bad_descriptor(descriptor)); } } Some(_) => {} None => return Err(bad_descriptor(descriptor)), } slots += 1; } _ => return Err(bad_descriptor(descriptor)), } } u8::try_from(slots) .map_err(|_| ClassfileError::Write(format!("слишком много аргументов в {descriptor}"))) } /// Skip characters up to the separator (inclusive); `false` - the string ended. fn skip_to(chars: &mut std::str::Chars<'_>, end: char) -> bool { for char in chars { if char == end { return true; } } false } fn bad_descriptor(descriptor: &str) -> ClassfileError { ClassfileError::Write(format!("кривой дескриптор {descriptor}")) }