pr 01
This commit is contained in:
parent
c56256f63e
commit
5941cd7248
@ -2,29 +2,14 @@
|
|||||||
//! It specifies how instructions are encoded in bytes and how they interact with memory.
|
//! It specifies how instructions are encoded in bytes and how they interact with memory.
|
||||||
|
|
||||||
use crate::opcode::OpCode;
|
use crate::opcode::OpCode;
|
||||||
|
use crate::opcode_spec::OpCodeSpecExt;
|
||||||
|
|
||||||
/// Returns the size in bytes of the operands for a given OpCode.
|
/// Returns the size in bytes of the operands for a given OpCode.
|
||||||
///
|
///
|
||||||
/// Note: This does NOT include the 2 bytes of the OpCode itself.
|
/// 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).
|
/// 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 {
|
pub fn operand_size(opcode: OpCode) -> usize {
|
||||||
match opcode {
|
opcode.spec().imm_bytes as usize
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- HIP Trap Codes ---
|
// --- 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.
|
/// Checks if an instruction has any immediate operands in the instruction stream.
|
||||||
pub fn has_immediate(opcode: OpCode) -> bool {
|
pub fn has_immediate(opcode: OpCode) -> bool {
|
||||||
operand_size(opcode) > 0
|
opcode.spec().imm_bytes > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
//! - [`readwrite`]: Internal utilities for Little-Endian binary I/O.
|
//! - [`readwrite`]: Internal utilities for Little-Endian binary I/O.
|
||||||
|
|
||||||
pub mod opcode;
|
pub mod opcode;
|
||||||
|
pub mod opcode_spec;
|
||||||
pub mod abi;
|
pub mod abi;
|
||||||
pub mod readwrite;
|
pub mod readwrite;
|
||||||
pub mod asm;
|
pub mod asm;
|
||||||
|
|||||||
115
crates/prometeu-bytecode/src/opcode_spec.rs
Normal file
115
crates/prometeu-bytecode/src/opcode_spec.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -69,12 +69,11 @@ pub fn emit_fragments(module: &ir_vm::Module) -> Result<EmitFragments> {
|
|||||||
let mut functions = Vec::new();
|
let mut functions = Vec::new();
|
||||||
let mut function_names = Vec::new();
|
let mut function_names = Vec::new();
|
||||||
for (i, function) in module.functions.iter().enumerate() {
|
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];
|
let start_pc = pcs[start_idx];
|
||||||
// Interpretamos `end_idx` como o índice da ÚLTIMA instrução pertencente à função (inclusivo).
|
// `last_op_idx` aponta para o último Asm::Op pertencente à função. O PC de término canônico
|
||||||
// Portanto, o `end_pc` correto é o PC da próxima instrução (exclusivo). Se não houver próxima,
|
// é o PC da próxima entrada em `pcs` (exclusivo). Labels subsequentes não alteram o PC.
|
||||||
// usamos o tamanho total do bytecode.
|
let end_pc = if (last_op_idx + 1) < pcs.len() { pcs[last_op_idx + 1] } else { bytecode.len() as u32 };
|
||||||
let end_pc = if (end_idx + 1) < pcs.len() { pcs[end_idx + 1] } else { bytecode.len() as u32 };
|
|
||||||
|
|
||||||
// Nome enriquecido para tooling/analysis: "name@offset+len"
|
// Nome enriquecido para tooling/analysis: "name@offset+len"
|
||||||
let enriched_name = format!("{}@{}+{}", function.name, start_pc, end_pc - start_pc);
|
let enriched_name = format!("{}@{}+{}", function.name, start_pc, end_pc - start_pc);
|
||||||
@ -179,6 +178,8 @@ impl BytecodeEmitter {
|
|||||||
let mut stack_height: i32 = 0;
|
let mut stack_height: i32 = 0;
|
||||||
// Nome canônico para o label de término desta função
|
// Nome canônico para o label de término desta função
|
||||||
let end_label = format!("{}::__end", function.name);
|
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 {
|
for instr in &function.body {
|
||||||
let op_start_idx = asm_instrs.len();
|
let op_start_idx = asm_instrs.len();
|
||||||
@ -331,21 +332,22 @@ impl BytecodeEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let op_end_idx = asm_instrs.len();
|
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 {
|
for _ in op_start_idx..op_end_idx {
|
||||||
ir_instr_map.push(Some(instr));
|
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
|
// Emite label canônico de término no fim real do corpo
|
||||||
asm_instrs.push(Asm::Label(end_label));
|
asm_instrs.push(Asm::Label(end_label));
|
||||||
ir_instr_map.push(None);
|
ir_instr_map.push(None);
|
||||||
let end_idx = asm_instrs.len();
|
// Determine last op index; if function had no ops, fallback to the padding NOP we just injected
|
||||||
ranges.push((start_idx, end_idx));
|
let last_op_idx = last_op_idx_in_func.unwrap_or(start_idx);
|
||||||
|
ranges.push((start_idx, last_op_idx));
|
||||||
}
|
}
|
||||||
Ok(ranges)
|
Ok(ranges)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::building::output::CompiledModule;
|
|||||||
use crate::building::plan::BuildStep;
|
use crate::building::plan::BuildStep;
|
||||||
use prometeu_bytecode::opcode::OpCode;
|
use prometeu_bytecode::opcode::OpCode;
|
||||||
use prometeu_bytecode::layout;
|
use prometeu_bytecode::layout;
|
||||||
|
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
|
||||||
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo};
|
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use prometeu_abi::virtual_machine::{ProgramImage, Value};
|
use prometeu_abi::virtual_machine::{ProgramImage, Value};
|
||||||
@ -219,51 +220,46 @@ impl Linker {
|
|||||||
};
|
};
|
||||||
pos += 2;
|
pos += 2;
|
||||||
|
|
||||||
|
let imm_len = opcode.spec().imm_bytes as usize;
|
||||||
match opcode {
|
match opcode {
|
||||||
OpCode::PushConst => {
|
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;
|
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) {
|
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());
|
combined_code[pos..pos+4].copy_from_slice(&global_idx.to_le_bytes());
|
||||||
}
|
}
|
||||||
pos += 4;
|
|
||||||
}
|
}
|
||||||
|
pos += imm_len;
|
||||||
}
|
}
|
||||||
OpCode::Call => {
|
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());
|
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.
|
// Check if this PC was already patched by an import.
|
||||||
// If it wasn't, it's an internal call that needs relocation.
|
// 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),
|
// `import.relocation_pcs` holds the PC at the start of the CALL immediate (after opcode),
|
||||||
// and here `pos` currently points exactly at that immediate.
|
// and here `pos` currently points exactly at that immediate.
|
||||||
let reloc_pc = (pos - code_offset) as u32;
|
let reloc_pc = (pos - code_offset) as u32;
|
||||||
let is_import = module.imports.iter().any(|imp| imp.relocation_pcs.contains(&reloc_pc));
|
let is_import = module.imports.iter().any(|imp| imp.relocation_pcs.contains(&reloc_pc));
|
||||||
|
|
||||||
if !is_import {
|
if !is_import {
|
||||||
let global_func_idx = module_function_offsets[i] + local_func_idx;
|
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());
|
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
|
// Relocate intra-function control-flow immediates by module code offset to preserve absolute PCs
|
||||||
// function-relative by contract and must remain untouched by the linker.
|
|
||||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => {
|
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => {
|
||||||
// Just skip the immediate
|
if pos + imm_len <= end && imm_len == 4 {
|
||||||
pos += 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 => {
|
// Generic advance using canonical immediate length.
|
||||||
pos += 4;
|
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
|
// "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
|
// valores (os offsets do DebugInfo são locais ao módulo antes do link). Mantemos o
|
||||||
// `code_offset` já realocado durante o PASS 1.
|
// `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 {
|
for (idx, name) in &combined_function_names {
|
||||||
if let Some((base, rest)) = name.split_once('@') {
|
if let Some((base, rest)) = name.split_once('@') {
|
||||||
let mut parts = rest.split('+');
|
let mut parts = rest.split('+');
|
||||||
@ -308,6 +307,7 @@ impl Linker {
|
|||||||
let old_off = meta.code_offset;
|
let old_off = meta.code_offset;
|
||||||
let old_len = meta.code_len;
|
let old_len = meta.code_len;
|
||||||
meta.code_len = len;
|
meta.code_len = len;
|
||||||
|
has_precise_len[*idx as usize] = true;
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[Linker][debug] Align len idx={} name={} -> code_offset {} (kept) | code_len {} -> {}",
|
"[Linker][debug] Align len idx={} name={} -> code_offset {} (kept) | code_len {} -> {}",
|
||||||
idx, base, old_off, old_len, 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
|
// Ensure DebugInfo also contains plain base names alongside enriched names for easy lookup.
|
||||||
// (end exclusivo). Isso garante que o fim efetivo da função seja exatamente o início da próxima
|
// For any entry of form "name@off+len", also add (idx, "name") if missing.
|
||||||
// no buffer combinado, evitando divergências em saltos para o fim da função.
|
let mut plain_names_to_add: Vec<(u32, String)> = Vec::new();
|
||||||
// Use rotina canônica compartilhada para recalcular os comprimentos das funções
|
for (idx, name) in &combined_function_names {
|
||||||
layout::recompute_function_lengths_in_place(&mut combined_functions, combined_code.len());
|
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
|
// 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.
|
// 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() {
|
let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} 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 {
|
Some(DebugInfo {
|
||||||
pc_to_span: combined_pc_to_span,
|
pc_to_span: combined_pc_to_span,
|
||||||
function_names: combined_function_names,
|
function_names: combined_function_names,
|
||||||
|
|||||||
@ -248,6 +248,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_framesync_injected_end_to_end() {
|
fn test_framesync_injected_end_to_end() {
|
||||||
use prometeu_bytecode::opcode::OpCode;
|
use prometeu_bytecode::opcode::OpCode;
|
||||||
|
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let project_dir = dir.path().to_path_buf();
|
let project_dir = dir.path().to_path_buf();
|
||||||
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
|
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.
|
// 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.
|
// We'll walk forward and record the sequence of opcodes, ignoring operands based on known sizes.
|
||||||
fn operand_size(op: u16) -> usize {
|
fn operand_size(op: u16) -> usize {
|
||||||
match op {
|
match OpCode::try_from(op) {
|
||||||
x if x == OpCode::PushConst as u16 => 4,
|
Ok(opc) => opc.spec().imm_bytes as usize,
|
||||||
x if x == OpCode::PushI64 as u16 => 8,
|
Err(_) => 0,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::opcode_spec::{OpCodeSpecExt, OpcodeSpec};
|
|
||||||
use prometeu_bytecode::opcode::OpCode;
|
use prometeu_bytecode::opcode::OpCode;
|
||||||
|
use prometeu_bytecode::opcode_spec::{OpCodeSpecExt, OpcodeSpec};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum DecodeError {
|
pub enum DecodeError {
|
||||||
|
|||||||
@ -1,84 +1,3 @@
|
|||||||
use prometeu_bytecode::opcode::OpCode;
|
// Canonical `OpcodeSpec` now lives in `prometeu-bytecode`.
|
||||||
|
// Keep this module as a thin re-export for compatibility.
|
||||||
/// Specification for a single OpCode.
|
pub use prometeu_bytecode::opcode_spec::*;
|
||||||
/// 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 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Binary file not shown.
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "canonical",
|
"name": "canonical",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"script_fe": "pbs"
|
"script_fe": "pbs",
|
||||||
|
"entry": "src/main/modules/main.pbs"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user