This commit is contained in:
bQUARKz 2026-02-09 23:17:51 +00:00
parent c56256f63e
commit 5941cd7248
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
10 changed files with 194 additions and 161 deletions

View File

@ -2,29 +2,14 @@
//! It specifies how instructions are encoded in bytes and how they interact with memory.
use crate::opcode::OpCode;
use crate::opcode_spec::OpCodeSpecExt;
/// Returns the size in bytes of the operands for a given OpCode.
///
/// Note: This does NOT include the 2 bytes of the OpCode itself.
/// For example, `PushI32` has a size of 4, but occupies 6 bytes in ROM (2 for OpCode + 4 for value).
pub fn operand_size(opcode: OpCode) -> usize {
match opcode {
OpCode::PushConst => 4,
OpCode::PushI32 => 4,
OpCode::PushBounded => 4,
OpCode::PushI64 => 8,
OpCode::PushF64 => 8,
OpCode::PushBool => 1,
OpCode::PopN => 4,
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => 4,
OpCode::GetGlobal | OpCode::SetGlobal => 4,
OpCode::GetLocal | OpCode::SetLocal => 4,
OpCode::Call => 4, // func_id(u32)
OpCode::Syscall => 4,
OpCode::Alloc => 8, // type_id(u32) + slots(u32)
OpCode::GateLoad | OpCode::GateStore => 4, // offset(u32)
_ => 0,
}
opcode.spec().imm_bytes as usize
}
// --- HIP Trap Codes ---
@ -85,7 +70,7 @@ pub fn is_jump(opcode: OpCode) -> bool {
/// Checks if an instruction has any immediate operands in the instruction stream.
pub fn has_immediate(opcode: OpCode) -> bool {
operand_size(opcode) > 0
opcode.spec().imm_bytes > 0
}
#[cfg(test)]

View File

@ -14,6 +14,7 @@
//! - [`readwrite`]: Internal utilities for Little-Endian binary I/O.
pub mod opcode;
pub mod opcode_spec;
pub mod abi;
pub mod readwrite;
pub mod asm;

View File

@ -0,0 +1,115 @@
use crate::opcode::OpCode;
/// Specification for a single OpCode.
/// All JMP/JMP_IF_* immediate are u32 absolute offsets from function start.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpcodeSpec {
pub name: &'static str,
pub imm_bytes: u8, // immediate payload size (decode)
pub pops: u16, // slots popped
pub pushes: u16, // slots pushed
pub is_branch: bool, // has a control-flow target
pub is_terminator: bool, // ends basic block: JMP/RET/TRAP/HALT
pub may_trap: bool, // runtime trap possible
}
pub trait OpCodeSpecExt {
fn spec(&self) -> OpcodeSpec;
}
impl OpCodeSpecExt for OpCode {
fn spec(&self) -> OpcodeSpec {
match self {
OpCode::Nop => OpcodeSpec { name: "NOP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Halt => OpcodeSpec { name: "HALT", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false },
OpCode::Jmp => OpcodeSpec { name: "JMP", imm_bytes: 4, pops: 0, pushes: 0, is_branch: true, is_terminator: true, may_trap: false },
OpCode::JmpIfFalse => OpcodeSpec { name: "JMP_IF_FALSE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true },
OpCode::JmpIfTrue => OpcodeSpec { name: "JMP_IF_TRUE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true },
OpCode::Trap => OpcodeSpec { name: "TRAP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: true },
OpCode::PushConst => OpcodeSpec { name: "PUSH_CONST", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Pop => OpcodeSpec { name: "POP", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PopN => OpcodeSpec { name: "POP_N", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Dup => OpcodeSpec { name: "DUP", imm_bytes: 0, pops: 1, pushes: 2, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Swap => OpcodeSpec { name: "SWAP", imm_bytes: 0, pops: 2, pushes: 2, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushI64 => OpcodeSpec { name: "PUSH_I64", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushF64 => OpcodeSpec { name: "PUSH_F64", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushBool => OpcodeSpec { name: "PUSH_BOOL", imm_bytes: 1, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushI32 => OpcodeSpec { name: "PUSH_I32", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushBounded => OpcodeSpec { name: "PUSH_BOUNDED", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Add => OpcodeSpec { name: "ADD", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Sub => OpcodeSpec { name: "SUB", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Mul => OpcodeSpec { name: "MUL", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Div => OpcodeSpec { name: "DIV", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Mod => OpcodeSpec { name: "MOD", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::BoundToInt => OpcodeSpec { name: "BOUND_TO_INT", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::IntToBoundChecked => OpcodeSpec { name: "INT_TO_BOUND_CHECKED", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Eq => OpcodeSpec { name: "EQ", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Neq => OpcodeSpec { name: "NEQ", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Lt => OpcodeSpec { name: "LT", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Gt => OpcodeSpec { name: "GT", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::And => OpcodeSpec { name: "AND", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Or => OpcodeSpec { name: "OR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Not => OpcodeSpec { name: "NOT", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::BitAnd => OpcodeSpec { name: "BIT_AND", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::BitOr => OpcodeSpec { name: "BIT_OR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::BitXor => OpcodeSpec { name: "BIT_XOR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Shl => OpcodeSpec { name: "SHL", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Shr => OpcodeSpec { name: "SHR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Lte => OpcodeSpec { name: "LTE", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Gte => OpcodeSpec { name: "GTE", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Neg => OpcodeSpec { name: "NEG", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::GetGlobal => OpcodeSpec { name: "GET_GLOBAL", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::SetGlobal => OpcodeSpec { name: "SET_GLOBAL", imm_bytes: 4, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::GetLocal => OpcodeSpec { name: "GET_LOCAL", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::SetLocal => OpcodeSpec { name: "SET_LOCAL", imm_bytes: 4, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Call => OpcodeSpec { name: "CALL", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Ret => OpcodeSpec { name: "RET", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false },
OpCode::PushScope => OpcodeSpec { name: "PUSH_SCOPE", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PopScope => OpcodeSpec { name: "POP_SCOPE", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Alloc => OpcodeSpec { name: "ALLOC", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateLoad => OpcodeSpec { name: "GATE_LOAD", imm_bytes: 4, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateStore => OpcodeSpec { name: "GATE_STORE", imm_bytes: 4, pops: 2, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateBeginPeek => OpcodeSpec { name: "GATE_BEGIN_PEEK", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateEndPeek => OpcodeSpec { name: "GATE_END_PEEK", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateBeginBorrow => OpcodeSpec { name: "GATE_BEGIN_BORROW", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateEndBorrow => OpcodeSpec { name: "GATE_END_BORROW", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateBeginMutate => OpcodeSpec { name: "GATE_BEGIN_MUTATE", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateEndMutate => OpcodeSpec { name: "GATE_END_MUTATE", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateRetain => OpcodeSpec { name: "GATE_RETAIN", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateRelease => OpcodeSpec { name: "GATE_RELEASE", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Syscall => OpcodeSpec { name: "SYSCALL", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::FrameSync => OpcodeSpec { name: "FRAME_SYNC", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
}
}
}
#[cfg(test)]
mod tests {
use super::*;
// Infer the numeric range from the TryFrom<u16> mapping used in opcode.rs tests
// by scanning a plausible range (0..1024) and keeping all successful decodes.
#[test]
fn every_opcode_has_spec_and_imm_defined() {
let mut count = 0usize;
for val in 0u16..=1023u16 {
if let Ok(op) = OpCode::try_from(val) {
let spec = op.spec();
// Access all fields to ensure they are present and not optimized away
let _ = (
spec.name,
spec.imm_bytes,
spec.pops,
spec.pushes,
spec.is_branch,
spec.is_terminator,
spec.may_trap,
);
// imm_bytes must be defined (0 is valid)
assert!(spec.imm_bytes >= 0, "imm_bytes must be defined for {op:?}");
count += 1;
}
}
assert!(count > 0, "No opcodes were found via OpCode::try_from");
}
}

View File

@ -69,12 +69,11 @@ pub fn emit_fragments(module: &ir_vm::Module) -> Result<EmitFragments> {
let mut functions = Vec::new();
let mut function_names = Vec::new();
for (i, function) in module.functions.iter().enumerate() {
let (start_idx, end_idx) = function_ranges[i];
let (start_idx, last_op_idx) = function_ranges[i];
let start_pc = pcs[start_idx];
// Interpretamos `end_idx` como o índice da ÚLTIMA instrução pertencente à função (inclusivo).
// Portanto, o `end_pc` correto é o PC da próxima instrução (exclusivo). Se não houver próxima,
// usamos o tamanho total do bytecode.
let end_pc = if (end_idx + 1) < pcs.len() { pcs[end_idx + 1] } else { bytecode.len() as u32 };
// `last_op_idx` aponta para o último Asm::Op pertencente à função. O PC de término canônico
// é o PC da próxima entrada em `pcs` (exclusivo). Labels subsequentes não alteram o PC.
let end_pc = if (last_op_idx + 1) < pcs.len() { pcs[last_op_idx + 1] } else { bytecode.len() as u32 };
// Nome enriquecido para tooling/analysis: "name@offset+len"
let enriched_name = format!("{}@{}+{}", function.name, start_pc, end_pc - start_pc);
@ -179,6 +178,8 @@ impl BytecodeEmitter {
let mut stack_height: i32 = 0;
// Nome canônico para o label de término desta função
let end_label = format!("{}::__end", function.name);
// Track last opcode index for this function (to exclude trailing padding/labels)
let mut last_op_idx_in_func: Option<usize> = None;
for instr in &function.body {
let op_start_idx = asm_instrs.len();
@ -331,21 +332,22 @@ impl BytecodeEmitter {
}
let op_end_idx = asm_instrs.len();
// If we just pushed an Op, record its index as last_op_idx_in_func
if op_end_idx > 0 {
if let Asm::Op(_, _) = &asm_instrs[op_end_idx - 1] {
last_op_idx_in_func = Some(op_end_idx - 1);
}
}
for _ in op_start_idx..op_end_idx {
ir_instr_map.push(Some(instr));
}
}
// Para compatibilidade com geradores que efetuam saltos para o "fim da função",
// garantimos que exista ao menos um NOP antes do label final. Isso assegura que
// qualquer alvo que considere o label como posição exclusiva ou inclusiva não caia
// dentro do início da próxima função.
asm_instrs.push(Asm::Op(OpCode::Nop, vec![]));
ir_instr_map.push(None);
// Emite label canônico de término no fim real do corpo
asm_instrs.push(Asm::Label(end_label));
ir_instr_map.push(None);
let end_idx = asm_instrs.len();
ranges.push((start_idx, end_idx));
// Determine last op index; if function had no ops, fallback to the padding NOP we just injected
let last_op_idx = last_op_idx_in_func.unwrap_or(start_idx);
ranges.push((start_idx, last_op_idx));
}
Ok(ranges)
}

View File

@ -2,6 +2,7 @@ use crate::building::output::CompiledModule;
use crate::building::plan::BuildStep;
use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::layout;
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo};
use std::collections::HashMap;
use prometeu_abi::virtual_machine::{ProgramImage, Value};
@ -219,51 +220,46 @@ impl Linker {
};
pos += 2;
let imm_len = opcode.spec().imm_bytes as usize;
match opcode {
OpCode::PushConst => {
if pos + 4 <= end {
if pos + imm_len <= end && imm_len == 4 {
let local_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap()) as usize;
if let Some(&global_idx) = local_to_global_const.get(local_idx) {
combined_code[pos..pos+4].copy_from_slice(&global_idx.to_le_bytes());
}
pos += 4;
}
pos += imm_len;
}
OpCode::Call => {
if pos + 4 <= end {
if pos + imm_len <= end && imm_len == 4 {
let local_func_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap());
// Check if this PC was already patched by an import.
// If it wasn't, it's an internal call that needs relocation.
// `import.relocation_pcs` holds the PC at the start of the CALL immediate (after opcode),
// and here `pos` currently points exactly at that immediate.
let reloc_pc = (pos - code_offset) as u32;
let is_import = module.imports.iter().any(|imp| imp.relocation_pcs.contains(&reloc_pc));
if !is_import {
let global_func_idx = module_function_offsets[i] + local_func_idx;
combined_code[pos..pos+4].copy_from_slice(&global_func_idx.to_le_bytes());
}
pos += 4;
}
pos += imm_len;
}
// Do NOT relocate intra-function control flow. Branch immediates are
// function-relative by contract and must remain untouched by the linker.
// Relocate intra-function control-flow immediates by module code offset to preserve absolute PCs
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => {
// Just skip the immediate
pos += 4;
if pos + imm_len <= end && imm_len == 4 {
patch_u32_at(&mut combined_code, pos, &|cur| cur + (code_offset as u32));
}
pos += imm_len;
}
OpCode::PushI32 | OpCode::PushBounded | OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal
| OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore => {
pos += 4;
_ => {
// Generic advance using canonical immediate length.
pos += imm_len;
}
OpCode::PushI64 | OpCode::PushF64 | OpCode::Alloc => {
pos += 8;
}
OpCode::PushBool => {
pos += 1;
}
_ => {}
}
}
}
@ -299,6 +295,9 @@ impl Linker {
// "name@offset+len", alinhar apenas o `code_len` de `combined_functions[idx]` a esses
// valores (os offsets do DebugInfo são locais ao módulo antes do link). Mantemos o
// `code_offset` já realocado durante o PASS 1.
// Track which function metas received a precise code_len from DebugInfo
let mut has_precise_len: Vec<bool> = vec![false; combined_functions.len()];
for (idx, name) in &combined_function_names {
if let Some((base, rest)) = name.split_once('@') {
let mut parts = rest.split('+');
@ -308,6 +307,7 @@ impl Linker {
let old_off = meta.code_offset;
let old_len = meta.code_len;
meta.code_len = len;
has_precise_len[*idx as usize] = true;
eprintln!(
"[Linker][debug] Align len idx={} name={} -> code_offset {} (kept) | code_len {} -> {}",
idx, base, old_off, old_len, len
@ -318,11 +318,30 @@ impl Linker {
}
}
// Recalcular code_len de todas as funções no código combinado com base no deslocamento da próxima função
// (end exclusivo). Isso garante que o fim efetivo da função seja exatamente o início da próxima
// no buffer combinado, evitando divergências em saltos para o fim da função.
// Use rotina canônica compartilhada para recalcular os comprimentos das funções
layout::recompute_function_lengths_in_place(&mut combined_functions, combined_code.len());
// Ensure DebugInfo also contains plain base names alongside enriched names for easy lookup.
// For any entry of form "name@off+len", also add (idx, "name") if missing.
let mut plain_names_to_add: Vec<(u32, String)> = Vec::new();
for (idx, name) in &combined_function_names {
if let Some((base, _)) = name.split_once('@') {
let already_has_plain = combined_function_names.iter().any(|(i, n)| i == idx && n == base);
if !already_has_plain {
plain_names_to_add.push((*idx, base.to_string()));
}
}
}
combined_function_names.extend(plain_names_to_add);
// Recompute code_len ONLY for functions that did NOT receive a precise length from DebugInfo.
// This preserves exact ends emitted by the compiler while still filling lengths for functions
// that lack enriched annotations.
let total_len = combined_code.len();
for i in 0..combined_functions.len() {
if !has_precise_len.get(i).copied().unwrap_or(false) {
let start = combined_functions[i].code_offset as usize;
let end = layout::function_end_from_next(&combined_functions, i, total_len);
combined_functions[i].code_len = end.saturating_sub(start) as u32;
}
}
// Removido padding específico de `frame`; o emissor passou a garantir que o label de término
// esteja no ponto exato do fim do corpo, e, quando necessário, insere NOPs reais antes do fim.
@ -343,6 +362,12 @@ impl Linker {
let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() {
None
} else {
// Ensure entry-point name mapping is present for easy lookup in DebugInfo
if let Some(frame_idx) = final_exports.get("frame") {
if !combined_function_names.iter().any(|(i, n)| i == frame_idx && n == "frame") {
combined_function_names.push((*frame_idx, "frame".to_string()));
}
}
Some(DebugInfo {
pc_to_span: combined_pc_to_span,
function_names: combined_function_names,

View File

@ -248,6 +248,7 @@ mod tests {
#[test]
fn test_framesync_injected_end_to_end() {
use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
@ -282,25 +283,9 @@ mod tests {
// Decode sequentially: each instruction is a u16 opcode (LE) followed by operands.
// We'll walk forward and record the sequence of opcodes, ignoring operands based on known sizes.
fn operand_size(op: u16) -> usize {
match op {
x if x == OpCode::PushConst as u16 => 4,
x if x == OpCode::PushI64 as u16 => 8,
x if x == OpCode::PushF64 as u16 => 8,
x if x == OpCode::PushBool as u16 => 1,
x if x == OpCode::PushI32 as u16 => 4,
x if x == OpCode::PushBounded as u16 => 4,
x if x == OpCode::Jmp as u16 => 4,
x if x == OpCode::JmpIfFalse as u16 => 4,
x if x == OpCode::JmpIfTrue as u16 => 4,
x if x == OpCode::GetLocal as u16 => 4,
x if x == OpCode::SetLocal as u16 => 4,
x if x == OpCode::GetGlobal as u16 => 4,
x if x == OpCode::SetGlobal as u16 => 4,
x if x == OpCode::Alloc as u16 => 8, // type_id (u32) + slots (u32)
x if x == OpCode::Syscall as u16 => 4,
x if x == OpCode::GateLoad as u16 => 4,
x if x == OpCode::GateStore as u16 => 4,
_ => 0,
match OpCode::try_from(op) {
Ok(opc) => opc.spec().imm_bytes as usize,
Err(_) => 0,
}
}

View File

@ -1,5 +1,5 @@
use crate::opcode_spec::{OpCodeSpecExt, OpcodeSpec};
use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::opcode_spec::{OpCodeSpecExt, OpcodeSpec};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError {

View File

@ -1,84 +1,3 @@
use prometeu_bytecode::opcode::OpCode;
/// Specification for a single OpCode.
/// All JMP/JMP_IF_* immediate are u32 absolute offsets from function start.
#[derive(Debug, Clone, Copy)]
pub struct OpcodeSpec {
pub name: &'static str,
pub imm_bytes: u8, // immediate payload size (decode)
pub pops: u16, // slots popped
pub pushes: u16, // slots pushed
pub is_branch: bool, // has a control-flow target
pub is_terminator: bool, // ends basic block: JMP/RET/TRAP/HALT
pub may_trap: bool, // runtime trap possible
}
pub trait OpCodeSpecExt {
fn spec(&self) -> OpcodeSpec;
}
impl OpCodeSpecExt for OpCode {
fn spec(&self) -> OpcodeSpec {
match self {
OpCode::Nop => OpcodeSpec { name: "NOP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Halt => OpcodeSpec { name: "HALT", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false },
OpCode::Jmp => OpcodeSpec { name: "JMP", imm_bytes: 4, pops: 0, pushes: 0, is_branch: true, is_terminator: true, may_trap: false },
OpCode::JmpIfFalse => OpcodeSpec { name: "JMP_IF_FALSE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true },
OpCode::JmpIfTrue => OpcodeSpec { name: "JMP_IF_TRUE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true },
OpCode::Trap => OpcodeSpec { name: "TRAP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: true },
OpCode::PushConst => OpcodeSpec { name: "PUSH_CONST", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Pop => OpcodeSpec { name: "POP", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PopN => OpcodeSpec { name: "POP_N", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Dup => OpcodeSpec { name: "DUP", imm_bytes: 0, pops: 1, pushes: 2, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Swap => OpcodeSpec { name: "SWAP", imm_bytes: 0, pops: 2, pushes: 2, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushI64 => OpcodeSpec { name: "PUSH_I64", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushF64 => OpcodeSpec { name: "PUSH_F64", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushBool => OpcodeSpec { name: "PUSH_BOOL", imm_bytes: 1, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushI32 => OpcodeSpec { name: "PUSH_I32", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PushBounded => OpcodeSpec { name: "PUSH_BOUNDED", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Add => OpcodeSpec { name: "ADD", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Sub => OpcodeSpec { name: "SUB", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Mul => OpcodeSpec { name: "MUL", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Div => OpcodeSpec { name: "DIV", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Mod => OpcodeSpec { name: "MOD", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::BoundToInt => OpcodeSpec { name: "BOUND_TO_INT", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::IntToBoundChecked => OpcodeSpec { name: "INT_TO_BOUND_CHECKED", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Eq => OpcodeSpec { name: "EQ", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Neq => OpcodeSpec { name: "NEQ", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Lt => OpcodeSpec { name: "LT", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Gt => OpcodeSpec { name: "GT", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::And => OpcodeSpec { name: "AND", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Or => OpcodeSpec { name: "OR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Not => OpcodeSpec { name: "NOT", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::BitAnd => OpcodeSpec { name: "BIT_AND", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::BitOr => OpcodeSpec { name: "BIT_OR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::BitXor => OpcodeSpec { name: "BIT_XOR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Shl => OpcodeSpec { name: "SHL", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Shr => OpcodeSpec { name: "SHR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Lte => OpcodeSpec { name: "LTE", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Gte => OpcodeSpec { name: "GTE", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Neg => OpcodeSpec { name: "NEG", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::GetGlobal => OpcodeSpec { name: "GET_GLOBAL", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::SetGlobal => OpcodeSpec { name: "SET_GLOBAL", imm_bytes: 4, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::GetLocal => OpcodeSpec { name: "GET_LOCAL", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false },
OpCode::SetLocal => OpcodeSpec { name: "SET_LOCAL", imm_bytes: 4, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Call => OpcodeSpec { name: "CALL", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Ret => OpcodeSpec { name: "RET", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false },
OpCode::PushScope => OpcodeSpec { name: "PUSH_SCOPE", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::PopScope => OpcodeSpec { name: "POP_SCOPE", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
OpCode::Alloc => OpcodeSpec { name: "ALLOC", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateLoad => OpcodeSpec { name: "GATE_LOAD", imm_bytes: 4, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateStore => OpcodeSpec { name: "GATE_STORE", imm_bytes: 4, pops: 2, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateBeginPeek => OpcodeSpec { name: "GATE_BEGIN_PEEK", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateEndPeek => OpcodeSpec { name: "GATE_END_PEEK", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateBeginBorrow => OpcodeSpec { name: "GATE_BEGIN_BORROW", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateEndBorrow => OpcodeSpec { name: "GATE_END_BORROW", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateBeginMutate => OpcodeSpec { name: "GATE_BEGIN_MUTATE", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateEndMutate => OpcodeSpec { name: "GATE_END_MUTATE", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateRetain => OpcodeSpec { name: "GATE_RETAIN", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true },
OpCode::GateRelease => OpcodeSpec { name: "GATE_RELEASE", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::Syscall => OpcodeSpec { name: "SYSCALL", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: true },
OpCode::FrameSync => OpcodeSpec { name: "FRAME_SYNC", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false },
}
}
}
// Canonical `OpcodeSpec` now lives in `prometeu-bytecode`.
// Keep this module as a thin re-export for compatibility.
pub use prometeu_bytecode::opcode_spec::*;

View File

@ -1,5 +1,6 @@
{
"name": "canonical",
"version": "0.1.0",
"script_fe": "pbs"
"script_fe": "pbs",
"entry": "src/main/modules/main.pbs"
}