Co-authored-by: Nilton Constantino <nilton.constantino@visma.com> Reviewed-on: #8
144 lines
5.3 KiB
Rust
144 lines
5.3 KiB
Rust
use crate::opcode::OpCode;
|
|
use crate::readwrite::*;
|
|
use std::collections::HashMap;
|
|
|
|
/// Represents an operand for an instruction.
|
|
#[derive(Debug, Clone)]
|
|
pub enum Operand {
|
|
/// 32-bit unsigned integer (e.g., indices, addresses).
|
|
U32(u32),
|
|
/// 32-bit signed integer.
|
|
I32(i32),
|
|
/// 64-bit signed integer.
|
|
I64(i64),
|
|
/// 64-bit floating point.
|
|
F64(f64),
|
|
/// Boolean (true/false).
|
|
Bool(bool),
|
|
/// A symbolic label that will be resolved to an absolute PC address.
|
|
Label(String),
|
|
/// A symbolic label that will be resolved to a PC address relative to another label.
|
|
RelLabel(String, String),
|
|
}
|
|
|
|
/// Represents an assembly-level element (either an instruction or a label).
|
|
#[derive(Debug, Clone)]
|
|
pub enum Asm {
|
|
/// An OpCode followed by its operands. The mnemonics represent the operation to be performed.
|
|
Op(OpCode, Vec<Operand>),
|
|
/// A named marker in the code (e.g., "start:").
|
|
Label(String),
|
|
}
|
|
|
|
/// Internal helper to calculate the next PC based on the operands' sizes.
|
|
pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec<Operand>) -> u32 {
|
|
let mut pcp: u32 = initial_pc;
|
|
for operand in operands {
|
|
match operand {
|
|
Operand::U32(_) | Operand::I32(_) | Operand::Label(_) | Operand::RelLabel(_, _) => pcp += 4,
|
|
Operand::I64(_) | Operand::F64(_) => pcp += 8,
|
|
Operand::Bool(_) => pcp += 1,
|
|
}
|
|
}
|
|
pcp
|
|
}
|
|
|
|
pub struct AssembleResult {
|
|
pub code: Vec<u8>,
|
|
pub unresolved_labels: HashMap<String, Vec<u32>>,
|
|
}
|
|
|
|
/// Converts a list of assembly instructions into raw ROM bytes.
|
|
///
|
|
/// The assembly process is done in two passes:
|
|
/// 1. **Label Resolution**: Iterates through all instructions to calculate the PC (Program Counter)
|
|
/// of each label and stores them in a map.
|
|
/// 2. **Code Generation**: Translates each OpCode and its operands (resolving labels using the map)
|
|
/// into the final binary format.
|
|
pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, String> {
|
|
let res = assemble_with_unresolved(instructions)?;
|
|
if !res.unresolved_labels.is_empty() {
|
|
let labels: Vec<_> = res.unresolved_labels.keys().cloned().collect();
|
|
return Err(format!("Undefined labels: {:?}", labels));
|
|
}
|
|
Ok(res.code)
|
|
}
|
|
|
|
pub fn assemble_with_unresolved(instructions: &[Asm]) -> Result<AssembleResult, String> {
|
|
let mut labels = HashMap::new();
|
|
let mut current_pc = 0u32;
|
|
|
|
// First pass: resolve labels
|
|
for instr in instructions {
|
|
match instr {
|
|
Asm::Label(name) => {
|
|
labels.insert(name.clone(), current_pc);
|
|
}
|
|
Asm::Op(_opcode, operands) => {
|
|
current_pc += 2; // OpCode is u16 (2 bytes)
|
|
current_pc = update_pc_by_operand(current_pc, operands);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass: generate bytes
|
|
let mut rom = Vec::new();
|
|
let mut unresolved_labels: HashMap<String, Vec<u32>> = HashMap::new();
|
|
let mut pc = 0u32;
|
|
|
|
for instr in instructions {
|
|
match instr {
|
|
Asm::Label(_) => {}
|
|
Asm::Op(opcode, operands) => {
|
|
write_u16_le(&mut rom, *opcode as u16).map_err(|e| e.to_string())?;
|
|
pc += 2;
|
|
for operand in operands {
|
|
match operand {
|
|
Operand::U32(v) => {
|
|
write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?;
|
|
pc += 4;
|
|
}
|
|
Operand::I32(v) => {
|
|
write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?;
|
|
pc += 4;
|
|
}
|
|
Operand::I64(v) => {
|
|
write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?;
|
|
pc += 8;
|
|
}
|
|
Operand::F64(v) => {
|
|
write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?;
|
|
pc += 8;
|
|
}
|
|
Operand::Bool(v) => {
|
|
rom.push(if *v { 1 } else { 0 });
|
|
pc += 1;
|
|
}
|
|
Operand::Label(name) => {
|
|
if let Some(addr) = labels.get(name) {
|
|
write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?;
|
|
} else {
|
|
unresolved_labels.entry(name.clone()).or_default().push(pc);
|
|
write_u32_le(&mut rom, 0).map_err(|e| e.to_string())?; // Placeholder
|
|
}
|
|
pc += 4;
|
|
}
|
|
Operand::RelLabel(name, base) => {
|
|
let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?;
|
|
let base_addr = labels.get(base).ok_or(format!("Undefined base label: {}", base))?;
|
|
let rel_addr = (*addr as i64) - (*base_addr as i64);
|
|
write_u32_le(&mut rom, rel_addr as u32).map_err(|e| e.to_string())?;
|
|
pc += 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(AssembleResult {
|
|
code: rom,
|
|
unresolved_labels,
|
|
})
|
|
}
|