Compare commits

..

12 Commits

18 changed files with 2246 additions and 453 deletions
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.85.0"
+32 -4
View File
@@ -2,8 +2,7 @@ use petgraph::dot::{Config, Dot};
use petgraph::graph::{Graph, NodeIndex}; use petgraph::graph::{Graph, NodeIndex};
use crate::ast::types::{ use crate::ast::types::{
BlockStmt, CompileUnit, Expr, ExprValue, FuncDeclStmt, GlobalDeclStmt, Param, ReturnStmt, BlockStmt, BreakStmt, CompileUnit, ContinueStmt, Expr, ExprValue, FuncDeclStmt, GlobalDeclStmt, IfStmt, Param, ReturnStmt, Statement, VarDeclStmt, VarDeclStmtValue, WhileStmt
Statement, VarDeclStmt, VarDeclStmtValue,
}; };
pub type AstGraph = Graph<String, String>; pub type AstGraph = Graph<String, String>;
@@ -116,7 +115,11 @@ impl AstGraphBuilder {
let node = self.child(parent, stmt.to_string()); let node = self.child(parent, stmt.to_string());
self.add_var_decl(node, var_decl); self.add_var_decl(node, var_decl);
node node
} },
Statement::If(if_stmt) => self.add_if_stmt(parent, if_stmt),
Statement::While(while_stmt) => self.add_while_stmt(parent, while_stmt),
Statement::Break(break_stmt) => self.add_break_stmt(parent, break_stmt),
Statement::Continue(continue_stmt) => self.add_continue_stmt(parent, continue_stmt),
} }
} }
@@ -126,7 +129,27 @@ impl AstGraphBuilder {
None => self.child(parent, "Void"), None => self.child(parent, "Void"),
} }
} }
fn add_if_stmt(&mut self, parent: NodeIndex, if_stmt: &IfStmt) -> NodeIndex {
let node = self.child(parent, if_stmt.to_string());
self.add_expr(node, &if_stmt.condition);
self.add_block_stmt(node, &if_stmt.then_branch);
if let Some(else_branch) = &if_stmt.else_branch {
self.add_block_stmt(node, else_branch);
}
node
}
fn add_while_stmt(&mut self, parent: NodeIndex, while_stmt: &WhileStmt) -> NodeIndex {
let node = self.child(parent, while_stmt.to_string());
self.add_expr(node, &while_stmt.condition);
self.add_block_stmt(node, &while_stmt.body);
node
}
fn add_break_stmt(&mut self, parent: NodeIndex, break_stmt: &BreakStmt) -> NodeIndex {
self.child(parent, break_stmt.to_string())
}
fn add_continue_stmt(&mut self, parent: NodeIndex, continue_stmt: &ContinueStmt) -> NodeIndex {
self.child(parent, continue_stmt.to_string())
}
fn add_expr(&mut self, parent: NodeIndex, expr: &Expr) -> NodeIndex { fn add_expr(&mut self, parent: NodeIndex, expr: &Expr) -> NodeIndex {
match &expr.value { match &expr.value {
ExprValue::IntLit(_) | ExprValue::Var(_) => self.child(parent, expr.value.to_string()), ExprValue::IntLit(_) | ExprValue::Var(_) => self.child(parent, expr.value.to_string()),
@@ -149,6 +172,11 @@ impl AstGraphBuilder {
self.add_expr(node, lvalue); self.add_expr(node, lvalue);
self.add_expr(node, rvalue); self.add_expr(node, rvalue);
node node
},
ExprValue::UnaryOp { op: _, operand } => {
let node = self.child(parent, expr.value.to_string());
self.add_expr(node, operand);
node
} }
} }
} }
+105 -17
View File
@@ -1,4 +1,4 @@
use crate::{diagnostic::span::Span, frontend::types::{TokenValue, TypeIdent}}; use crate::{diagnostic::span::Span, frontend::types::{Token, TokenValue, TypeIdent}};
use std::fmt; use std::fmt;
pub struct CompileUnit { pub struct CompileUnit {
@@ -11,12 +11,12 @@ pub enum GlobalDeclStmt {
pub struct VarDeclStmt { pub struct VarDeclStmt {
pub values: Vec<VarDeclStmtValue>, pub values: Vec<VarDeclStmtValue>,
pub span: Span, pub data_type: Type,
pub type_span: Span,
} }
pub struct VarDeclStmtValue { pub struct VarDeclStmtValue {
pub name: String, pub name: String,
pub var_type: Type, pub name_span: Span,
pub span: Span,
} }
@@ -25,11 +25,11 @@ pub struct FuncDeclStmt {
pub return_type: Type, pub return_type: Type,
pub params: Vec<Param>, pub params: Vec<Param>,
pub body: BlockStmt, pub body: BlockStmt,
pub span: Span, pub ret_type_span: Span,
pub name_span: Span,
} }
pub struct BlockStmt { pub struct BlockStmt {
pub statements: Vec<Statement>, pub statements: Vec<Statement>,
pub span: Span,
} }
pub enum Statement { pub enum Statement {
@@ -37,16 +37,46 @@ pub enum Statement {
Block(BlockStmt), Block(BlockStmt),
Expr(Expr), Expr(Expr),
VarDecl(VarDeclStmt), VarDecl(VarDeclStmt),
If(IfStmt),
While(WhileStmt),
Break(BreakStmt),
Continue(ContinueStmt),
} }
impl Statement { // impl Statement {
pub fn span(&self) -> Span { // pub fn span(&self) -> Span {
match self { // match self {
Statement::Return(s) => s.span, // Statement::Return(s) => s.span,
Statement::Block(s) => s.span, // Statement::Block(s) => s.span,
Statement::Expr(s) => s.span, // Statement::Expr(s) => s.span,
Statement::VarDecl(s) => s.span, // Statement::VarDecl(s) => s.span,
// Statement::If(s) => s.span,
// Statement::While(s) => s.span,
// Statement::Break(s) => s.span,
// Statement::Continue(s) => s.span,
// }
// }
// }
pub struct IfStmt {
pub condition: Expr,
pub then_branch: BlockStmt,
pub ifelse_branch: Vec<IfElseBranch>,
pub else_branch: Option<BlockStmt>,
// pub span: Span,
} }
pub struct IfElseBranch {
pub condition: Expr,
pub then_branch: BlockStmt,
} }
pub struct WhileStmt {
pub condition: Expr,
pub body: BlockStmt,
// pub span: Span,
}
pub struct BreakStmt {
pub span: Span,
}
pub struct ContinueStmt {
pub span: Span,
} }
pub struct ReturnStmt { pub struct ReturnStmt {
pub value: Option<Expr>, pub value: Option<Expr>,
@@ -64,6 +94,10 @@ pub enum ExprValue {
op: BinaryOp, op: BinaryOp,
rhs: Box<Expr> rhs: Box<Expr>
}, },
UnaryOp {
op: UnaryOp,
operand: Box<Expr>,
},
FuncCall(String, Vec<Expr>), FuncCall(String, Vec<Expr>),
Assign { Assign {
lvalue: Box<Expr>, lvalue: Box<Expr>,
@@ -74,8 +108,13 @@ pub enum ExprValue {
pub enum BinaryOp { pub enum BinaryOp {
Add, Sub, Mul, Div, Mod, Add, Sub, Mul, Div, Mod,
Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual, Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual,
And, Or,
} }
#[derive(Clone, Copy)]
pub enum UnaryOp {
Add, Sub, Not,
}
impl BinaryOp { impl BinaryOp {
pub fn from_token_value(token_value: &TokenValue) -> Option<Self> { pub fn from_token_value(token_value: &TokenValue) -> Option<Self> {
match token_value { match token_value {
@@ -90,10 +129,20 @@ impl BinaryOp {
TokenValue::LessEqual => Some(BinaryOp::LessEqual), TokenValue::LessEqual => Some(BinaryOp::LessEqual),
TokenValue::Greater => Some(BinaryOp::Greater), TokenValue::Greater => Some(BinaryOp::Greater),
TokenValue::GreaterEqual => Some(BinaryOp::GreaterEqual), TokenValue::GreaterEqual => Some(BinaryOp::GreaterEqual),
TokenValue::And => Some(BinaryOp::And),
TokenValue::Or => Some(BinaryOp::Or),
_ => None, _ => None,
} }
} }
pub fn is_logical(&self) -> bool {
matches!(self, BinaryOp::And | BinaryOp::Or)
} }
pub fn is_cmp(&self) -> bool {
matches!(self, BinaryOp::Equal | BinaryOp::NotEqual | BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual)
}
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Type { pub enum Type {
Int, Int,
@@ -110,7 +159,8 @@ impl From<TypeIdent> for Type {
pub struct Param { pub struct Param {
pub name: String, pub name: String,
pub param_type: Type, pub param_type: Type,
pub span: Span, pub name_span: Span,
pub type_span: Span,
} }
impl fmt::Display for CompileUnit { impl fmt::Display for CompileUnit {
@@ -136,7 +186,7 @@ impl fmt::Display for VarDeclStmt {
impl fmt::Display for VarDeclStmtValue { impl fmt::Display for VarDeclStmtValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.var_type, self.name) write!(f, "{}", self.name)
} }
} }
@@ -159,16 +209,42 @@ impl fmt::Display for Statement {
Statement::Block(_) => write!(f, "BlockStmt"), Statement::Block(_) => write!(f, "BlockStmt"),
Statement::Expr(_) => write!(f, "ExprStmt"), Statement::Expr(_) => write!(f, "ExprStmt"),
Statement::VarDecl(_) => write!(f, "VarDeclStmt"), Statement::VarDecl(_) => write!(f, "VarDeclStmt"),
Statement::If(_) => write!(f, "IfStmt"),
Statement::While(_) => write!(f, "WhileStmt"),
Statement::Break(_) => write!(f, "BreakStmt"),
Statement::Continue(_) => write!(f, "ContinueStmt"),
} }
} }
} }
impl fmt::Display for IfStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "IfStmt")
}
}
impl fmt::Display for ReturnStmt { impl fmt::Display for ReturnStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ReturnStmt") write!(f, "ReturnStmt")
} }
} }
impl fmt::Display for WhileStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WhileStmt")
}
}
impl fmt::Display for BreakStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BreakStmt")
}
}
impl fmt::Display for ContinueStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ContinueStmt")
}
}
impl fmt::Display for Expr { impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value) write!(f, "{}", self.value)
@@ -183,6 +259,7 @@ impl fmt::Display for ExprValue {
ExprValue::BinaryOp { op, .. } => write!(f, "BinaryOp({})", op), ExprValue::BinaryOp { op, .. } => write!(f, "BinaryOp({})", op),
ExprValue::FuncCall(name, _) => write!(f, "FuncCall({})", name), ExprValue::FuncCall(name, _) => write!(f, "FuncCall({})", name),
ExprValue::Assign { .. } => write!(f, "Assign"), ExprValue::Assign { .. } => write!(f, "Assign"),
ExprValue::UnaryOp { op, .. } => write!(f, "UnaryOp({})", op),
} }
} }
} }
@@ -207,11 +284,22 @@ impl fmt::Display for BinaryOp {
BinaryOp::LessEqual => "<=", BinaryOp::LessEqual => "<=",
BinaryOp::Greater => ">", BinaryOp::Greater => ">",
BinaryOp::GreaterEqual => ">=", BinaryOp::GreaterEqual => ">=",
BinaryOp::And => "&&",
BinaryOp::Or => "||",
};
write!(f, "{}", op)
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let op = match self {
UnaryOp::Add => "+",
UnaryOp::Sub => "-",
UnaryOp::Not => "!",
}; };
write!(f, "{}", op) write!(f, "{}", op)
} }
} }
impl fmt::Display for Type { impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
+71 -8
View File
@@ -1,6 +1,6 @@
use std::{fmt::Display, ops::Add}; use std::{fmt::Display, ops::Add};
use crate::backend::register_allocator::{REG_FP, REG_LR, REG_PC, REG_R0, REG_R1, REG_R2, REG_R3, REG_R12, REG_SP, Register}; use crate::{backend::register_allocator::{REG_FP, REG_LR, REG_PC, REG_R0, REG_R1, REG_R2, REG_R3, REG_R12, REG_SP, Register}, ir::types::CmpOp as IRCmpOp};
pub enum ARMInstr{ pub enum ARMInstr{
Move(MoveInstr), Move(MoveInstr),
Load(LoadInstr), Load(LoadInstr),
@@ -10,11 +10,14 @@ pub enum ARMInstr{
SDiv(SDivInstr), SDiv(SDivInstr),
Add(AddInstr), Add(AddInstr),
Sub(SubInstr), Sub(SubInstr),
Rsb(RsbInstr),
Cmp(CmpInstr), Cmp(CmpInstr),
Push(PushInstr), Push(PushInstr),
Pop(PopInstr), Pop(PopInstr),
FunctionHead(String, usize), FunctionHead(String, usize),
Bl(BlInstr), Bl(BlInstr),
B(BInstr),
Label(String),
} }
impl Display for ARMInstr { impl Display for ARMInstr {
@@ -28,11 +31,14 @@ impl Display for ARMInstr {
ARMInstr::SDiv(instr) => write!(f, "{}", instr), ARMInstr::SDiv(instr) => write!(f, "{}", instr),
ARMInstr::Add(instr) => write!(f, "{}", instr), ARMInstr::Add(instr) => write!(f, "{}", instr),
ARMInstr::Sub(instr) => write!(f, "{}", instr), ARMInstr::Sub(instr) => write!(f, "{}", instr),
ARMInstr::Rsb(instr) => write!(f, "{}", instr),
ARMInstr::Cmp(instr) => write!(f, "{}", instr), ARMInstr::Cmp(instr) => write!(f, "{}", instr),
ARMInstr::Push(instr) => write!(f, "{}", instr), ARMInstr::Push(instr) => write!(f, "{}", instr),
ARMInstr::Pop(instr) => write!(f, "{}", instr), ARMInstr::Pop(instr) => write!(f, "{}", instr),
ARMInstr::Bl(instr) => write!(f, "{}", instr), ARMInstr::Bl(instr) => write!(f, "{}", instr),
ARMInstr::B(instr) => write!(f, "{}", instr),
ARMInstr::FunctionHead(name, align_size) => write!(f, ".align {}\n.global {}\n.type {}, %function\n{}:", align_size, name, name, name), ARMInstr::FunctionHead(name, align_size) => write!(f, ".align {}\n.global {}\n.type {}, %function\n{}:", align_size, name, name, name),
ARMInstr::Label(name) => write!(f, "{}:", name),
} }
} }
} }
@@ -56,6 +62,18 @@ pub enum ConditionCode {
Gt, Gt,
Ge, Ge,
} }
impl From<IRCmpOp> for ConditionCode {
fn from(cmp_op: IRCmpOp) -> Self {
match cmp_op {
IRCmpOp::Eq => ConditionCode::Eq,
IRCmpOp::Ne => ConditionCode::Ne,
IRCmpOp::Lt => ConditionCode::Lt,
IRCmpOp::Le => ConditionCode::Le,
IRCmpOp::Gt => ConditionCode::Gt,
IRCmpOp::Ge => ConditionCode::Ge,
}
}
}
impl Display for ConditionCode { impl Display for ConditionCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let code_str = match self { let code_str = match self {
@@ -89,6 +107,9 @@ impl MoveInstr {
pub fn new_uncond(dest: Register, src: RegisterOrImm) -> ARMInstr { pub fn new_uncond(dest: Register, src: RegisterOrImm) -> ARMInstr {
ARMInstr::Move(MoveInstr(None, dest, src)) ARMInstr::Move(MoveInstr(None, dest, src))
} }
pub fn new_cond(condition: ConditionCode, dest: Register, src: RegisterOrImm) -> ARMInstr {
ARMInstr::Move(MoveInstr(Some(condition), dest, src))
}
pub fn new_sp_to_fp() -> ARMInstr { pub fn new_sp_to_fp() -> ARMInstr {
ARMInstr::Move(MoveInstr(None, REG_FP, RegisterOrImm::Reg(REG_SP))) ARMInstr::Move(MoveInstr(None, REG_FP, RegisterOrImm::Reg(REG_SP)))
} }
@@ -205,20 +226,37 @@ impl SubInstr {
ARMInstr::Sub(SubInstr(REG_SP, REG_SP, RegisterOrImm::Imm(offset))) ARMInstr::Sub(SubInstr(REG_SP, REG_SP, RegisterOrImm::Imm(offset)))
} }
} }
impl Display for SubInstr { impl Display for SubInstr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let SubInstr(dest, left, right) = self; let SubInstr(dest, left, right) = self;
write!(f, "sub {}, {}, {}", dest, left, right) write!(f, "sub {}, {}, {}", dest, left, right)
} }
} }
pub struct CmpInstr(Register, Register); pub struct RsbInstr(Register, Register, RegisterOrImm);
impl RsbInstr {
pub fn new(dest: Register, left: Register, right: RegisterOrImm) -> ARMInstr {
ARMInstr::Rsb(RsbInstr(dest, left, right))
}
}
impl Display for RsbInstr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let RsbInstr(dest, left, right) = self;
write!(f, "rsb {}, {}, {}", dest, left, right)
}
}
pub struct CmpInstr(Register, RegisterOrImm);
impl Display for CmpInstr { impl Display for CmpInstr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let CmpInstr(left, right) = self; let CmpInstr(left, right) = self;
write!(f, "cmp {}, {}", left, right) write!(f, "cmp {}, {}", left, right)
} }
} }
impl CmpInstr {
pub fn new(left: Register, right: RegisterOrImm) -> ARMInstr {
ARMInstr::Cmp(CmpInstr(left, right))
}
}
pub struct PushInstr(Vec<Register>); pub struct PushInstr(Vec<Register>);
impl PushInstr { impl PushInstr {
pub fn new_push_fp_lr() -> ARMInstr { pub fn new_push_fp_lr() -> ARMInstr {
@@ -272,16 +310,41 @@ impl Display for PopInstr {
} }
} }
pub struct BlInstr(String); pub struct BlInstr(Option<ConditionCode>, String);
impl Display for BlInstr { impl Display for BlInstr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let BlInstr(func_name) = self; match &self.0 {
write!(f, "bl {}", func_name) Some(condition) => write!(f, "bl{} {}", condition, self.1),
None => write!(f, "bl {}", self.1),
}
} }
} }
impl BlInstr { impl BlInstr {
pub fn new(func_name: String) -> ARMInstr { pub fn new(label_name: String) -> ARMInstr {
ARMInstr::Bl(BlInstr(func_name)) ARMInstr::Bl(BlInstr(None, label_name))
}
pub fn new_cond(condition: ConditionCode, label_name: String) -> ARMInstr {
ARMInstr::Bl(BlInstr(Some(condition), label_name))
}
}
pub struct BInstr(Option<ConditionCode>, String);
impl Display for BInstr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
Some(condition) => write!(f, "b{} {}", condition, self.1),
None => write!(f, "b {}", self.1),
}
}
}
impl BInstr {
pub fn new(label_name: String) -> ARMInstr {
ARMInstr::B(BInstr(None, label_name))
}
pub fn new_cond(condition: ConditionCode, label_name: String) -> ARMInstr {
ARMInstr::B(BInstr(Some(condition), label_name))
} }
} }
+135 -67
View File
@@ -1,7 +1,10 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::{backend::{arm_instr::{ARMInstr, AddInstr, BlInstr, LoadInstr, LoadPseudoInstr, MoveInstr, MulInstr, PopInstr, PushInstr, RegisterOrImm, SDivInstr, StoreInstr, SubInstr}, register_allocator::{REG_R0, REG_R1, REG_R2, REG_R3, Register, RegisterAlloc, RegisterAllocator}, types::ARMAsmVar}, ir::types::{Function, IRInstr, MoveRValue, Variable, VariableType}}; use crate::{backend::{arm_instr::{ARMInstr, AddInstr, BInstr, BlInstr, CmpInstr, ConditionCode, LoadInstr, LoadPseudoInstr, MoveInstr, MulInstr, PopInstr, PushInstr, RegisterOrImm, RsbInstr, SDivInstr, StoreInstr, SubInstr}, register_allocator::{REG_R0, REG_R1, REG_R2, REG_R3, Register, RegisterAlloc, RegisterAllocator}, types::ARMAsmVar}, ir::types::{Function, IRInstr, MoveRValue, Variable, VariableOrIntLit, VariableType}};
use crate::ir::types::BinaryOp as IRBinaryOp; use crate::ir::types::BinaryOp as IRBinaryOp;
use crate::ir::types::CmpOp as IRCmpOp;
use crate::ir::types::UnaryOp as IRUnaryOp;
pub const ARM_STACK_ALIGNMENT: usize = 8; pub const ARM_STACK_ALIGNMENT: usize = 8;
pub struct Generator { pub struct Generator {
instrs: Vec<ARMInstr>, instrs: Vec<ARMInstr>,
@@ -11,6 +14,56 @@ pub struct Generator {
} }
const DEFAULT_VAR_ALIGN: usize = 4; const DEFAULT_VAR_ALIGN: usize = 4;
const ARG_REGS: [Register; 4] = [REG_R0, REG_R1, REG_R2, REG_R3];
fn load_variable(variable: Variable, reg_allocator: &mut RegisterAllocator, var_index_to_stack_offset: &BTreeMap<usize, usize>, instrs: &mut Vec<ARMInstr>) -> RegisterAlloc {
match variable.var_type {
VariableType::Global => {
let var_alloc = reg_allocator.alloc(variable).expect("Ran out of registers");
let var_reg = var_alloc.reg;
// if !var_alloc.is_reused {
let address_alloc = reg_allocator.alloc_any().expect("Ran out of registers");
instrs.push(LoadPseudoInstr::new(address_alloc.reg, format!("global_var_{}", variable.index)));
instrs.push(LoadInstr::new(var_reg, address_alloc.reg, None));
// }
var_alloc
},
VariableType::ParamTemp(param_index) => {
if param_index < ARG_REGS.len() {
let reg = ARG_REGS[param_index];
let var_alloc = reg_allocator.alloc_reg(reg).expect("Ran out of registers");
var_alloc
} else {
todo!("More than 4 parameters not supported yet");
}
},
_ => {
let stack_offset = var_index_to_stack_offset.get(&variable.index).expect("Variable not declared");
let var_alloc = reg_allocator.alloc(variable).expect("Ran out of registers");
// if !var_alloc.is_reused {
instrs.push(LoadInstr::new_stack(var_alloc.reg, *stack_offset as i32));
// }
var_alloc
}
}
}
fn save_variable(variable: Variable, reg: Register, reg_allocator: &mut RegisterAllocator, var_index_to_stack_offset: &BTreeMap<usize, usize>, instrs: &mut Vec<ARMInstr>) {
match variable.var_type {
VariableType::Global => {
let address_alloc = reg_allocator.alloc_any().expect("Ran out of registers");
instrs.push(LoadPseudoInstr::new(address_alloc.reg, format!("global_var_{}", variable.index)));
instrs.push(StoreInstr::new(reg, address_alloc.reg, None));
},
VariableType::ParamTemp(_) => {
todo!()
},
_ => {
let stack_offset = var_index_to_stack_offset.get(&variable.index).expect("Variable not declared");
instrs.push(StoreInstr::new_stack(reg, *stack_offset as i32));
}
}
}
impl Generator { impl Generator {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@@ -59,10 +112,10 @@ impl Generator {
self.instrs.push(ARMInstr::FunctionHead(func.name.clone(), 4)); // Assuming 4-byte alignment for simplicity self.instrs.push(ARMInstr::FunctionHead(func.name.clone(), 4)); // Assuming 4-byte alignment for simplicity
self.instrs.push(PushInstr::new_push_fp_lr()); self.instrs.push(PushInstr::new_push_fp_lr());
self.instrs.push(MoveInstr::new_sp_to_fp()); self.instrs.push(MoveInstr::new_sp_to_fp());
self.emit_func(body); self.emit_func(&func.name, body);
} }
fn emit_func(&mut self, instrs: Vec<IRInstr>) { fn emit_func(&mut self, func_name: &str, instrs: Vec<IRInstr>) {
let mut encounter_entry = false; let mut encounter_entry = false;
let mut stack_size_needed = 0; let mut stack_size_needed = 0;
let mut var_index_to_stack_offset = BTreeMap::new(); let mut var_index_to_stack_offset = BTreeMap::new();
@@ -73,13 +126,8 @@ impl Generator {
if let Some(v) = v { if let Some(v) = v {
let ret_alloc = self.register_allocator.alloc_reg(REG_R0).expect("Ran out of registers"); let ret_alloc = self.register_allocator.alloc_reg(REG_R0).expect("Ran out of registers");
let ret_reg = ret_alloc.reg; let ret_reg = ret_alloc.reg;
let v_alloc = self.register_allocator.alloc(v).expect("Ran out of registers"); let v_alloc = load_variable(v, &mut self.register_allocator, &var_index_to_stack_offset, &mut self.instrs);
let v_reg = v_alloc.reg; self.instrs.push(MoveInstr::new_uncond(ret_reg, RegisterOrImm::Reg(v_alloc.reg)));
if !v_alloc.is_reused {
let v_stack_offset = var_index_to_stack_offset.get(&v.index).expect("Variable not declared");
self.instrs.push(LoadInstr::new_stack(v_reg, *v_stack_offset as i32));
}
self.instrs.push(MoveInstr::new_uncond(ret_reg, RegisterOrImm::Reg(v_reg)));
} }
self.instrs.push(MoveInstr::new_fp_to_sp()); self.instrs.push(MoveInstr::new_fp_to_sp());
self.instrs.push(PopInstr::new_pop_fp_pc()); self.instrs.push(PopInstr::new_pop_fp_pc());
@@ -98,30 +146,75 @@ impl Generator {
self.instrs.push(SubInstr::new_sp(stack_size_needed as i32)); self.instrs.push(SubInstr::new_sp(stack_size_needed as i32));
}, },
IRInstr::DefineFunc(_, _, _) => unreachable!(), IRInstr::DefineFunc(_, _, _) => unreachable!(),
IRInstr::Cmp(variable, left, cmp_op, right) => self.emit_cmp(variable, left, cmp_op, right, &var_index_to_stack_offset),
IRInstr::Unary(variable, unary_op, variable1) => self.emit_unary(variable, unary_op, variable1, &var_index_to_stack_offset),
IRInstr::Goto(index) => self.instrs.push(BInstr::new(format!("label_{}_{}", func_name, index))),
IRInstr::CondGoto(variable, true_label_index, false_label_index) => {
let variable_alloc = load_variable(variable, &mut self.register_allocator, &var_index_to_stack_offset, &mut self.instrs);
let variable_reg = variable_alloc.reg;
self.instrs.push(CmpInstr::new(variable_reg, RegisterOrImm::Imm(0)));
self.instrs.push(BInstr::new_cond(ConditionCode::Ne, format!("label_{}_{}", func_name, true_label_index)));
self.instrs.push(BInstr::new(format!("label_{}_{}", func_name, false_label_index)));
},
IRInstr::Label(index) => self.instrs.push(ARMInstr::Label(format!("label_{}_{}", func_name, index))),
} }
} }
} }
fn emit_unary(&mut self, dest: Variable, unary_op: IRUnaryOp, variable: Variable, var_index_to_stack_offset: &BTreeMap<usize, usize>) {
let variable_alloc = load_variable(variable, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers");
match unary_op {
IRUnaryOp::Neg => {
self.instrs.push(RsbInstr::new(dest_alloc.reg, variable_alloc.reg, RegisterOrImm::Imm(0)));
},
}
save_variable(dest, dest_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
}
fn emit_cmp(&mut self, variable: Variable, left: VariableOrIntLit, cmp_op: IRCmpOp, right: VariableOrIntLit, var_index_to_stack_offset: &BTreeMap<usize, usize>) {
let left_alloc = match left {
VariableOrIntLit::Var(var) => {
load_variable(var, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs)
},
VariableOrIntLit::IntLit(lit) => {
let alloc = self.register_allocator.alloc_any().expect("Ran out of registers");
self.instrs.push(MoveInstr::new_uncond(alloc.reg, RegisterOrImm::Imm(lit)));
alloc
},
};
// TODO: check left == right
let right_alloc = match right {
VariableOrIntLit::Var(var) => {
load_variable(var, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs)
},
VariableOrIntLit::IntLit(lit) => {
let alloc = self.register_allocator.alloc_any().expect("Ran out of registers");
self.instrs.push(MoveInstr::new_uncond(alloc.reg, RegisterOrImm::Imm(lit)));
alloc
},
};
let variable_alloc = self.register_allocator.alloc(variable).expect("Ran out of registers");
let variable_reg = variable_alloc.reg;
self.instrs.push(CmpInstr::new(left_alloc.reg, RegisterOrImm::Reg(right_alloc.reg)));
self.instrs.push(MoveInstr::new_uncond(variable_reg, RegisterOrImm::Imm(0)));
self.instrs.push(MoveInstr::new_cond(cmp_op.into(), variable_reg, RegisterOrImm::Imm(1)));
save_variable(variable, variable_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
}
fn emit_func_call(&mut self, func: Function, args: Vec<Variable>, ret: Option<Variable>, var_index_to_stack_offset: &BTreeMap<usize, usize>) { fn emit_func_call(&mut self, func: Function, args: Vec<Variable>, ret: Option<Variable>, var_index_to_stack_offset: &BTreeMap<usize, usize>) {
self.instrs.push(PushInstr::new_push_caller_save()); self.instrs.push(PushInstr::new_push_caller_save());
if args.len() > 4 { if args.len() > 4 {
todo!("More than 4 arguments not supported yet"); todo!("More than 4 arguments not supported yet");
} }
const ARG_REGS: [Register; 4] = [REG_R0, REG_R1, REG_R2, REG_R3]; let mut arg_reg_allocs = Vec::new();
for (i, arg) in args.into_iter().enumerate() { for (i, arg) in args.into_iter().enumerate() {
let arg_alloc = self.register_allocator.alloc(arg).expect("Ran out of registers"); arg_reg_allocs.push(self.register_allocator.alloc_reg(ARG_REGS[i]).expect("Ran out of registers"));
let arg_reg = arg_alloc.reg; let arg_alloc = load_variable(arg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
if !arg_alloc.is_reused { self.instrs.push(MoveInstr::new_uncond(ARG_REGS[i], RegisterOrImm::Reg(arg_alloc.reg)));
let arg_stack_offset = var_index_to_stack_offset.get(&arg.index).expect("Variable not declared");
self.instrs.push(LoadInstr::new_stack(arg_reg, *arg_stack_offset as i32));
}
self.instrs.push(MoveInstr::new_uncond(ARG_REGS[i], RegisterOrImm::Reg(arg_reg)));
} }
self.instrs.push(BlInstr::new(func.name.clone())); self.instrs.push(BlInstr::new(func.name.clone()));
if let Some(ret) = ret { if let Some(ret) = ret {
let ret_alloc = self.register_allocator.alloc(ret).expect("Ran out of registers"); save_variable(ret, REG_R0, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
let ret_reg = ret_alloc.reg;
self.instrs.push(MoveInstr::new_uncond(ret_reg, RegisterOrImm::Reg(REG_R0)));
} }
@@ -132,72 +225,47 @@ impl Generator {
let dest_register = dest_alloc.reg; let dest_register = dest_alloc.reg;
match src { match src {
MoveRValue::Var(variable) => { MoveRValue::Var(variable) => {
if !dest_alloc.is_reused { let src_alloc = load_variable(variable, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
let var_stack_offset = var_index_to_stack_offset.get(&variable.index).expect("Variable not found"); self.instrs.push(MoveInstr::new_uncond(dest_register, RegisterOrImm::Reg(src_alloc.reg)));
self.instrs.push(LoadInstr::new_stack(dest_register, *var_stack_offset as i32));
}
}, },
MoveRValue::ConstInt(literal_int) => self.instrs.push(MoveInstr::new_uncond(dest_register, RegisterOrImm::Imm(literal_int))), MoveRValue::ConstInt(literal_int) => self.instrs.push(MoveInstr::new_uncond(dest_register, RegisterOrImm::Imm(literal_int))),
}; };
match dest.var_type { save_variable(dest, dest_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
VariableType::Global => {
let address_reg = self.register_allocator.alloc_any().expect("Ran out of registers");
self.instrs.push(LoadPseudoInstr::new(address_reg, format!("global_var_{}", dest.index)));
self.instrs.push(StoreInstr::new(dest_register, address_reg, None));
},
VariableType::ParamTemp => {
todo!()
},
_ => {
let offset = *var_index_to_stack_offset.get(&dest.index).expect("Variable not declared");
self.instrs.push(StoreInstr::new_stack(dest_register, offset as i32));
}
}
} }
fn emit_binary(&mut self, dest: Variable, left: Variable, op: IRBinaryOp, right: Variable, var_index_to_stack_offset: &BTreeMap<usize, usize>) { fn emit_binary(&mut self, dest: Variable, left: Variable, op: IRBinaryOp, right: Variable, var_index_to_stack_offset: &BTreeMap<usize, usize>) {
let left_alloc = self.register_allocator.alloc(left).expect("Ran out of registers");
let right_alloc = self.register_allocator.alloc(right).expect("Ran out of registers");
let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers"); let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers");
let left_reg = left_alloc.reg;
let right_reg = right_alloc.reg;
let dest_reg = dest_alloc.reg; let dest_reg = dest_alloc.reg;
if !left_alloc.is_reused { // should consider left == right
let left_offset = var_index_to_stack_offset.get(&left.index).expect("Variable not declared"); let left_alloc = load_variable(left, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
self.instrs.push(LoadInstr::new_stack(left_reg, *left_offset as i32)); let (_right_alloc, right_reg) = if left != right {
let right_alloc = load_variable(right, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
} let right_reg = right_alloc.reg;
if !right_alloc.is_reused { (Some(right_alloc), right_reg)
let right_offset = var_index_to_stack_offset.get(&right.index).expect("Variable not declared"); } else {
self.instrs.push(LoadInstr::new_stack(right_reg, *right_offset as i32)); (None, left_alloc.reg)
} };
match op { match op {
IRBinaryOp::Add => { IRBinaryOp::Add => {
self.instrs.push(AddInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(AddInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg)));
}, },
IRBinaryOp::Sub => { IRBinaryOp::Sub => {
self.instrs.push(SubInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(SubInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg)));
}, },
IRBinaryOp::Mul => { IRBinaryOp::Mul => {
self.instrs.push(MulInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(MulInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg)));
}, },
IRBinaryOp::Div => { IRBinaryOp::Div => {
self.instrs.push(SDivInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(SDivInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg)));
}, },
IRBinaryOp::Mod => { IRBinaryOp::Mod => {
let temp_reg = self.register_allocator.alloc_any().expect("Ran out of registers"); let temp_alloc = self.register_allocator.alloc_any().expect("Ran out of registers");
self.instrs.push(SDivInstr::new(temp_reg, left_reg, RegisterOrImm::Reg(right_reg))); let temp_reg = temp_alloc.reg;
self.instrs.push(SDivInstr::new(temp_reg, left_alloc.reg, RegisterOrImm::Reg(right_reg)));
self.instrs.push(MulInstr::new(temp_reg, temp_reg, RegisterOrImm::Reg(right_reg))); self.instrs.push(MulInstr::new(temp_reg, temp_reg, RegisterOrImm::Reg(right_reg)));
self.instrs.push(SubInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(temp_reg))); self.instrs.push(SubInstr::new(dest_reg, left_alloc.reg, RegisterOrImm::Reg(temp_reg)));
}, },
IRBinaryOp::Le => todo!(),
IRBinaryOp::Lt => todo!(),
IRBinaryOp::Gt => todo!(),
IRBinaryOp::Ge => todo!(),
IRBinaryOp::Ne => todo!(),
IRBinaryOp::Eq => todo!(),
} }
let dest_stack_offset = var_index_to_stack_offset.get(&dest.index).expect("Variable not declared"); save_variable(dest, dest_alloc.reg, &mut self.register_allocator, var_index_to_stack_offset, &mut self.instrs);
self.instrs.push(StoreInstr::new_stack(dest_reg, *dest_stack_offset as i32));
} }
} }
+8
View File
@@ -74,4 +74,12 @@ mod tests {
test_case("0-3,14-25"); test_case("0-3,14-25");
// test_case("0-3,14-25"); // test_case("0-3,14-25");
} }
#[test]
fn test_if_while() {
test_case("26-32,34-41,46-51,57");
}
#[test]
fn test_func() {
test_case("12-13,58-60");
}
} }
+51 -5
View File
@@ -45,6 +45,9 @@ pub const REGISTERS: &[Register] = &[
pub const REGISTERS_CAN_ALLOC: &[Register] = &[ pub const REGISTERS_CAN_ALLOC: &[Register] = &[
REG_R0, REG_R1, REG_R2, REG_R3, REG_R4, REG_R5, REG_R6, REG_R7, REG_R8, REG_R9, REG_R10, REG_R12 REG_R0, REG_R1, REG_R2, REG_R3, REG_R4, REG_R5, REG_R6, REG_R7, REG_R8, REG_R9, REG_R10, REG_R12
]; ];
pub const REGISTERS_RESERVED: &[Register] = &[
REG_R0, REG_R1, REG_R2, REG_R3
];
pub struct RegisterAlloc { pub struct RegisterAlloc {
allocator: Weak<RefCell<RegisterAllocatorInner>>, allocator: Weak<RefCell<RegisterAllocatorInner>>,
pub reg: Register, pub reg: Register,
@@ -102,13 +105,26 @@ impl RegisterAllocator {
})), })),
} }
} }
fn debug_use(&self) {
let inner = self.inner.borrow();
for (&reg, use_kind) in inner.register_map.iter() {
match use_kind {
RegisterUseKind::Designated => println!("{}: Designated", reg),
RegisterUseKind::UsedByVariable(var) => println!("{}: UsedByVariable({})", reg, var),
RegisterUseKind::AllocatedToVariable(var) => println!("{}: AllocatedToVariable({})", reg, var),
RegisterUseKind::Free => println!("{}: Free", reg),
}
}
}
pub fn alloc(&mut self, var: Variable) -> Option<RegisterAlloc> { pub fn alloc(&mut self, var: Variable) -> Option<RegisterAlloc> {
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.borrow_mut();
if let Some(&reg) = inner.variable_to_register.get(&var) { if let Some(&reg) = inner.variable_to_register.get(&var) {
// Variable already has a register allocated // Variable already has a register allocated
let use_kind = inner.register_map.get_mut(&reg).expect("Inconsistent state: variable has a register but it's not in the register map"); let use_kind = inner.register_map.get_mut(&reg).expect("Inconsistent state: variable has a register but it's not in the register map");
assert!(matches!(use_kind, RegisterUseKind::UsedByVariable(v) | RegisterUseKind::AllocatedToVariable(v) if *v == var)); assert!(matches!(use_kind, RegisterUseKind::UsedByVariable(v) | RegisterUseKind::AllocatedToVariable(v) if *v == var));
if matches!(use_kind, RegisterUseKind::UsedByVariable(_)) {
panic!("variable already actively borrowed");
}
*use_kind = RegisterUseKind::UsedByVariable(var); *use_kind = RegisterUseKind::UsedByVariable(var);
return Some(RegisterAlloc { return Some(RegisterAlloc {
allocator: Rc::downgrade(&self.inner), allocator: Rc::downgrade(&self.inner),
@@ -119,6 +135,9 @@ impl RegisterAllocator {
// Find a free register // Find a free register
for (&reg, use_kind) in inner.register_map.iter_mut() { for (&reg, use_kind) in inner.register_map.iter_mut() {
// Find free register first // Find free register first
if REGISTERS_RESERVED.contains(&reg) {
continue;
}
if let RegisterUseKind::Free = use_kind { if let RegisterUseKind::Free = use_kind {
*use_kind = RegisterUseKind::UsedByVariable(var); *use_kind = RegisterUseKind::UsedByVariable(var);
inner.variable_to_register.insert(var, reg); inner.variable_to_register.insert(var, reg);
@@ -132,6 +151,9 @@ impl RegisterAllocator {
let RegisterAllocatorInner{register_map, variable_to_register} = &mut *inner; let RegisterAllocatorInner{register_map, variable_to_register} = &mut *inner;
for (&reg, use_kind) in register_map.iter_mut() { for (&reg, use_kind) in register_map.iter_mut() {
// Find allocated register then // Find allocated register then
if REGISTERS_RESERVED.contains(&reg) {
continue;
}
if let RegisterUseKind::AllocatedToVariable(ori_var) = use_kind { if let RegisterUseKind::AllocatedToVariable(ori_var) = use_kind {
assert!(variable_to_register.remove(&ori_var).is_some()); assert!(variable_to_register.remove(&ori_var).is_some());
*use_kind = RegisterUseKind::UsedByVariable(var); *use_kind = RegisterUseKind::UsedByVariable(var);
@@ -143,6 +165,8 @@ impl RegisterAllocator {
}); });
} }
} }
std::mem::drop(inner);
self.debug_use();
// No free register available // No free register available
None None
} }
@@ -161,6 +185,10 @@ impl RegisterAllocator {
}); });
}, },
RegisterUseKind::UsedByVariable(_var) => { RegisterUseKind::UsedByVariable(_var) => {
std::mem::drop(inner);
println!("{}", reg);
self.debug_use();
return None; return None;
}, },
RegisterUseKind::AllocatedToVariable(var) => { RegisterUseKind::AllocatedToVariable(var) => {
@@ -173,30 +201,48 @@ impl RegisterAllocator {
}); });
}, },
RegisterUseKind::Designated => { RegisterUseKind::Designated => {
std::mem::drop(inner);
println!("{}", reg);
self.debug_use();
return None; return None;
}, },
} }
} }
pub fn alloc_any(&mut self) -> Option<Register> { pub fn alloc_any(&mut self) -> Option<RegisterAlloc> {
let mut inner = self.inner.borrow_mut(); let mut inner = self.inner.borrow_mut();
for (&reg, use_kind) in inner.register_map.iter_mut() { for (&reg, use_kind) in inner.register_map.iter_mut() {
if REGISTERS_RESERVED.contains(&reg) {
continue;
}
if let RegisterUseKind::Free = use_kind { if let RegisterUseKind::Free = use_kind {
*use_kind = RegisterUseKind::Designated; *use_kind = RegisterUseKind::Designated;
return Some(reg); return Some(RegisterAlloc {
allocator: Rc::downgrade(&self.inner),
reg,
is_reused: false,
});
} }
} }
let RegisterAllocatorInner{register_map, variable_to_register} = &mut *inner; let RegisterAllocatorInner{register_map, variable_to_register} = &mut *inner;
for (&reg, use_kind) in register_map.iter_mut() { for (&reg, use_kind) in register_map.iter_mut() {
if REGISTERS_RESERVED.contains(&reg) {
continue;
}
if let RegisterUseKind::AllocatedToVariable(ori_var) = use_kind { if let RegisterUseKind::AllocatedToVariable(ori_var) = use_kind {
variable_to_register.remove(&ori_var); variable_to_register.remove(&ori_var);
*use_kind = RegisterUseKind::Designated; *use_kind = RegisterUseKind::Designated;
return Some(reg); return Some(RegisterAlloc {
allocator: Rc::downgrade(&self.inner),
reg,
is_reused: false,
});
} }
} }
std::mem::drop(inner);
self.debug_use();
None None
} }
} }
+1 -1
View File
@@ -28,7 +28,7 @@ pub enum ParseError {
UnexpectedToken(TokenValue, &'static str), UnexpectedToken(TokenValue, &'static str),
#[error("cannot combine with previous {}", .0)] #[error("cannot combine with previous {}", .0)]
CantCombineWith(TokenValue), CantCombineWith(TokenValue),
#[error("expect {0}")] #[error("expect {0} after")]
ExpectButEof(&'static str), ExpectButEof(&'static str),
} }
#[derive(Debug, Clone, PartialEq, Eq, Error)] #[derive(Debug, Clone, PartialEq, Eq, Error)]
+36 -16
View File
@@ -17,7 +17,7 @@ pub struct Lexer {
const WHITESPACE_CHARS: &[char] = &[' ', '\t', '\n', '\r']; const WHITESPACE_CHARS: &[char] = &[' ', '\t', '\n', '\r'];
const DELIMITER_CHARS: &[char] = &[ const DELIMITER_CHARS: &[char] = &[
'+', '-', '*', '/', '%', '=', '!', '<', '>', '(', ')', ',', ';' '+', '-', '*', '/', '%', '=', '!', '<', '>', '(', ')', ',', ';', '{', '|', '&'
]; ];
struct Cursor { struct Cursor {
chars: Vec<char>, chars: Vec<char>,
@@ -185,22 +185,10 @@ fn parse_litint(
) -> Result<TokenValue, LexParseError> { ) -> Result<TokenValue, LexParseError> {
let mut c1 = str_iter.peek().ok_or(LexParseError::NotMatched)?; let mut c1 = str_iter.peek().ok_or(LexParseError::NotMatched)?;
// c1 is the peek value from here // c1 is the peek value from here
let mut sign_base: i64 = 1;
let mut base: i64 = 10; let mut base: i64 = 10;
if !(c1.is_ascii_digit() || c1 == '-') { if !(c1.is_ascii_digit()) {
return Err(LexParseError::NotMatched); return Err(LexParseError::NotMatched);
} }
if c1 == '-' {
sign_base = -1;
str_iter.advance(1);
c1 = str_iter.peek().ok_or(LexParseError::NotMatched)?;
if !c1.is_ascii_digit() {
// only a minus sign, not a number
// back one so cursor still points to the minus sign
str_iter.back(1);
return Err(LexParseError::NotMatched);
}
}
let mut number = 0i64; let mut number = 0i64;
let mut has_digits = false; let mut has_digits = false;
if c1 == '0' { if c1 == '0' {
@@ -248,7 +236,6 @@ fn parse_litint(
// No valid digits found, add a diagnostic // No valid digits found, add a diagnostic
return Err(LexParseError::InvalidInMatch(LexingError::InvalidIntLiteral)); return Err(LexParseError::InvalidInMatch(LexingError::InvalidIntLiteral));
} }
number *= sign_base;
Ok(TokenValue::IntLit(number)) Ok(TokenValue::IntLit(number))
} }
@@ -293,6 +280,7 @@ fn parse_puncuation(
if let Some('=') = str_iter.peek() { if let Some('=') = str_iter.peek() {
TokenValue::NotEqual TokenValue::NotEqual
} else { } else {
str_iter.back(1);
TokenValue::Not TokenValue::Not
} }
}, },
@@ -301,7 +289,24 @@ fn parse_puncuation(
',' => TokenValue::Comma, ',' => TokenValue::Comma,
';' => TokenValue::Semicolon, ';' => TokenValue::Semicolon,
'|' => {
str_iter.advance(1);
if let Some('|') = str_iter.peek() {
TokenValue::Or
} else {
// unrecognized token starting with '|'
return Err(LexParseError::InvalidInMatch(LexingError::UnrecognizedToken("|".to_string())));
}
},
'&' => {
str_iter.advance(1);
if let Some('&') = str_iter.peek() {
TokenValue::And
} else {
// unrecognized token starting with '&'
return Err(LexParseError::InvalidInMatch(LexingError::UnrecognizedToken("&".to_string())));
}
},
_ => return Err(LexParseError::NotMatched), _ => return Err(LexParseError::NotMatched),
}; };
str_iter.advance(1); str_iter.advance(1);
@@ -330,6 +335,21 @@ fn parse_ident(
if name.eq("return") { if name.eq("return") {
return Ok(TokenValue::Return); return Ok(TokenValue::Return);
} }
if name.eq("if") {
return Ok(TokenValue::If);
}
if name.eq("else") {
return Ok(TokenValue::Else);
}
if name.eq("while") {
return Ok(TokenValue::While);
}
if name.eq("break") {
return Ok(TokenValue::Break);
}
if name.eq("continue") {
return Ok(TokenValue::Continue);
}
if let Some(type_ident) = TypeIdent::from_str(&name).ok() { if let Some(type_ident) = TypeIdent::from_str(&name).ok() {
return Ok(TokenValue::TypeIdent(type_ident)); return Ok(TokenValue::TypeIdent(type_ident));
} }
+532 -201
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -17,7 +17,7 @@ pub enum TokenValue {
TypeIdent(TypeIdent), TypeIdent(TypeIdent),
Plus, Minus, Star, Slash, Percent, Plus, Minus, Star, Slash, Percent,
Equal, DoubleEqual, Not, NotEqual, Less, LessEqual, Greater, GreaterEqual, Equal, DoubleEqual, Not, NotEqual, Less, LessEqual, Greater, GreaterEqual, And, Or,
LParen, RParen, LParen, RParen,
LBrace, RBrace, LBrace, RBrace,
@@ -58,6 +58,8 @@ impl std::fmt::Display for TokenValue {
TokenValue::Equal => write!(f, "`=`"), TokenValue::Equal => write!(f, "`=`"),
TokenValue::DoubleEqual => write!(f, "`==`"), TokenValue::DoubleEqual => write!(f, "`==`"),
TokenValue::Not => write!(f, "`!`"), TokenValue::Not => write!(f, "`!`"),
TokenValue::And => write!(f, "`&&`"),
TokenValue::Or => write!(f, "`||`"),
TokenValue::NotEqual => write!(f, "`!=`"), TokenValue::NotEqual => write!(f, "`!=`"),
TokenValue::Less => write!(f, "`<`"), TokenValue::Less => write!(f, "`<`"),
TokenValue::LessEqual => write!(f, "`<=`"), TokenValue::LessEqual => write!(f, "`<=`"),
+8
View File
@@ -24,4 +24,12 @@ pub enum IRError {
TypeMismatch(IRType, IRType), TypeMismatch(IRType, IRType),
#[error("invalid assignment target")] #[error("invalid assignment target")]
InvalidAssignmentTarget, InvalidAssignmentTarget,
#[error("break statement outside of loop")]
BreakOutsideLoop,
#[error("continue statement outside of loop")]
ContinueOutsideLoop,
#[error("invalid parameter type: {0}")]
InvalidParameterType(IRType),
#[error("return expression on void function")]
ReturnExpressionOnVoidFunction,
} }
+469 -70
View File
@@ -1,12 +1,22 @@
use std::{collections::{BTreeMap, BTreeSet}, result, vec}; use std::{collections::{BTreeMap, BTreeSet}, vec};
use crate::{ast::types::{BlockStmt, CompileUnit, Expr, FuncDeclStmt, GlobalDeclStmt, ReturnStmt, Statement, VarDeclStmt}, diagnostic::Diagnositics, ir::{err::IRError, types::{BinaryOp, Function, IRInstr, IRType, MoveRValue, Variable, VariableType}}};
use crate::{ast::types::{BlockStmt, BreakStmt, CompileUnit, ContinueStmt, Expr, ExprValue, FuncDeclStmt, GlobalDeclStmt, IfElseBranch, IfStmt, ReturnStmt, Statement, VarDeclStmt, WhileStmt}, diagnostic::Diagnositics, ir::{err::IRError, types::{BinaryOp, CmpOp, Function, IRInstr, IRType, MoveRValue, UnaryOp, Variable, VariableOrIntLit, VariableType}}};
use crate::ast::types::BinaryOp as AstBinaryOp;
use crate::ast::types::UnaryOp as AstUnaryOp;
pub struct Generator { pub struct Generator {
var_manager: VariableManager, var_manager: VariableManager,
function_map: BTreeMap<String, Function>, function_map: BTreeMap<String, Function>,
current_func_return_type: Option<IRType>, current_func_return_type: Option<IRType>,
diagnostic: Diagnositics, diagnostic: Diagnositics,
current_exit_label: Vec<Option<(usize, usize)>>, // true exit, false exit
// About exit label passing:
// for non-logical parent expr, current_exit_label is None
// this means we need to create label for logical child expr, and calculate the value
// for logical parent expr, we can directly use the label for logical child expr, and no need to calculate
// if child expr isn't logical, we need to do cmp to decide which label to goto
while_exit_label: Vec<(usize, usize)>, // continue exit, break exit
func_exit: Option<(usize, Option<Variable>)>, // (label, return_var)
label_counter: usize
} }
impl Generator { impl Generator {
@@ -17,14 +27,27 @@ impl Generator {
parameter_types: vec![IRType::I32], parameter_types: vec![IRType::I32],
return_type: IRType::Void, return_type: IRType::Void,
}); });
function_map.insert("getint".to_string(), Function {
name: "getint".to_string(),
parameter_types: vec![],
return_type: IRType::I32,
});
Self { Self {
var_manager: VariableManager::new(), var_manager: VariableManager::new(),
current_func_return_type: None, current_func_return_type: None,
diagnostic: Diagnositics::new(), diagnostic: Diagnositics::new(),
function_map, function_map,
current_exit_label: vec![],
while_exit_label: vec![],
func_exit: None,
label_counter: 0,
} }
} }
fn request_label(&mut self) -> usize {
let label = self.label_counter;
self.label_counter += 1;
label
}
pub fn emit(&mut self, compile_unit: CompileUnit) -> Vec<IRInstr> { pub fn emit(&mut self, compile_unit: CompileUnit) -> Vec<IRInstr> {
self.generate_compile_unit(compile_unit) self.generate_compile_unit(compile_unit)
} }
@@ -51,12 +74,12 @@ impl Generator {
let mut instrs = vec![]; let mut instrs = vec![];
let var_type = if is_global { VariableType::Global } else { VariableType::Local }; let var_type = if is_global { VariableType::Global } else { VariableType::Local };
for value in var_decl.values { for value in var_decl.values {
match self.var_manager.declare_variable(&value.name, var_type, value.var_type.into()) { match self.var_manager.declare_variable(&value.name, var_type, var_decl.data_type.into()) {
Ok(var) => { Ok(var) => {
if is_global { instrs.push(IRInstr::Declare(var)); } if is_global { instrs.push(IRInstr::Declare(var)); }
} }
Err(e) => { Err(e) => {
self.diagnostic.add_from_ir_error(e, value.span); self.diagnostic.add_from_ir_error(e, value.name_span);
} }
} }
} }
@@ -66,7 +89,7 @@ impl Generator {
fn generate_func_decl(&mut self, func_decl: FuncDeclStmt) -> Vec<IRInstr> { fn generate_func_decl(&mut self, func_decl: FuncDeclStmt) -> Vec<IRInstr> {
if self.function_map.contains_key(&func_decl.name) { if self.function_map.contains_key(&func_decl.name) {
self.diagnostic.add_from_ir_error(IRError::FunctionHasBeenDefined(func_decl.name.clone()), func_decl.span); self.diagnostic.add_from_ir_error(IRError::FunctionHasBeenDefined(func_decl.name.clone()), func_decl.name_span);
return vec![]; return vec![];
} }
self.current_func_return_type = Some(func_decl.return_type.into()); self.current_func_return_type = Some(func_decl.return_type.into());
@@ -75,7 +98,7 @@ impl Generator {
match self.var_manager.declare_variable(&param.name, VariableType::Local, param.param_type.into()) { match self.var_manager.declare_variable(&param.name, VariableType::Local, param.param_type.into()) {
Ok(var) => Ok(var), Ok(var) => Ok(var),
Err(e) => { Err(e) => {
self.diagnostic.add_from_ir_error(e, param.span); self.diagnostic.add_from_ir_error(e, param.name_span);
Err(()) Err(())
} }
} }
@@ -83,21 +106,33 @@ impl Generator {
Ok(p) => p, Ok(p) => p,
Err(()) => return vec![], Err(()) => return vec![],
}; };
let temp_parameters = parameters.iter().map(|param| self.var_manager.declare_param_temp(param.data_type)).collect::<Vec<_>>(); let temp_parameters = parameters.iter().enumerate()
.map(|(i, param)| self.var_manager.declare_param_temp(param.data_type, i))
.collect::<Vec<_>>();
let mut body_instrs = vec![]; let mut body_instrs = vec![];
self.func_exit = Some((self.request_label(), {
let ret_type = func_decl.return_type.into();
if ret_type != IRType::Void {
Some(self.var_manager.declare_unamed_local(ret_type))
} else {
None
}
}));
let block_instrs = self.generate_block_stmt(func_decl.body); let block_instrs = self.generate_block_stmt(func_decl.body);
for var in self.var_manager.get_cur_func_variables() { for var in self.var_manager.get_cur_func_variables() {
if matches!(var.var_type, VariableType::ParamTemp) { if matches!(var.var_type, VariableType::ParamTemp(_)) {
continue; continue;
} }
body_instrs.push(IRInstr::Declare(var)); body_instrs.push(IRInstr::Declare(var));
} }
body_instrs.push(IRInstr::Entry); body_instrs.push(IRInstr::Entry);
parameters.iter().zip(temp_parameters.iter()).for_each(|(param, temp_param)| { parameters.iter().zip(temp_parameters.iter()).for_each(|(param, temp_param)| {
body_instrs.push(IRInstr::Move(*temp_param, MoveRValue::Var(*param))); body_instrs.push(IRInstr::Move(*param, MoveRValue::Var(*temp_param)));
}); });
body_instrs.extend(block_instrs); body_instrs.extend(block_instrs);
let func_exit = self.func_exit.take().unwrap();
body_instrs.push(IRInstr::Label(func_exit.0));
body_instrs.push(IRInstr::Exit(func_exit.1));
self.var_manager.exit_scope(); self.var_manager.exit_scope();
self.current_func_return_type = None; self.current_func_return_type = None;
self.var_manager.clear_local_counter(); self.var_manager.clear_local_counter();
@@ -107,7 +142,7 @@ impl Generator {
return_type: func_decl.return_type.into(), return_type: func_decl.return_type.into(),
}; };
self.function_map.insert(func.name.clone(), func.clone()); self.function_map.insert(func.name.clone(), func.clone());
vec![IRInstr::DefineFunc(func, parameters, body_instrs)] vec![IRInstr::DefineFunc(func, temp_parameters, body_instrs)]
} }
fn generate_block_stmt(&mut self, block_stmt: BlockStmt) -> Vec<IRInstr> { fn generate_block_stmt(&mut self, block_stmt: BlockStmt) -> Vec<IRInstr> {
let mut instrs = vec![]; let mut instrs = vec![];
@@ -121,13 +156,28 @@ impl Generator {
use Statement::*; use Statement::*;
let instrs = match stmt { let instrs = match stmt {
Return(return_stmt) => self.generate_return_stmt(return_stmt), Return(return_stmt) => self.generate_return_stmt(return_stmt),
If(if_stmt) => self.generate_if_stmt(if_stmt),
While(while_stmt) => self.generate_while_stmt(while_stmt),
Break(break_stmt) => self.generate_break_stmt(break_stmt),
Continue(continue_stmt) => self.generate_continue_stmt(continue_stmt),
Block(block_stmt) => { Block(block_stmt) => {
self.var_manager.enter_scope(); self.var_manager.enter_scope();
let block_instrs = self.generate_block_stmt(block_stmt); let block_instrs = self.generate_block_stmt(block_stmt);
self.var_manager.exit_scope(); self.var_manager.exit_scope();
block_instrs block_instrs
}, },
Expr(expr) => self.generate_expr(expr).0, Expr(expr) => {
self.current_exit_label.push(None);
let (instrs, _) = match self.generate_expr(expr) {
Some(res) => res,
None => {
self.current_exit_label.pop();
return vec![];
}
};
self.current_exit_label.pop();
instrs
},
VarDecl(var_decl) => self.generate_var_decl(var_decl, false), VarDecl(var_decl) => self.generate_var_decl(var_decl, false),
}; };
instrs instrs
@@ -135,22 +185,160 @@ impl Generator {
fn generate_return_stmt(&mut self, return_stmt: ReturnStmt) -> Vec<IRInstr> { fn generate_return_stmt(&mut self, return_stmt: ReturnStmt) -> Vec<IRInstr> {
let mut instrs = vec![]; let mut instrs = vec![];
let func_exit = self.func_exit.unwrap();
match return_stmt.value { match return_stmt.value {
Some(expr) => { Some(expr) => {
let (value_instrs, value_var) = self.generate_expr(expr); if func_exit.1.is_none() {
// shouldn't return value but return stmt has expr;
self.diagnostic.add_from_ir_error(IRError::ReturnExpressionOnVoidFunction, return_stmt.span);
return vec![];
}
self.current_exit_label.push(None);
let (value_instrs, value_var) = match self.generate_expr(expr) {
Some(res) => res,
None => {
self.current_exit_label.pop();
return vec![];
}
};
self.current_exit_label.pop();
if value_var.is_none() {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), return_stmt.span);
self.current_exit_label.pop();
return vec![];
}
instrs.extend(value_instrs); instrs.extend(value_instrs);
if value_var.is_some() { instrs.push(IRInstr::Move(func_exit.1.unwrap(), MoveRValue::Var(value_var.unwrap())));
instrs.push(IRInstr::Exit(value_var)); // if value_var.is_none() {
// self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), return_stmt.span);
// return vec![];
// }
// let value_var = value_var.unwrap();
} }
None => {
if func_exit.1.is_some() {
// should return value but return stmt has no expr;
self.diagnostic.add_from_ir_error(IRError::TypeMismatch(self.current_func_return_type.unwrap(), IRType::Void), return_stmt.span);
} }
None => instrs.push(IRInstr::Exit(None)), },
} }
instrs.push(IRInstr::Goto(func_exit.0));
instrs instrs
} }
fn generate_while_stmt(&mut self, while_stmt: WhileStmt) -> Vec<IRInstr> {
let mut instrs = vec![];
let cond_label = self.request_label();
let body_label = self.request_label();
let exit_label = self.request_label();
instrs.push(IRInstr::Label(cond_label));
self.current_exit_label.push(Some((body_label, exit_label)));
let while_cond_span = while_stmt.condition.span;
let (cond_instrs, cond_var) = match self.generate_expr(while_stmt.condition) {
Some(res) => res,
None => {
self.current_exit_label.pop();
return vec![];
}
};
self.current_exit_label.pop();
if cond_var.is_none() {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), while_cond_span);
return vec![];
}
instrs.extend(cond_instrs);
instrs.push(IRInstr::Label(body_label));
self.while_exit_label.push((cond_label, exit_label));
instrs.extend(self.generate_block_stmt(while_stmt.body));
self.while_exit_label.pop();
instrs.push(IRInstr::Goto(cond_label));
instrs.push(IRInstr::Label(exit_label));
instrs
}
fn generate_if_stmt(&mut self, if_stmt: IfStmt) -> Vec<IRInstr> {
let mut instrs = vec![];
let then_label = self.request_label();
let exit_label = self.request_label();
let mut labels = vec![then_label];
for _ in &if_stmt.ifelse_branch {
let else_if_label = self.request_label();
let else_if_body_label = self.request_label();
fn generate_expr(&mut self, expr: Expr) -> (Vec<IRInstr>, Option<Variable>) { labels.push(else_if_label);
use crate::ast::types::ExprValue; labels.push(else_if_body_label);
match expr.value { }
if if_stmt.else_branch.is_some() {
let else_label = self.request_label();
labels.push(else_label);
}
labels.push(exit_label);
// now generate if expr, true exit to labels[0], false exit to labels[1]
self.current_exit_label.push(Some((labels[0], labels[1])));
let cond_span = if_stmt.condition.span;
let (cond_instrs, cond_var) = match self.generate_expr(if_stmt.condition) {
Some(res) => res,
None => {
self.current_exit_label.pop();
return vec![];
}
};
self.current_exit_label.pop();
if cond_var.is_none() {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), cond_span);
return vec![];
}
instrs.extend(cond_instrs);
instrs.push(IRInstr::Label(labels[0]));
instrs.extend(self.generate_block_stmt(if_stmt.then_branch));
instrs.push(IRInstr::Goto(exit_label));
for (i, else_if_branch) in if_stmt.ifelse_branch.into_iter().enumerate() {
let IfElseBranch { condition: else_if_cond, then_branch: else_if_block } = else_if_branch;
instrs.push(IRInstr::Label(labels[i * 2 + 1]));
self.current_exit_label.push(Some((labels[i * 2 + 2], labels[i * 2 + 3])));
let else_if_cond_span = else_if_cond.span;
let (else_if_cond_instrs, else_if_cond_var) = match self.generate_expr(else_if_cond) {
Some(res) => res,
None => {
self.current_exit_label.pop();
continue;
}
};
self.current_exit_label.pop();
if else_if_cond_var.is_none() {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), else_if_cond_span);
return vec![];
}
instrs.extend(else_if_cond_instrs);
instrs.push(IRInstr::Label(labels[i * 2 + 2]));
instrs.extend(self.generate_block_stmt(else_if_block));
instrs.push(IRInstr::Goto(exit_label));
}
if let Some(else_block) = if_stmt.else_branch {
instrs.push(IRInstr::Label(labels[labels.len() - 2]));
instrs.extend(self.generate_block_stmt(else_block));
instrs.push(IRInstr::Goto(exit_label));
}
instrs.push(IRInstr::Label(exit_label));
instrs
}
fn generate_continue_stmt(&mut self, stmt: ContinueStmt) -> Vec<IRInstr> {
if let Some((continue_label, _)) = self.while_exit_label.last() {
vec![IRInstr::Goto(*continue_label)]
} else {
self.diagnostic.add_from_ir_error(IRError::ContinueOutsideLoop, stmt.span);
vec![]
}
}
fn generate_break_stmt(&mut self, stmt: BreakStmt) -> Vec<IRInstr> {
if let Some((_, break_label)) = self.while_exit_label.last() {
vec![IRInstr::Goto(*break_label)]
} else {
self.diagnostic.add_from_ir_error(IRError::BreakOutsideLoop, stmt.span);
vec![]
}
}
fn generate_expr(&mut self, expr: Expr) -> Option<(Vec<IRInstr>, Option<Variable>)> {
// there may be some expr that doesn't produce value, like void func call
let (mut instrs, var) = match expr.value {
ExprValue::IntLit(i) => { ExprValue::IntLit(i) => {
// TODO: convert check // TODO: convert check
let var = self.var_manager.declare_temp(IRType::I32); let var = self.var_manager.declare_temp(IRType::I32);
@@ -161,7 +349,7 @@ impl Generator {
(vec![], Some(var)) (vec![], Some(var))
} else { } else {
self.diagnostic.add_from_ir_error(IRError::VariableNotFound(name.clone()), expr.span); self.diagnostic.add_from_ir_error(IRError::VariableNotFound(name.clone()), expr.span);
(vec![], None) return None;
} }
}, },
ExprValue::Assign { lvalue, rvalue } => { ExprValue::Assign { lvalue, rvalue } => {
@@ -171,69 +359,251 @@ impl Generator {
Some(var) => var, Some(var) => var,
None => { None => {
self.diagnostic.add_from_ir_error(IRError::VariableNotFound(name.clone()), lvalue.span); self.diagnostic.add_from_ir_error(IRError::VariableNotFound(name.clone()), lvalue.span);
return (vec![], None); return None;
} }
}; };
let (mut instrs, rvalue_var) = self.generate_expr(*rvalue); self.current_exit_label.push(None);
let rvalue_span = rvalue.span;
let (mut instrs, rvalue_var) = self.generate_expr(*rvalue)?;
self.current_exit_label.pop();
// TODO: further check
// if var.data_type != rvalue_var.data_type {
// self.diagnostic.add_from_ir_error(IRError::TypeMismatch(var.data_type, rvalue_var.data_type), lvalue.span);
// return (vec![], None);
// }
if rvalue_var.is_none() { if rvalue_var.is_none() {
return (vec![], None); self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), rvalue_span);
return None;
} }
let rvalue_var = rvalue_var.unwrap(); instrs.push(IRInstr::Move(var, MoveRValue::Var(rvalue_var.unwrap())));
if var.data_type != rvalue_var.data_type {
self.diagnostic.add_from_ir_error(IRError::TypeMismatch(var.data_type, rvalue_var.data_type), lvalue.span);
return (vec![], None);
}
instrs.push(IRInstr::Move(var, MoveRValue::Var(rvalue_var)));
let temp_var = self.var_manager.declare_temp(var.data_type); let temp_var = self.var_manager.declare_temp(var.data_type);
instrs.push(IRInstr::Move(temp_var, MoveRValue::Var(var))); instrs.push(IRInstr::Move(temp_var, MoveRValue::Var(var)));
(instrs, Some(temp_var)) (instrs, Some(temp_var))
} else { } else {
self.diagnostic.add_from_ir_error(IRError::InvalidAssignmentTarget, lvalue.span); self.diagnostic.add_from_ir_error(IRError::InvalidAssignmentTarget, lvalue.span);
return (vec![], None); return None;
} }
}, },
ExprValue::UnaryOp { op, operand } => {
let mut parent_is_logical = true;
let exit_passdown = if matches!(op, AstUnaryOp::Not) {
Some(self.current_exit_label.last().cloned().flatten().map_or_else(|| {
parent_is_logical = false;
(self.request_label(), self.request_label())
}, |(true_exit, false_exit)| (false_exit, true_exit)))
} else {
None
};
// NOT:
// $x = cmp eq left_var, 0
// bc, $x, true_exit, false_exit
// false_exit: #only when parent isn't logical
// true_exit:
// $dest = $x
// +--------+-------------------------+-------------------------+----------------------------+
// | parent | (true_exit, false_exit) | (true_exit, false_exit) | None |
// | self | not | add/sub | not(true_exit, false_exit) |
// | child | (false_exit, true_exit) | None | (true_exit, false_exit) |
// +--------+-------------------------+-------------------------+----------------------------+
self.current_exit_label.push(exit_passdown);
let operand_span = operand.span;
let (mut instrs, operand_var) = self.generate_expr(*operand)?;
self.current_exit_label.pop();
if operand_var.is_none() {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), operand_span);
return None;
}
let operand_var = operand_var.unwrap();
let dest_var = match op {
AstUnaryOp::Add => {
let dest_var = self.var_manager.declare_temp(operand_var.data_type);
instrs.push(IRInstr::Move(dest_var, MoveRValue::Var(operand_var)));
dest_var
},
AstUnaryOp::Sub => {
let dest_var = self.var_manager.declare_temp(operand_var.data_type);
instrs.push(IRInstr::Unary(dest_var, UnaryOp::Neg, operand_var));
dest_var
},
AstUnaryOp::Not => {
let dest_var = self.var_manager.declare_unamed_local(operand_var.data_type);
// child will do the cmp
if !parent_is_logical {
let exit = exit_passdown.unwrap(); // (false_exit, true_exit) (consider `not`)
let final_exit = self.request_label();
instrs.push(IRInstr::Label(exit.1));
instrs.push(IRInstr::Move(dest_var, MoveRValue::ConstInt(1)));
instrs.push(IRInstr::Goto(final_exit));
instrs.push(IRInstr::Label(exit.0));
instrs.push(IRInstr::Move(dest_var, MoveRValue::ConstInt(0)));
instrs.push(IRInstr::Goto(final_exit));
instrs.push(IRInstr::Label(final_exit));
}
// return directly since it's logical expr
return Some((instrs, Some(dest_var)));
}
};
(instrs, Some(dest_var))
},
ExprValue::BinaryOp { lhs, op, rhs } => { ExprValue::BinaryOp { lhs, op, rhs } => {
let lhs_span = lhs.span; let lhs_span = lhs.span;
let rhs_span = rhs.span; let rhs_span = rhs.span;
let (mut instrs, left_var) = self.generate_expr(*lhs);
// +--------+-------------------------+-------------------------+-------------------------+
// | parent | (true_exit, false_exit) | (true_exit, false_exit) | (true_exit, false_exit) |
// | self | and(next) | or(next) | others |
// | lhs | (next, false_exit) | (true_exit, next) | None |
// | rhs | (true_exit, false_exit) | (next, false_exit) | None |
// +--------+-------------------------+-------------------------+-------------------------+
// +--------+----------------------------------+-------------------------------+--------+
// | parent | None | None | None |
// | self | and(true_exit, next, false_exit) | or(true_exit,next,false_exit) | others |
// | lhs | (next, false_exit) | (true_exit, next) | None |
// | rhs | (true_exit, false_exit) | (next, false_exit) | None |
// +--------+----------------------------------+-------------------------------+--------+
let mut parent_exit = None;
let exit = if op.is_logical() {
Some(self.current_exit_label.last().cloned().flatten().map_or(
(self.request_label(), self.request_label(), self.request_label()),
|(true_exit, false_exit)| {
parent_exit = Some((true_exit, false_exit));
(true_exit, self.request_label(), false_exit)
}
))
} else {
None
}; // (true_exit, next_label, false_exit)
let lhs_exit_passdown = exit.map(|(true_exit, next_label, false_exit)| match op {
AstBinaryOp::And => (next_label, false_exit),
AstBinaryOp::Or => (true_exit, next_label),
_ => unreachable!(),
});
let rhs_exit_passdown = exit.map(|(true_exit, _, false_exit)| match op {
AstBinaryOp::And => (true_exit, false_exit),
AstBinaryOp::Or => (true_exit, false_exit),
_ => unreachable!(),
});
self.current_exit_label.push(lhs_exit_passdown);
let (mut instrs, left_var) = self.generate_expr(*lhs)?;
self.current_exit_label.pop();
if left_var.is_none() { if left_var.is_none() {
return (vec![], None);
}
let left_var = left_var.unwrap();
let (right_instrs, right_var) = self.generate_expr(*rhs);
if right_var.is_none() {
return (vec![], None);
}
let right_var = right_var.unwrap();
instrs.extend(right_instrs);
let mut has_void = false;
if matches!(left_var.data_type, IRType::Void) {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), lhs_span); self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), lhs_span);
has_void = true; return None;
} }
if matches!(right_var.data_type, IRType::Void) { let mut left_var = left_var.unwrap();
self.current_exit_label.push(rhs_exit_passdown);
let (right_instrs, right_var) = self.generate_expr(*rhs)?;
self.current_exit_label.pop();
if right_var.is_none() {
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), rhs_span); self.diagnostic.add_from_ir_error(IRError::InvalidOperand(IRType::Void), rhs_span);
has_void = true; return None;
} }
if has_void { let mut right_var = right_var.unwrap();
return (vec![], None); // check implicit convert
} let convert_to;
if left_var.data_type != right_var.data_type { if op.is_logical() {
// we dont really care since we use cmp
convert_to = left_var.data_type;
} else {
if let Some(ty) = IRType::get_elevate_result(left_var.data_type, right_var.data_type) {
convert_to = ty;
} else {
self.diagnostic.add_from_ir_error(IRError::IncompatiableOperand(left_var.data_type, right_var.data_type), lhs_span); self.diagnostic.add_from_ir_error(IRError::IncompatiableOperand(left_var.data_type, right_var.data_type), lhs_span);
self.diagnostic.add_from_ir_error(IRError::InvalidOperand(left_var.data_type), rhs_span); self.diagnostic.add_from_ir_error(IRError::IncompatiableOperand(left_var.data_type, right_var.data_type), rhs_span);
return (vec![], None); return None;
}
}
// do implicit convert if needed
// TODO: further check
if !op.is_logical() && convert_to != left_var.data_type {
let temp_var = self.var_manager.declare_temp(convert_to);
instrs.push(IRInstr::Move(temp_var, MoveRValue::Var(left_var)));
left_var = temp_var;
} }
let result_type; let result_type;
match Into::<BinaryOp>::into(op) { match op {
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => { AstBinaryOp::Add | AstBinaryOp::Sub | AstBinaryOp::Mul | AstBinaryOp::Div | AstBinaryOp::Mod => {
result_type = left_var.data_type; result_type = left_var.data_type;
}, },
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => { AstBinaryOp::Equal | AstBinaryOp::NotEqual | AstBinaryOp::Less | AstBinaryOp::Greater | AstBinaryOp::LessEqual | AstBinaryOp::GreaterEqual
| AstBinaryOp::And | AstBinaryOp::Or => {
result_type = IRType::I1; result_type = IRType::I1;
} }
} }
let dest_var = self.var_manager.declare_temp(result_type); let dest_var = self.var_manager.declare_temp(result_type);
match op {
AstBinaryOp::And | AstBinaryOp::Or => {
instrs.push(IRInstr::Label(exit.unwrap().1));
},
_ => {}
}
// for logical operator, considering short-circuit
// AND:
// $x = cmp ne left_var, 0
// bc, $x, next, false_exit
// next:
// $x = cmp eq right_var, 0
// bc, $x, true_exit, false_exit
// false_exit: #only when parent isn't logical
// true_exit:
// OR:
// $x = cmp ne left_var, 0
// bc, $x, true_exit, next
// next:
// $x = cmp ne right_var, 0
// bc, $x, true_exit, false_exit
// false_exit: #only when parent isn't logical
// true_exit:
instrs.extend(right_instrs);
if !op.is_logical() && convert_to != right_var.data_type {
let temp_var = self.var_manager.declare_temp(convert_to);
instrs.push(IRInstr::Move(temp_var, MoveRValue::Var(right_var)));
right_var = temp_var;
}
if !op.is_logical() {
if let Some((true_exit, false_exit)) = parent_exit {
let final_exit = self.request_label();
instrs.push(IRInstr::Label(true_exit));
instrs.push(IRInstr::Move(dest_var, MoveRValue::ConstInt(1)));
instrs.push(IRInstr::Goto(final_exit));
instrs.push(IRInstr::Label(false_exit));
instrs.push(IRInstr::Move(dest_var, MoveRValue::ConstInt(0)));
instrs.push(IRInstr::Label(final_exit));
}
}
if op.is_logical() {
return Some((instrs, Some(dest_var)));
} else {
if op.is_cmp() {
instrs.push(IRInstr::Cmp(dest_var, VariableOrIntLit::Var(left_var), op.into(), VariableOrIntLit::Var(right_var)));
} else {
instrs.push(IRInstr::Binary(dest_var, left_var, op.into(), right_var)); instrs.push(IRInstr::Binary(dest_var, left_var, op.into(), right_var));
}
}
// if !op.is_logical() {
// if let Some((true_exit, false_exit)) = self.current_exit_label.last().cloned().flatten() {
// // parent is logical
// instrs.push(IRInstr::Cmp(dest_var, VariableOrIntLit::Var(dest_var), CmpOp::Ne, VariableOrIntLit::IntLit(0)));
// instrs.push(IRInstr::CondGoto(dest_var, true_exit, false_exit));
// } else {
// // parent isn't logical
// if op.is_cmp() {
// instrs.push(IRInstr::Cmp(dest_var, VariableOrIntLit::Var(left_var), op.into(), VariableOrIntLit::Var(right_var)));
// } else {
// instrs.push(IRInstr::Binary(dest_var, left_var, op.into(), right_var));
// }
// }
// }
(instrs, Some(dest_var)) (instrs, Some(dest_var))
}, },
ExprValue::FuncCall(func_name, args) => { ExprValue::FuncCall(func_name, args) => {
@@ -243,35 +613,42 @@ impl Generator {
func_def func_def
} else { } else {
self.diagnostic.add_from_ir_error(IRError::FunctionNotFound(func_name.clone()), expr.span); self.diagnostic.add_from_ir_error(IRError::FunctionNotFound(func_name.clone()), expr.span);
return (vec![], None); return None;
}.clone(); }.clone();
if args.len() < func_def.parameter_types.len() { if args.len() < func_def.parameter_types.len() {
self.diagnostic.add_from_ir_error(IRError::TooFewArguments(func_def.parameter_types.len(), args.len()), expr.span); self.diagnostic.add_from_ir_error(IRError::TooFewArguments(func_def.parameter_types.len(), args.len()), expr.span);
return (vec![], None); return None;
} }
if args.len() > func_def.parameter_types.len() { if args.len() > func_def.parameter_types.len() {
self.diagnostic.add_from_ir_error(IRError::TooManyArguments(func_def.parameter_types.len(), args.len()), expr.span); self.diagnostic.add_from_ir_error(IRError::TooManyArguments(func_def.parameter_types.len(), args.len()), expr.span);
return (vec![], None); return None;
} }
let mut has_error = false; let mut has_error = false;
for (i, arg) in args.into_iter().enumerate() { for parameter_type in &func_def.parameter_types {
let (arg_instrs, arg_var) = self.generate_expr(arg); if matches!(parameter_type, IRType::Void) {
if arg_var.is_none() { self.diagnostic.add_from_ir_error(IRError::InvalidParameterType(IRType::Void), expr.span);
has_error = true; has_error = true;
continue;
} }
let arg_var = arg_var.unwrap(); }
if has_error {
return None;
}
for (i, arg) in args.into_iter().enumerate() {
self.current_exit_label.push(None);
let (arg_instrs, arg_var) = self.generate_expr(arg)?;
self.current_exit_label.pop();
let parameter_type = func_def.parameter_types.get(i).unwrap(); let parameter_type = func_def.parameter_types.get(i).unwrap();
if *parameter_type != arg_var.data_type { if *parameter_type != arg_var.map_or(IRType::Void, |v| v.data_type) {
self.diagnostic.add_from_ir_error(IRError::TypeMismatch(*parameter_type, arg_var.data_type), expr.span); self.diagnostic.add_from_ir_error(IRError::TypeMismatch(*parameter_type, arg_var.map_or(IRType::Void, |v| v.data_type)), expr.span);
has_error = true; has_error = true;
continue; continue;
} }
instrs.extend(arg_instrs); instrs.extend(arg_instrs);
arg_vars.push(arg_var); arg_vars.push(arg_var.unwrap());
} }
if has_error { if has_error {
return (vec![], None); return None;
} }
let ret_variable = if matches!(func_def.return_type, IRType::Void) { let ret_variable = if matches!(func_def.return_type, IRType::Void) {
None None
@@ -281,6 +658,14 @@ impl Generator {
instrs.push(IRInstr::FuncCall(func_def.clone(), arg_vars, ret_variable)); instrs.push(IRInstr::FuncCall(func_def.clone(), arg_vars, ret_variable));
(instrs, ret_variable) (instrs, ret_variable)
} }
};
if let Some((true_exit, false_exit)) = self.current_exit_label.last().cloned().flatten() {
let cmp_var = self.var_manager.declare_temp(IRType::I1);
instrs.push(IRInstr::Cmp(cmp_var, VariableOrIntLit::Var(var.unwrap()), CmpOp::Ne, VariableOrIntLit::IntLit(0)));
instrs.push(IRInstr::CondGoto(cmp_var, true_exit, false_exit));
Some((instrs, var))
} else {
Some((instrs, var))
} }
} }
} }
@@ -350,8 +735,14 @@ impl VariableManager {
self.local_var_type.push(var); self.local_var_type.push(var);
var var
} }
pub fn declare_param_temp(&mut self, var_data_type: IRType) -> Variable { pub fn declare_unamed_local(&mut self, var_data_type: IRType) -> Variable {
let var = Variable { index: self.local_counter, var_type: VariableType::ParamTemp, data_type: var_data_type }; let var = Variable { index: self.local_counter, var_type: VariableType::Local, data_type: var_data_type };
self.local_counter += 1;
self.local_var_type.push(var);
var
}
pub fn declare_param_temp(&mut self, var_data_type: IRType, param_index: usize) -> Variable {
let var = Variable { index: self.local_counter, var_type: VariableType::ParamTemp(param_index), data_type: var_data_type };
self.local_counter += 1; self.local_counter += 1;
self.local_var_type.push(var); self.local_var_type.push(var);
var var
@@ -439,4 +830,12 @@ mod tests {
test_case("0-3,14-25"); test_case("0-3,14-25");
// test_case("0-3,14-25"); // test_case("0-3,14-25");
} }
#[test]
fn test_if_while() {
test_case("26-32,34-41,46-51,57");
}
#[test]
fn test_func() {
test_case("12-13,58-60");
}
} }
+91 -17
View File
@@ -3,15 +3,32 @@ use std::fmt::Display;
use crate::ast::types::Type as AstType; use crate::ast::types::Type as AstType;
use crate::ast::types::BinaryOp as AstBinaryOp; use crate::ast::types::BinaryOp as AstBinaryOp;
use crate::ir::err::IRError; use crate::ir::err::IRError;
pub enum VariableOrIntLit {
Var(Variable),
IntLit(i32),
}
impl Display for VariableOrIntLit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VariableOrIntLit::Var(v) => write!(f, "{}", v),
VariableOrIntLit::IntLit(i) => write!(f, "{}", i),
}
}
}
pub enum IRInstr { pub enum IRInstr {
Declare(Variable), Declare(Variable),
DefineFunc(Function, Vec<Variable>, Vec<IRInstr>), DefineFunc(Function, Vec<Variable>, Vec<IRInstr>),
Entry, Entry,
Binary(Variable, Variable, BinaryOp, Variable), Binary(Variable, Variable, BinaryOp, Variable),
Cmp(Variable, VariableOrIntLit, CmpOp, VariableOrIntLit),
Unary(Variable, UnaryOp, Variable),
Exit(Option<Variable>), Exit(Option<Variable>),
FuncCall(Function, Vec<Variable>, Option<Variable>), FuncCall(Function, Vec<Variable>, Option<Variable>),
// Goto, Goto(usize),
// Label, CondGoto(Variable, usize, usize), // condition, true label, false label
Label(usize),
Move(Variable, MoveRValue), Move(Variable, MoveRValue),
} }
@@ -20,6 +37,7 @@ impl Display for IRInstr {
match self { match self {
IRInstr::Entry => write!(f, "entry"), IRInstr::Entry => write!(f, "entry"),
IRInstr::Binary(dest, left, op, right) => write!(f, "{} = {} {},{}", dest, op, left, right), IRInstr::Binary(dest, left, op, right) => write!(f, "{} = {} {},{}", dest, op, left, right),
IRInstr::Cmp(dest, left, op, right) => write!(f, "{} = icmp {} {},{}", dest, op, left, right),
IRInstr::Exit(v) => if let Some(v) = v { write!(f, "exit {}", v) } else { write!(f, "exit") }, IRInstr::Exit(v) => if let Some(v) = v { write!(f, "exit {}", v) } else { write!(f, "exit") },
IRInstr::FuncCall(func, args, dest) => { IRInstr::FuncCall(func, args, dest) => {
if let Some(dest) = dest { if let Some(dest) = dest {
@@ -33,7 +51,11 @@ impl Display for IRInstr {
IRInstr::DefineFunc(func, args, body) => { IRInstr::DefineFunc(func, args, body) => {
let body_str = body.iter().map(|instr| format!(" {}", instr)).collect::<Vec<_>>().join("\n"); let body_str = body.iter().map(|instr| format!(" {}", instr)).collect::<Vec<_>>().join("\n");
write!(f, "define {} {{\n{}\n}}", func.to_decl_string(args), body_str) write!(f, "define {} {{\n{}\n}}", func.to_decl_string(args), body_str)
} },
IRInstr::Goto(label) => write!(f, "br label .L{}", label),
IRInstr::CondGoto(cond, true_label, false_label) => write!(f, "bc {}, .L{}, .L{}", cond, true_label, false_label),
IRInstr::Label(label) => write!(f, ".L{}:", label),
IRInstr::Unary(dest, op, src) => write!(f, "{} = {} {}", dest, op, src),
} }
} }
} }
@@ -51,6 +73,16 @@ impl IRType {
IRType::Void => 0, IRType::Void => 0,
} }
} }
pub fn get_elevate_result(lhs: IRType, rhs: IRType) -> Option<IRType> {
if lhs == rhs {
Some(lhs)
} else if (lhs == IRType::I32 && rhs == IRType::I1) || (lhs == IRType::I1 && rhs == IRType::I32) {
Some(IRType::I32)
} else {
None
}
}
} }
impl Display for IRType { impl Display for IRType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -84,7 +116,7 @@ impl Display for MoveRValue {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum VariableType { pub enum VariableType {
Global, Global,
ParamTemp, ParamTemp(usize),
Local, Local,
Temp, Temp,
} }
@@ -123,7 +155,7 @@ impl Display for Variable {
VariableType::Global => "@g", VariableType::Global => "@g",
VariableType::Local => "%l", VariableType::Local => "%l",
VariableType::Temp => "%t", VariableType::Temp => "%t",
VariableType::ParamTemp => "%t", VariableType::ParamTemp(_) => "%t",
}; };
write!(f, "{}{}", prefix, self.index) write!(f, "{}{}", prefix, self.index)
} }
@@ -153,6 +185,8 @@ pub enum BinaryOp {
Mul, Mul,
Div, Div,
Mod, Mod,
}
pub enum CmpOp {
Le, Le,
Lt, Lt,
Gt, Gt,
@@ -160,6 +194,9 @@ pub enum BinaryOp {
Ne, Ne,
Eq, Eq,
} }
pub enum UnaryOp {
Neg
}
impl Display for BinaryOp { impl Display for BinaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -169,12 +206,35 @@ impl Display for BinaryOp {
BinaryOp::Mul => "mul", BinaryOp::Mul => "mul",
BinaryOp::Div => "div", BinaryOp::Div => "div",
BinaryOp::Mod => "mod", BinaryOp::Mod => "mod",
BinaryOp::Le => "le", // BinaryOp::Le => "cmp le",
BinaryOp::Lt => "lt", // BinaryOp::Lt => "cmp lt",
BinaryOp::Gt => "gt", // BinaryOp::Gt => "cmp gt",
BinaryOp::Ge => "ge", // BinaryOp::Ge => "cmp ge",
BinaryOp::Ne => "ne", // BinaryOp::Ne => "cmp ne",
BinaryOp::Eq => "eq", // BinaryOp::Eq => "cmp eq",
};
write!(f, "{}", op_str)
}
}
impl Display for CmpOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let op_str = match self {
CmpOp::Le => "le",
CmpOp::Lt => "lt",
CmpOp::Gt => "gt",
CmpOp::Ge => "ge",
CmpOp::Ne => "ne",
CmpOp::Eq => "eq",
};
write!(f, "{}", op_str)
}
}
impl Display for UnaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let op_str = match self {
UnaryOp::Neg => "neg",
}; };
write!(f, "{}", op_str) write!(f, "{}", op_str)
} }
@@ -187,12 +247,26 @@ impl From<AstBinaryOp> for BinaryOp {
AstBinaryOp::Mul => BinaryOp::Mul, AstBinaryOp::Mul => BinaryOp::Mul,
AstBinaryOp::Div => BinaryOp::Div, AstBinaryOp::Div => BinaryOp::Div,
AstBinaryOp::Mod => BinaryOp::Mod, AstBinaryOp::Mod => BinaryOp::Mod,
AstBinaryOp::Equal => BinaryOp::Eq, // AstBinaryOp::Equal => BinaryOp::Eq,
AstBinaryOp::NotEqual => BinaryOp::Ne, // AstBinaryOp::NotEqual => BinaryOp::Ne,
AstBinaryOp::Less => BinaryOp::Lt, // AstBinaryOp::Less => BinaryOp::Lt,
AstBinaryOp::LessEqual => BinaryOp::Le, // AstBinaryOp::LessEqual => BinaryOp::Le,
AstBinaryOp::Greater => BinaryOp::Gt, // AstBinaryOp::Greater => BinaryOp::Gt,
AstBinaryOp::GreaterEqual => BinaryOp::Ge, // AstBinaryOp::GreaterEqual => BinaryOp::Ge,
_ => unreachable!(),
}
}
}
impl From<AstBinaryOp> for CmpOp {
fn from(ast_op: AstBinaryOp) -> Self {
match ast_op {
AstBinaryOp::Equal => CmpOp::Eq,
AstBinaryOp::NotEqual => CmpOp::Ne,
AstBinaryOp::Less => CmpOp::Lt,
AstBinaryOp::LessEqual => CmpOp::Le,
AstBinaryOp::Greater => CmpOp::Gt,
AstBinaryOp::GreaterEqual => CmpOp::Ge,
_ => unreachable!(),
} }
} }
} }
+25 -4
View File
@@ -11,7 +11,7 @@ use std::{fs::File, io::BufRead};
use clap::Parser as ArgParser; use clap::Parser as ArgParser;
use crate::{frontend::{lexer::Lexer, parser::Parser}, ir::generator::Generator}; use crate::{frontend::{lexer::Lexer, parser::Parser}, ir::generator::Generator};
use crate::backend::generator::Generator as ASMGerenerator;
/// Simple minic compiler built by Rust /// Simple minic compiler built by Rust
#[derive(ArgParser, Debug)] #[derive(ArgParser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@@ -19,6 +19,10 @@ struct Args {
/// Output the generated IR code /// Output the generated IR code
#[arg(short = 'I', long = "ir")] #[arg(short = 'I', long = "ir")]
output_ir: bool, output_ir: bool,
#[arg(skip)]
output_asm: bool,
#[arg(short = 't', long = "target", default_value = "ARM32")]
target: String,
/// Use recursive descent parsing /// Use recursive descent parsing
#[arg(short = 'D', long = "recursive-descent")] #[arg(short = 'D', long = "recursive-descent")]
recursive_descent: bool, recursive_descent: bool,
@@ -33,15 +37,18 @@ struct Args {
} }
fn main() { fn main() {
let args = Args::parse(); let mut args = Args::parse();
if !args.output_ir { if !args.output_ir {
eprintln!("Currently only IR generation is supported. Use -I to enable it."); args.output_asm = true;
return;
} }
if !args.recursive_descent { if !args.recursive_descent {
eprintln!("Currently only recursive descent parsing is supported. Use -D to enable it."); eprintln!("Currently only recursive descent parsing is supported. Use -D to enable it.");
return; return;
} }
if args.target != "ARM32" {
eprintln!("Currently only ARM32 assembly output is supported. Use -t ARM32 to specify the target architecture.");
return;
}
let source_path = std::path::Path::new(&args.source); let source_path = std::path::Path::new(&args.source);
let file = match File::open(&args.source) { let file = match File::open(&args.source) {
Ok(f) => f, Ok(f) => f,
@@ -76,6 +83,7 @@ fn main() {
if !generator.get_diagnostics().is_empty() { if !generator.get_diagnostics().is_empty() {
generator.get_diagnostics().print(&format!("{}", source_path.display()), &full_text); generator.get_diagnostics().print(&format!("{}", source_path.display()), &full_text);
} }
if args.output_ir {
if let Some(output_path) = args.output { if let Some(output_path) = args.output {
match std::fs::write(&output_path, ir.iter().map(|instr| instr.to_string()).collect::<Vec<_>>().join("\n")) { match std::fs::write(&output_path, ir.iter().map(|instr| instr.to_string()).collect::<Vec<_>>().join("\n")) {
Ok(_) => println!("IR code written to {}", output_path), Ok(_) => println!("IR code written to {}", output_path),
@@ -86,4 +94,17 @@ fn main() {
println!("{}", instr); println!("{}", instr);
} }
} }
} else if args.output_asm {
let mut asm_generator = ASMGerenerator::new();
asm_generator.emit(ir);
let asm_text = asm_generator.to_text();
if let Some(output_path) = args.output {
match std::fs::write(&output_path, asm_text) {
Ok(_) => println!("Assembly code written to {}", output_path),
Err(e) => eprintln!("Failed to write assembly code to {}: {}", output_path, e),
}
} else {
println!("{}", asm_text);
}
}
} }
+16 -3
View File
@@ -84,6 +84,11 @@ def parse_args() -> argparse.Namespace:
action="store_true", action="store_true",
help="do not compile tests/std.c with the assembly file", help="do not compile tests/std.c with the assembly file",
) )
parser.add_argument(
"--stdin-file",
type=Path,
help="file used as stdin when running the compiled program",
)
args = parser.parse_args(tool_args) args = parser.parse_args(tool_args)
args.program_args = program_args args.program_args = program_args
return args return args
@@ -95,9 +100,13 @@ def require_command(command: str) -> None:
sys.exit(127) sys.exit(127)
def run_command(command: list[str]) -> subprocess.CompletedProcess[bytes]: def run_command(
command: list[str],
*,
stdin: bytes | None = None,
) -> subprocess.CompletedProcess[bytes]:
try: try:
return subprocess.run(command, capture_output=True, check=False) return subprocess.run(command, input=stdin, capture_output=True, check=False)
except OSError as err: except OSError as err:
print(f"error: failed to run {command[0]}: {err}", file=sys.stderr) print(f"error: failed to run {command[0]}: {err}", file=sys.stderr)
sys.exit(127) sys.exit(127)
@@ -125,6 +134,9 @@ def main() -> int:
if not args.no_std_c and not std_c.is_file(): if not args.no_std_c and not std_c.is_file():
print(f"error: std C file not found: {std_c}", file=sys.stderr) print(f"error: std C file not found: {std_c}", file=sys.stderr)
return 2 return 2
if args.stdin_file is not None and not args.stdin_file.is_file():
print(f"error: stdin file not found: {args.stdin_file}", file=sys.stderr)
return 2
require_command(args.cc) require_command(args.cc)
require_command(args.qemu) require_command(args.qemu)
@@ -164,7 +176,8 @@ def main() -> int:
print("gdb: use 'si' or 'c'; do not use 'run'", file=sys.stderr, flush=True) print("gdb: use 'si' or 'c'; do not use 'run'", file=sys.stderr, flush=True)
qemu_cmd.extend([str(output), *args.program_args]) qemu_cmd.extend([str(output), *args.program_args])
run_result = run_command(qemu_cmd) stdin = args.stdin_file.read_bytes() if args.stdin_file is not None else None
run_result = run_command(qemu_cmd, stdin=stdin)
sys.stdout.buffer.write(run_result.stdout) sys.stdout.buffer.write(run_result.stdout)
sys.stderr.buffer.write(run_result.stderr) sys.stderr.buffer.write(run_result.stderr)
if run_result.stdout and not run_result.stdout.endswith(b"\n"): if run_result.stdout and not run_result.stdout.endswith(b"\n"):
+321
View File
@@ -0,0 +1,321 @@
#!/usr/bin/env python3
"""Run selected numbered testcases through the ARM assembly backend."""
from __future__ import annotations
import argparse
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class ExpectedOutput:
stdout: bytes
returncode: int
@dataclass(frozen=True)
class Testcase:
number: int
source: Path
input_file: Path | None
output_file: Path | None
@dataclass(frozen=True)
class RunOutput:
stdout: bytes
returncode: int
stderr: bytes
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Run numbered testcases like 1-5,7 with "
"`cargo run -- -S -D -o output.s testcase.c`, then "
"`tools/run_arm_asm.py output.s`, and compare with testcase.out."
)
)
parser.add_argument("numbers", help="testcase numbers, for example: 1-5,7")
parser.add_argument(
"--testcases",
type=Path,
default=Path("testcases"),
help="testcase directory (default: testcases)",
)
parser.add_argument(
"--asm",
type=Path,
default=Path("output.s"),
help="assembly output path reused for each testcase (default: output.s)",
)
parser.add_argument(
"--runner",
type=Path,
default=Path("tools/run_arm_asm.py"),
help="assembly runner path (default: tools/run_arm_asm.py)",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="print compiler/runner stderr for passing cases too",
)
return parser.parse_args()
def parse_numbers(spec: str) -> list[int]:
numbers: list[int] = []
seen: set[int] = set()
for raw_part in spec.split(","):
part = raw_part.strip()
if not part:
continue
if "-" in part:
bounds = part.split("-", 1)
if len(bounds) != 2 or not bounds[0] or not bounds[1]:
raise ValueError(f"invalid range: {part!r}")
start, end = int(bounds[0]), int(bounds[1])
step = 1 if start <= end else -1
values = range(start, end + step, step)
else:
values = (int(part),)
for value in values:
if value <= 0:
raise ValueError(f"testcase number must be positive: {value}")
if value not in seen:
seen.add(value)
numbers.append(value)
if not numbers:
raise ValueError("no testcase numbers specified")
return numbers
def find_testcase(testcases_dir: Path, number: int) -> Testcase:
prefix = f"{number:03d}_"
matches = sorted(testcases_dir.glob(f"{prefix}*.c"))
if not matches:
raise FileNotFoundError(f"no C testcase found for {number} ({prefix}*.c)")
if len(matches) > 1:
joined = ", ".join(str(path) for path in matches)
raise RuntimeError(f"multiple C testcases found for {number}: {joined}")
source = matches[0]
stem = source.with_suffix("")
input_file = stem.with_suffix(".in")
output_file = stem.with_suffix(".out")
return Testcase(
number=number,
source=source,
input_file=input_file if input_file.is_file() else None,
output_file=output_file if output_file.is_file() else None,
)
def parse_expected(output_file: Path) -> ExpectedOutput:
data = output_file.read_bytes()
lines = data.splitlines(keepends=True)
if not lines:
raise ValueError(f"empty expected output file: {output_file}")
returncode_line = lines[-1].strip()
try:
returncode = int(returncode_line)
except ValueError as err:
raise ValueError(
f"last line of {output_file} is not an integer return code: "
f"{returncode_line!r}"
) from err
return ExpectedOutput(stdout=b"".join(lines[:-1]), returncode=returncode)
def normalize_returncode(returncode: int) -> int:
return returncode & 255
def strip_trailing_newlines(data: bytes) -> bytes:
return data.rstrip(b"\r\n")
def run_command(command: list[str]) -> subprocess.CompletedProcess[bytes]:
return subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
def format_bytes(data: bytes) -> str:
if not data:
return "<empty>"
try:
return data.decode()
except UnicodeDecodeError:
return repr(data)
def format_bytes_debug(data: bytes) -> str:
return f"{data!r} ({len(data)} bytes)"
def parse_runner_output(result: subprocess.CompletedProcess[bytes]) -> RunOutput:
lines = result.stdout.splitlines(keepends=True)
if not lines:
return RunOutput(stdout=b"", returncode=normalize_returncode(result.returncode), stderr=result.stderr)
last_line = lines[-1].strip()
marker = b"$? = "
if not last_line.startswith(marker):
return RunOutput(stdout=result.stdout, returncode=normalize_returncode(result.returncode), stderr=result.stderr)
try:
returncode = int(last_line[len(marker) :])
except ValueError:
return RunOutput(stdout=result.stdout, returncode=normalize_returncode(result.returncode), stderr=result.stderr)
return RunOutput(stdout=b"".join(lines[:-1]), returncode=normalize_returncode(returncode), stderr=result.stderr)
def print_failure_detail(
testcase: Testcase,
*,
expected: ExpectedOutput | None,
actual_stdout: bytes,
actual_returncode: int | None,
stderr: bytes,
verbose: bool,
) -> None:
print(f" source: {testcase.source}")
if testcase.input_file is not None:
print(f" input: {testcase.input_file}")
if expected is not None:
print(" expected stdout:")
print(format_bytes(expected.stdout), end="" if expected.stdout.endswith(b"\n") else "\n")
print(f" expected stdout bytes: {format_bytes_debug(expected.stdout)}")
print(f" expected returncode: {expected.returncode}")
print(" actual stdout:")
print(format_bytes(actual_stdout), end="" if actual_stdout.endswith(b"\n") else "\n")
print(f" actual stdout bytes: {format_bytes_debug(actual_stdout)}")
if actual_returncode is not None:
print(f" actual returncode: {actual_returncode}")
if stderr and (verbose or expected is None):
print(" stderr:")
print(format_bytes(stderr), end="" if stderr.endswith(b"\n") else "\n")
def run_testcase(
testcase: Testcase,
*,
asm_file: Path,
runner: Path,
verbose: bool,
) -> bool:
compile_cmd = [
"cargo",
"run",
"--",
"-S",
"-D",
"-o",
str(asm_file),
str(testcase.source),
]
compile_result = run_command(compile_cmd)
if compile_result.returncode != 0:
print(f"[FAIL] {testcase.number:03d} compile failed")
print_failure_detail(
testcase,
expected=None,
actual_stdout=compile_result.stdout,
actual_returncode=compile_result.returncode,
stderr=compile_result.stderr,
verbose=True,
)
return False
run_cmd = [str(runner), str(asm_file)]
if testcase.input_file is not None:
run_cmd.extend(["--stdin-file", str(testcase.input_file)])
run_result = parse_runner_output(run_command(run_cmd))
if testcase.output_file is None:
print(f"[SKIP] {testcase.number:03d} missing .out: {testcase.source.name}")
print_failure_detail(
testcase,
expected=None,
actual_stdout=run_result.stdout,
actual_returncode=run_result.returncode,
stderr=run_result.stderr,
verbose=verbose,
)
return False
expected = parse_expected(testcase.output_file)
ok = (
strip_trailing_newlines(run_result.stdout)
== strip_trailing_newlines(expected.stdout)
and run_result.returncode == expected.returncode
)
status = "PASS" if ok else "FAIL"
print(f"[{status}] {testcase.number:03d} {testcase.source.name}")
if not ok or (verbose and (compile_result.stderr or run_result.stderr)):
if verbose and compile_result.stderr:
print(" compiler stderr:")
print(
format_bytes(compile_result.stderr),
end="" if compile_result.stderr.endswith(b"\n") else "\n",
)
print_failure_detail(
testcase,
expected=expected,
actual_stdout=run_result.stdout,
actual_returncode=run_result.returncode,
stderr=run_result.stderr,
verbose=verbose,
)
return ok
def main() -> int:
args = parse_args()
try:
numbers = parse_numbers(args.numbers)
testcases = [find_testcase(args.testcases, number) for number in numbers]
except (OSError, RuntimeError, ValueError) as err:
print(f"error: {err}", file=sys.stderr)
return 2
passed = 0
for testcase in testcases:
try:
if run_testcase(
testcase,
asm_file=args.asm,
runner=args.runner,
verbose=args.verbose,
):
passed += 1
except OSError as err:
print(f"[FAIL] {testcase.number:03d} failed to run command: {err}")
except ValueError as err:
print(f"[FAIL] {testcase.number:03d} invalid expected output: {err}")
total = len(testcases)
failed = total - passed
print(f"\nsummary: {passed}/{total} passed, {failed} failed")
return 0 if failed == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())
+301
View File
@@ -0,0 +1,301 @@
#!/usr/bin/env python3
"""Run selected numbered testcases through the IR backend and IRCompiler."""
from __future__ import annotations
import argparse
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class ExpectedOutput:
stdout: bytes
returncode: int
@dataclass(frozen=True)
class Testcase:
number: int
source: Path
input_file: Path | None
output_file: Path | None
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Run numbered testcases like 1-5,7 with "
"`cargo run -- -I -S -D -o output.ir testcase.c`, then "
"`tools/IRCompiler -R output.ir`, and compare with testcase.out."
)
)
parser.add_argument("numbers", help="testcase numbers, for example: 1-5,7")
parser.add_argument(
"--testcases",
type=Path,
default=Path("testcases"),
help="testcase directory (default: testcases)",
)
parser.add_argument(
"--ir",
type=Path,
default=Path("output.ir"),
help="IR output path reused for each testcase (default: output.ir)",
)
parser.add_argument(
"--ircompiler",
type=Path,
default=Path("tools/IRCompiler"),
help="IRCompiler path (default: tools/IRCompiler)",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="print compiler/interpreter stderr for passing cases too",
)
return parser.parse_args()
def parse_numbers(spec: str) -> list[int]:
numbers: list[int] = []
seen: set[int] = set()
for raw_part in spec.split(","):
part = raw_part.strip()
if not part:
continue
if "-" in part:
bounds = part.split("-", 1)
if len(bounds) != 2 or not bounds[0] or not bounds[1]:
raise ValueError(f"invalid range: {part!r}")
start, end = int(bounds[0]), int(bounds[1])
step = 1 if start <= end else -1
values = range(start, end + step, step)
else:
values = (int(part),)
for value in values:
if value <= 0:
raise ValueError(f"testcase number must be positive: {value}")
if value not in seen:
seen.add(value)
numbers.append(value)
if not numbers:
raise ValueError("no testcase numbers specified")
return numbers
def find_testcase(testcases_dir: Path, number: int) -> Testcase:
prefix = f"{number:03d}_"
matches = sorted(testcases_dir.glob(f"{prefix}*.c"))
if not matches:
raise FileNotFoundError(f"no C testcase found for {number} ({prefix}*.c)")
if len(matches) > 1:
joined = ", ".join(str(path) for path in matches)
raise RuntimeError(f"multiple C testcases found for {number}: {joined}")
source = matches[0]
stem = source.with_suffix("")
input_file = stem.with_suffix(".in")
output_file = stem.with_suffix(".out")
return Testcase(
number=number,
source=source,
input_file=input_file if input_file.is_file() else None,
output_file=output_file if output_file.is_file() else None,
)
def parse_expected(output_file: Path) -> ExpectedOutput:
data = output_file.read_bytes()
lines = data.splitlines(keepends=True)
if not lines:
raise ValueError(f"empty expected output file: {output_file}")
returncode_line = lines[-1].strip()
try:
returncode = int(returncode_line)
except ValueError as err:
raise ValueError(
f"last line of {output_file} is not an integer return code: "
f"{returncode_line!r}"
) from err
return ExpectedOutput(stdout=b"".join(lines[:-1]), returncode=returncode)
def normalize_returncode(returncode: int) -> int:
return returncode & 255
def strip_trailing_newlines(data: bytes) -> bytes:
return data.rstrip(b"\r\n")
def run_command(
command: list[str],
*,
stdin: bytes | None = None,
) -> subprocess.CompletedProcess[bytes]:
return subprocess.run(
command,
input=stdin,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
def format_bytes(data: bytes) -> str:
if not data:
return "<empty>"
try:
return data.decode()
except UnicodeDecodeError:
return repr(data)
def format_bytes_debug(data: bytes) -> str:
return f"{data!r} ({len(data)} bytes)"
def print_failure_detail(
testcase: Testcase,
*,
expected: ExpectedOutput | None,
actual_stdout: bytes,
actual_returncode: int | None,
stderr: bytes,
verbose: bool,
) -> None:
print(f" source: {testcase.source}")
if testcase.input_file is not None:
print(f" input: {testcase.input_file}")
if expected is not None:
print(" expected stdout:")
print(format_bytes(expected.stdout), end="" if expected.stdout.endswith(b"\n") else "\n")
print(f" expected stdout bytes: {format_bytes_debug(expected.stdout)}")
print(f" expected returncode: {expected.returncode}")
print(" actual stdout:")
print(format_bytes(actual_stdout), end="" if actual_stdout.endswith(b"\n") else "\n")
print(f" actual stdout bytes: {format_bytes_debug(actual_stdout)}")
if actual_returncode is not None:
print(f" actual returncode: {actual_returncode}")
if stderr and (verbose or expected is None):
print(" stderr:")
print(format_bytes(stderr), end="" if stderr.endswith(b"\n") else "\n")
def run_testcase(
testcase: Testcase,
*,
ir_file: Path,
ircompiler: Path,
verbose: bool,
) -> bool:
compile_cmd = [
"cargo",
"run",
"--",
"-I",
"-S",
"-D",
"-o",
str(ir_file),
str(testcase.source),
]
compile_result = run_command(compile_cmd)
if compile_result.returncode != 0:
print(f"[FAIL] {testcase.number:03d} compile failed")
print_failure_detail(
testcase,
expected=None,
actual_stdout=compile_result.stdout,
actual_returncode=compile_result.returncode,
stderr=compile_result.stderr,
verbose=True,
)
return False
stdin = testcase.input_file.read_bytes() if testcase.input_file is not None else None
run_result = run_command([str(ircompiler), "-R", str(ir_file)], stdin=stdin)
if testcase.output_file is None:
print(f"[SKIP] {testcase.number:03d} missing .out: {testcase.source.name}")
print_failure_detail(
testcase,
expected=None,
actual_stdout=run_result.stdout,
actual_returncode=run_result.returncode,
stderr=run_result.stderr,
verbose=verbose,
)
return False
expected = parse_expected(testcase.output_file)
actual_returncode = normalize_returncode(run_result.returncode)
ok = (
strip_trailing_newlines(run_result.stdout)
== strip_trailing_newlines(expected.stdout)
and actual_returncode == expected.returncode
)
status = "PASS" if ok else "FAIL"
print(f"[{status}] {testcase.number:03d} {testcase.source.name}")
if not ok or (verbose and (compile_result.stderr or run_result.stderr)):
if verbose and compile_result.stderr:
print(" compiler stderr:")
print(
format_bytes(compile_result.stderr),
end="" if compile_result.stderr.endswith(b"\n") else "\n",
)
print_failure_detail(
testcase,
expected=expected,
actual_stdout=run_result.stdout,
actual_returncode=actual_returncode,
stderr=run_result.stderr,
verbose=verbose,
)
return ok
def main() -> int:
args = parse_args()
try:
numbers = parse_numbers(args.numbers)
testcases = [find_testcase(args.testcases, number) for number in numbers]
except (OSError, RuntimeError, ValueError) as err:
print(f"error: {err}", file=sys.stderr)
return 2
passed = 0
for testcase in testcases:
try:
if run_testcase(
testcase,
ir_file=args.ir,
ircompiler=args.ircompiler,
verbose=args.verbose,
):
passed += 1
except OSError as err:
print(f"[FAIL] {testcase.number:03d} failed to run command: {err}")
except ValueError as err:
print(f"[FAIL] {testcase.number:03d} invalid expected output: {err}")
total = len(testcases)
failed = total - passed
print(f"\nsummary: {passed}/{total} passed, {failed} failed")
return 0 if failed == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())