From 4c850d58012e643a42da9c4524ebacb7dc8c669c Mon Sep 17 00:00:00 2001 From: Jeehoon Kang Date: Fri, 29 May 2020 21:27:35 +0900 Subject: [PATCH] Update skeleton --- README.md | 9 +- bin/fuzz.rs | 4 + examples/asm/alignof.c | 3 + examples/asm/bar.c | 10 + examples/asm/cmp.c | 18 ++ examples/asm/fibonacci.c | 11 + src/asm/mod.rs | 164 ++++++++++++- src/asm/write_asm.rs | 47 +++- src/asmgen/mod.rs | 501 +-------------------------------------- src/ir/dtype.rs | 2 +- src/opt/simplify_cfg.rs | 8 +- src/tests.rs | 136 +++++++++++ tests/test_examples.rs | 6 + 13 files changed, 398 insertions(+), 521 deletions(-) create mode 100644 examples/asm/alignof.c create mode 100644 examples/asm/bar.c create mode 100644 examples/asm/cmp.c create mode 100644 examples/asm/fibonacci.c diff --git a/README.md b/README.md index 05dd31d..9bbf3f4 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,11 @@ cargo build --release # release build ## Run ```sh -cargo run -- -h # print options -cargo run -- -p examples/c/fibonacci.c # parse -cargo run -- -i examples/c/fibonacci.c # irgen -cargo run -- examples/c/fibonacci.c # compile +cargo run -- -h # print options +cargo run -- -p examples/c/fibonacci.c # parse +cargo run -- -i examples/c/fibonacci.c # irgen +cargo run -- -O --iroutput examples/c/fibonacci.c # optimize +cargo run -- examples/c/fibonacci.c # compile cargo run -- --irrun examples/c/fibonacci.c # interprets the IR diff --git a/bin/fuzz.rs b/bin/fuzz.rs index a05234a..195ffd7 100644 --- a/bin/fuzz.rs +++ b/bin/fuzz.rs @@ -22,10 +22,12 @@ fn main() { if matches.is_present("print") { kecc::test_write_c(&unit, Path::new(&input)); + return; } if matches.is_present("irgen") { kecc::test_irgen(&unit, Path::new(&input)); + return; } if matches.is_present("simplify-cfg") { @@ -43,4 +45,6 @@ fn main() { if matches.is_present("gvn") { todo!("test gvn"); } + + kecc::test_asmgen(&unit, Path::new(&input)); } diff --git a/examples/asm/alignof.c b/examples/asm/alignof.c new file mode 100644 index 0000000..5e36459 --- /dev/null +++ b/examples/asm/alignof.c @@ -0,0 +1,3 @@ +int main() { + return _Alignof(const int) == 4; +} diff --git a/examples/asm/bar.c b/examples/asm/bar.c new file mode 100644 index 0000000..4cd8b0b --- /dev/null +++ b/examples/asm/bar.c @@ -0,0 +1,10 @@ +int bar(int x, int y, int z){ + int arith_mean = (x + y + z) / 3; + int ugly_mean = (((x + y) / 2) * 2 + z) / 3; + if (x == y) { return y; } + else { return z; } +} + +int main() { + return 1; +} diff --git a/examples/asm/cmp.c b/examples/asm/cmp.c new file mode 100644 index 0000000..4d68ddf --- /dev/null +++ b/examples/asm/cmp.c @@ -0,0 +1,18 @@ +int int_greater_than(int i, unsigned int j) { + if (i > j) return 1; + else return 0; +} + +int char_greater_than(char i, unsigned char j) { + if (i > j) return 1; + else return 0; +} + +int main() { + // cmp ugt + int r1 = int_greater_than(-1, 1); + // cmp sgt + int r2 = char_greater_than(-1, 1); + + return r1 == 1 && r2 == 0; +} diff --git a/examples/asm/fibonacci.c b/examples/asm/fibonacci.c new file mode 100644 index 0000000..a65fe3a --- /dev/null +++ b/examples/asm/fibonacci.c @@ -0,0 +1,11 @@ +int fibonacci(int n) { + if (n < 2) { + return n; + } + + return fibonacci(n - 2) + fibonacci(n - 1); +} + +int main() { + return fibonacci(9) == 34; +} diff --git a/src/asm/mod.rs b/src/asm/mod.rs index f0cfba6..96701c6 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -87,7 +87,7 @@ pub enum SymbolType { #[derive(Debug, Clone, PartialEq)] pub enum Instruction { /// R-type instruction format - /// https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf (11p, 104p) + /// https://riscv.org/specifications/isa-spec-pdf/ (16p, 129p) RType { instr: RType, rd: Register, @@ -95,7 +95,7 @@ pub enum Instruction { rs2: Register, }, /// I-type instruction format - /// https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf (11p, 104p) + /// https://riscv.org/specifications/isa-spec-pdf/ (16p, 129p) IType { instr: IType, rd: Register, @@ -103,26 +103,109 @@ pub enum Instruction { imm: isize, }, /// S-type instruction format - /// https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf (11p, 104p) + /// https://riscv.org/specifications/isa-spec-pdf/ (16p, 129p) SType { instr: SType, rs1: Register, rs2: Register, imm: isize, }, + /// B-type instruction format + /// https://riscv.org/specifications/isa-spec-pdf/ (16p, 129p) + BType { + instr: BType, + rs1: Register, + rs2: Register, + imm: Label, + }, Pseudo(Pseudo), } +/// If the enum variant contains `Option`, +/// it means that the instructions used may vary according to `DataSize`. +/// Use 'Some' if RISC-V ISA provides instruction to support a specific 'DataSize', +/// if not, 'None' which means to use default instruction. +/// Because KECC uses RV64 (RISC-V ISA for 64-bit architecture), +/// KECC uses `Some` if `DataSize` is `Word`, if not, use `None`. +/// https://riscv.org/specifications/isa-spec-pdf/ (35p) +/// +/// If the enum variant contains `bool`, +/// It means that different instructions exist +/// depending on whether the operand is signed or not. #[derive(Debug, Clone, PartialEq)] pub enum RType { Add(Option), + Sub(Option), Mul(Option), + Div(Option, bool), + Slt(bool), + Xor, } +impl RType { + pub fn add(dtype: ir::Dtype) -> Self { + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + let data_size = if data_size == DataSize::Word { + Some(data_size) + } else { + None + }; + + Self::Add(data_size) + } + + pub fn sub(dtype: ir::Dtype) -> Self { + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + let data_size = if data_size == DataSize::Word { + Some(data_size) + } else { + None + }; + + Self::Sub(data_size) + } + + pub fn mul(dtype: ir::Dtype) -> Self { + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + let data_size = if data_size == DataSize::Word { + Some(data_size) + } else { + None + }; + + Self::Mul(data_size) + } + + pub fn div(dtype: ir::Dtype, is_signed: bool) -> Self { + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + let data_size = if data_size == DataSize::Word { + Some(data_size) + } else { + None + }; + + Self::Div(data_size, is_signed) + } +} + +/// If the enum variant contains `Option`, +/// it means that the instructions used may vary according to `DataSize`. +/// Use 'Some' if RISC-V ISA provides instruction to support a specific 'DataSize', +/// if not, 'None' which means to use default instruction. +/// Because KECC uses RV64 (RISC-V ISA for 64-bit architecture), +/// KECC uses `Some` if `DataSize` is `Word`, if not, use `None`. +/// https://riscv.org/specifications/isa-spec-pdf/ (35p) #[derive(Debug, Clone, PartialEq)] pub enum IType { Load(DataSize), Addi(Option), + Andi, + Slli, + Srli, } impl IType { @@ -131,9 +214,9 @@ impl IType { pub const ADDI: Self = Self::Addi(None); pub fn load(dtype: ir::Dtype) -> Self { - let data_align = - DataSize::try_from(dtype).expect("`data_align` must be derived from `dtype`"); - Self::Load(data_align) + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + Self::Load(data_size) } } @@ -147,12 +230,20 @@ impl SType { pub const SD: Self = Self::Store(DataSize::Double); pub fn store(dtype: ir::Dtype) -> Self { - let data_align = - DataSize::try_from(dtype).expect("`data_align` must be derived from `dtype`"); - Self::Store(data_align) + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + Self::Store(data_size) } } +#[derive(Debug, Clone, PartialEq)] +pub enum BType { + Beq, + Bne, + Blt(bool), + Bge(bool), +} + /// The assembler implements a number of convenience psuedo-instructions that are formed from /// instructions in the base ISA, but have implicit arguments or in some case reversed arguments, /// that result in distinct semantics. @@ -163,9 +254,17 @@ pub enum Pseudo { /// li rd, immediate Li { rd: Register, imm: isize }, /// mv rd, rs - Mv { rs: Register, rd: Register }, + Mv { rd: Register, rs: Register }, + /// neg(w) rd, rs + Neg { + data_size: Option, + rs: Register, + rd: Register, + }, /// sext.w rd, rs - SextW { rs: Register, rd: Register }, + SextW { rd: Register, rs: Register }, + /// seqz rd, rs + Seqz { rd: Register, rs: Register }, /// j offset J { offset: Label }, /// jr rs @@ -176,6 +275,20 @@ pub enum Pseudo { Call { offset: Label }, } +impl Pseudo { + pub fn neg(dtype: ir::Dtype, rs: Register, rd: Register) -> Self { + let data_size = + DataSize::try_from(dtype).expect("`data_size` must be derived from `dtype`"); + let data_size = if data_size == DataSize::Word { + Some(data_size) + } else { + None + }; + + Self::Neg { data_size, rs, rd } + } +} + /// `Label` is used as branch, unconditional jump targets and symbol offsets. /// https://github.com/rv8-io/rv8-io.github.io/blob/master/asm.md#labels #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -220,7 +333,7 @@ impl TryFrom for DataSize { // TODO: Add calling convention information (caller/callee-save registers) /// ABI name for RISC-V integer and floating-point register -/// https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf (109p) +/// https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf (155p) #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] pub enum Register { Zero, @@ -237,10 +350,35 @@ pub enum Register { } impl Register { - // TODO: add all possible registers in the future + pub const T0: Self = Self::Temp(0); + pub const T1: Self = Self::Temp(1); + pub const T2: Self = Self::Temp(2); + pub const T3: Self = Self::Temp(3); + pub const T4: Self = Self::Temp(4); + pub const T5: Self = Self::Temp(5); + pub const T6: Self = Self::Temp(6); + pub const S0: Self = Self::Saved(0); + pub const S1: Self = Self::Saved(1); + pub const S2: Self = Self::Saved(2); + pub const S3: Self = Self::Saved(3); + pub const S4: Self = Self::Saved(4); + pub const S5: Self = Self::Saved(5); + pub const S6: Self = Self::Saved(6); + pub const S7: Self = Self::Saved(7); + pub const S8: Self = Self::Saved(8); + pub const S9: Self = Self::Saved(9); + pub const S10: Self = Self::Saved(10); + pub const S11: Self = Self::Saved(11); + pub const A0: Self = Self::Arg(0); + pub const A1: Self = Self::Arg(1); + pub const A2: Self = Self::Arg(2); + pub const A3: Self = Self::Arg(3); + pub const A4: Self = Self::Arg(4); pub const A5: Self = Self::Arg(5); + pub const A6: Self = Self::Arg(6); + pub const A7: Self = Self::Arg(7); pub fn temp(id: usize) -> Self { assert!(id <= 6); diff --git a/src/asm/write_asm.rs b/src/asm/write_asm.rs index 67ccccd..68f42c2 100644 --- a/src/asm/write_asm.rs +++ b/src/asm/write_asm.rs @@ -137,9 +137,21 @@ impl WriteString for Instruction { "{}\t{},{}({})", instr.write_string(), rs2.write_string(), - imm, + imm.to_string(), rs1.write_string() ), + Self::BType { + instr, + rs1, + rs2, + imm, + } => format!( + "{}\t{},{}, {}", + instr.write_string(), + rs1.write_string(), + rs2.write_string(), + imm.0, + ), Self::Pseudo(pseudo) => pseudo.write_string(), } } @@ -149,7 +161,15 @@ impl WriteString for RType { fn write_string(&self) -> String { match self { Self::Add(data_size) => format!("add{}", data_size.write_string()), + Self::Sub(data_size) => format!("sub{}", data_size.write_string()), Self::Mul(data_size) => format!("mul{}", data_size.write_string()), + Self::Div(data_size, is_signed) => format!( + "div{}{}", + if *is_signed { "" } else { "u" }, + data_size.write_string() + ), + Self::Slt(is_signed) => format!("slt{}", if *is_signed { "" } else { "u" }), + Self::Xor => "xor".to_string(), } } } @@ -159,6 +179,9 @@ impl WriteString for IType { match self { Self::Load(data_size) => format!("l{}", data_size.write_string()), Self::Addi(data_size) => format!("addi{}", data_size.write_string()), + Self::Andi => "andi".to_string(), + Self::Slli => "slli".to_string(), + Self::Srli => "srli".to_string(), } } } @@ -171,12 +194,32 @@ impl WriteString for SType { } } +impl WriteString for BType { + fn write_string(&self) -> String { + match self { + Self::Beq => "beq".to_string(), + Self::Bne => "bne".to_string(), + Self::Blt(is_signed) => format!("blt{}", if *is_signed { "" } else { "u" }), + Self::Bge(is_signed) => format!("bge{}", if *is_signed { "" } else { "u" }), + } + } +} + impl WriteString for Pseudo { fn write_string(&self) -> String { match self { Self::Li { rd, imm } => format!("li\t{},{}", rd.write_string(), imm), Self::Mv { rs, rd } => format!("mv\t{},{}", rd.write_string(), rs.write_string()), - Self::SextW { .. } => todo!(), + Self::Neg { data_size, rs, rd } => format!( + "neg{}\t{},{}", + data_size.write_string(), + rd.write_string(), + rs.write_string() + ), + Self::SextW { rs, rd } => { + format!("sext.w\t{},{}", rd.write_string(), rs.write_string()) + } + Self::Seqz { rs, rd } => format!("seqz\t{},{}", rd.write_string(), rs.write_string()), Self::J { offset } => format!("j\t{}", offset.0), Self::Jr { rs } => format!("jr\t{}", rs.write_string()), Self::Ret => "ret".to_string(), diff --git a/src/asmgen/mod.rs b/src/asmgen/mod.rs index da165f2..d5f4327 100644 --- a/src/asmgen/mod.rs +++ b/src/asmgen/mod.rs @@ -1,24 +1,7 @@ use crate::asm; use crate::ir; -use crate::ir::HasDtype; use crate::Translate; -use core::ops::Deref; -use std::collections::HashMap; - -struct StackInfo { - /// Function name for generating the block labels - name: String, - /// Minimum stack size required to execute function - stack_size: usize, - /// Stack offsets of registers - stack_offsets_registers: HashMap, - /// Stack offset for previous `ra` value - stack_offset_ra: usize, - /// Stack offset for previous `s0` value - stack_offset_s0: usize, -} - #[derive(Default)] pub struct Asmgen {} @@ -26,487 +9,7 @@ impl Translate for Asmgen { type Target = asm::Asm; type Error = (); - fn translate(&mut self, source: &ir::TranslationUnit) -> Result { - let mut functions = Vec::new(); - let mut _variables = Vec::new(); - for (name, decl) in &source.decls { - match decl { - ir::Declaration::Variable { .. } => todo!(), - ir::Declaration::Function { - signature, - definition, - } => { - let definition = definition - .as_ref() - .expect("function must have its definition"); - let function = self.translate_function( - name.clone(), - signature, - definition, - &source.structs, - )?; - functions.push(function); - } - } - } - - Ok(asm::Asm { - unit: asm::TranslationUnit { - functions, - variables: _variables, - }, - }) - } -} - -impl Asmgen { - const STACK_ALIGNMENT: usize = 16; - const SIZE_OF_S_ZERO: usize = 8; - const SIZE_OF_RETURN_ADDRESS: usize = 8; - - fn translate_function( - &self, - name: String, - _signature: &ir::FunctionSignature, - definition: &ir::FunctionDefinition, - structs: &HashMap>, - ) -> Result, ()> { - // TODO: need to map function parameters and memory address - // Map `RegisterId` and its memory address. The memory address is represented - // by a distance from the stack pointer. - let mut stack_offsets_registers = HashMap::new(); - - // Allocate memory for return address and s0(frame pointer). - let mut required_size = Self::SIZE_OF_RETURN_ADDRESS + Self::SIZE_OF_S_ZERO; - - // Allocate memory for local variables. - for (i, alloc) in definition.allocations.iter().enumerate() { - required_size += alloc.deref().size_align_of(structs).unwrap().0; - let rid = ir::RegisterId::local(i); - assert_eq!(stack_offsets_registers.insert(rid, required_size), None); - } - - // Allocate memory for saved registers. - let required_size = definition - .blocks - .iter() - .fold(required_size, |size, (bid, block)| { - block - .instructions - .iter() - .enumerate() - .fold(size, |size, (i, instr)| { - let size = size + instr.deref().dtype().size_align_of(structs).unwrap().0; - let rid = ir::RegisterId::temp(*bid, i); - assert_eq!(stack_offsets_registers.insert(rid, size), None); - size - }) - }); - - // TODO: Allocate memory for next function args - - let stack_size = ((required_size - 1) / Self::STACK_ALIGNMENT + 1) * Self::STACK_ALIGNMENT; - let stack_offset_ra = stack_size - Self::SIZE_OF_RETURN_ADDRESS; - let stack_offset_s0 = stack_offset_ra - Self::SIZE_OF_S_ZERO; - let stack_info = StackInfo { - name, - stack_size, - stack_offsets_registers, - stack_offset_ra, - stack_offset_s0, - }; - - let mut function_section = asm::Section::new(Vec::new(), asm::Function::new(Vec::new())); - - self.translate_section_header(&stack_info, &mut function_section)?; - self.translate_init_block(&stack_info, &mut function_section)?; - definition - .blocks - .iter() - .map(|(bid, block)| { - self.translate_block(&stack_info, *bid, block, &mut function_section) - }) - .collect::>()?; - self.translate_final_block(&stack_info, &mut function_section)?; - - Ok(function_section) - } - - fn translate_section_header( - &self, - stack_info: &StackInfo, - function_section: &mut asm::Section, - ) -> Result<(), ()> { - // .globl func_name - function_section - .header - .push(asm::Directive::Globl(asm::Label(stack_info.name.clone()))); - // .type func_name, @function - function_section.header.push(asm::Directive::Type( - asm::Label(stack_info.name.clone()), - asm::SymbolType::Function, - )); - - Ok(()) - } - - fn translate_init_block( - &self, - stack_info: &StackInfo, - function_section: &mut asm::Section, - ) -> Result<(), ()> { - let mut instrs = Vec::new(); - // addi sp, sp, -stack_size - instrs.push(asm::Instruction::IType { - instr: asm::IType::ADDI, - rd: asm::Register::Sp, - rs1: asm::Register::Sp, - imm: -(stack_info.stack_size as isize), - }); - // sd ra, stack_offset_ra(sp) - instrs.push(asm::Instruction::SType { - instr: asm::SType::SD, - rs2: asm::Register::Ra, - rs1: asm::Register::Sp, - imm: stack_info.stack_offset_ra as isize, - }); - // sd s0, stack_offset_s0(sp) - instrs.push(asm::Instruction::SType { - instr: asm::SType::SD, - rs2: asm::Register::S0, - rs1: asm::Register::Sp, - imm: stack_info.stack_offset_s0 as isize, - }); - // addi s0, sp, stack_size - instrs.push(asm::Instruction::IType { - instr: asm::IType::ADDI, - rd: asm::Register::S0, - rs1: asm::Register::Sp, - imm: stack_info.stack_size as isize, - }); - - let enter_block = asm::Block::new(Some(asm::Label(stack_info.name.clone())), instrs); - function_section.body.blocks.push(enter_block); - - Ok(()) - } - - fn translate_final_block( - &self, - stack_info: &StackInfo, - function_section: &mut asm::Section, - ) -> Result<(), ()> { - let mut instrs = Vec::new(); - // ld ra, stack_offset_ra(sp) - instrs.push(asm::Instruction::IType { - instr: asm::IType::LD, - rd: asm::Register::Ra, - rs1: asm::Register::Sp, - imm: stack_info.stack_offset_ra as isize, - }); - // ld s0, stack_offset_s0(sp) - instrs.push(asm::Instruction::IType { - instr: asm::IType::LD, - rd: asm::Register::S0, - rs1: asm::Register::Sp, - imm: stack_info.stack_offset_s0 as isize, - }); - // addi sp, sp, stack_size - instrs.push(asm::Instruction::IType { - instr: asm::IType::ADDI, - rd: asm::Register::Sp, - rs1: asm::Register::Sp, - imm: stack_info.stack_size as isize, - }); - // jr ra - instrs.push(asm::Instruction::Pseudo(asm::Pseudo::Jr { - rs: asm::Register::Ra, - })); - - let exit_label = asm::Label(format!(".{}_END", stack_info.name)); - let exit_block = asm::Block::new(Some(exit_label), instrs); - function_section.body.blocks.push(exit_block); - - Ok(()) - } - - fn translate_block( - &self, - stack_info: &StackInfo, - bid: ir::BlockId, - block: &ir::Block, - function_section: &mut asm::Section, - ) -> Result<(), ()> { - let instrs_for_phinodes = block - .phinodes - .iter() - .map(|p| self.translate_phinode(stack_info, p.deref())) - .collect::, _>>()? - .into_iter() - .flatten() - .collect(); - let instrs_for_instructions = block - .instructions - .iter() - .enumerate() - .map(|(iid, instr)| { - let dest_rid = ir::RegisterId::temp(bid, iid); - self.translate_instruction(stack_info, dest_rid, instr.deref()) - }) - .collect::, _>>()? - .into_iter() - .flatten() - .collect(); - let instrs_for_block_exit = self.translate_block_exit(stack_info, &block.exit)?; - - let instrs = vec![ - instrs_for_phinodes, - instrs_for_instructions, - instrs_for_block_exit, - ] - .into_iter() - .flatten() - .collect(); - - let label = asm::Label::new(&stack_info.name, bid); - let block = asm::Block::new(Some(label), instrs); - function_section.body.blocks.push(block); - - Ok(()) - } - - fn translate_phinode( - &self, - _stack_info: &StackInfo, - _phinode: &ir::Dtype, - ) -> Result, ()> { - todo!() - } - - fn translate_instruction( - &self, - stack_info: &StackInfo, - dest_rid: ir::RegisterId, - instruction: &ir::Instruction, - ) -> Result, ()> { - let (mut instrs, rd) = match instruction { - ir::Instruction::Store { ptr, value } => { - let instrs = self.translate_store(stack_info, ptr, value)?; - return Ok(instrs); - } - ir::Instruction::Load { ptr } => self.translate_load(stack_info, ptr)?, - ir::Instruction::Call { callee, args, .. } => self.translate_call(callee, args)?, - _ => todo!(), - }; - - // Store `rd` into memory - let dtype = instruction.dtype(); - let dist_s0_to_ptr = stack_info - .stack_offsets_registers - .get(&dest_rid) - .expect("address matched with `rid` must exist"); - let store_instr = asm::SType::store(dtype); - - instrs.push(asm::Instruction::SType { - instr: store_instr, - rs2: rd, - rs1: asm::Register::S0, - imm: -(*dist_s0_to_ptr as isize), - }); - - Ok(instrs) - } - - fn translate_store( - &self, - stack_info: &StackInfo, - ptr: &ir::Operand, - value: &ir::Operand, - ) -> Result, ()> { - let (instrs_for_value, reg_of_value) = self.translate_operand(stack_info, value)?; - - let mut instrs = Vec::new(); - let (rid, dtype) = ptr.get_register().expect("`ptr` must be register"); - let dist_s0_to_ptr = stack_info - .stack_offsets_registers - .get(rid) - .expect("address matched with `rid` must exist"); - let inner_dtype = dtype - .get_pointer_inner() - .expect("`dtype` must be pointer type"); - let store_instr = asm::SType::store(inner_dtype.clone()); - - instrs.push(asm::Instruction::SType { - instr: store_instr, - rs2: reg_of_value, - rs1: asm::Register::S0, - imm: -(*dist_s0_to_ptr as isize), - }); - - let instrs = vec![instrs_for_value, instrs] - .into_iter() - .flatten() - .collect(); - Ok(instrs) - } - - fn translate_load( - &self, - stack_info: &StackInfo, - ptr: &ir::Operand, - ) -> Result<(Vec, asm::Register), ()> { - let mut instrs = Vec::new(); - - let (rid, dtype) = ptr.get_register().expect("`ptr` must be register"); - let dist_s0_to_ptr = stack_info - .stack_offsets_registers - .get(rid) - .expect("address matched with `rid` must exist"); - let inner_dtype = dtype - .get_pointer_inner() - .expect("`dtype` must be pointer type"); - let load_instr = asm::IType::load(inner_dtype.clone()); - - // TODO: select register which is not occupied - let rd = asm::Register::A5; - instrs.push(asm::Instruction::IType { - instr: load_instr, - rd, - rs1: asm::Register::S0, - imm: -(*dist_s0_to_ptr as isize), - }); - - Ok((instrs, rd)) - } - - fn translate_call( - &self, - callee: &ir::Operand, - _args: &[ir::Operand], - ) -> Result<(Vec, asm::Register), ()> { - let mut instrs = Vec::new(); - - // TODO: translate pass the args - - match callee { - ir::Operand::Constant(constant) => { - if let ir::Constant::GlobalVariable { name, dtype } = constant { - assert!(dtype.get_function_inner().is_some()); - instrs.push(asm::Instruction::Pseudo(asm::Pseudo::Call { - offset: asm::Label(name.clone()), - })); - } else { - panic!("`callee` must be `GlobalVariable`") - } - } - _ => todo!(), - } - - // TODO: select register which is not occupied - let rd = asm::Register::A5; - instrs.push(asm::Instruction::Pseudo(asm::Pseudo::Mv { - rs: asm::Register::A0, - rd, - })); - - Ok((instrs, rd)) - } - - fn translate_block_exit( - &self, - stack_info: &StackInfo, - block_exit: &ir::BlockExit, - ) -> Result, ()> { - match block_exit { - ir::BlockExit::Return { value } => self.translate_return(stack_info, value), - _ => todo!(), - } - } - - fn translate_return( - &self, - stack_info: &StackInfo, - value: &ir::Operand, - ) -> Result, ()> { - let (instrs_for_value, rs) = self.translate_operand(stack_info, value)?; - - let mut instrs = Vec::new(); - instrs.push(asm::Instruction::Pseudo(asm::Pseudo::Mv { - rs, - rd: asm::Register::A0, - })); - // Jump to exit block - instrs.push(asm::Instruction::Pseudo(asm::Pseudo::J { - offset: asm::Label(format!(".{}_END", stack_info.name)), - })); - - let instrs = vec![instrs_for_value, instrs] - .into_iter() - .flatten() - .collect(); - Ok(instrs) - } - - fn translate_operand( - &self, - stack_info: &StackInfo, - operand: &ir::Operand, - ) -> Result<(Vec, asm::Register), ()> { - match operand { - ir::Operand::Constant(constant) => self.translate_constant(stack_info, constant), - ir::Operand::Register { rid, dtype } => self.translate_register(stack_info, rid, dtype), - } - } - - fn translate_constant( - &self, - _stack_info: &StackInfo, - constant: &ir::Constant, - ) -> Result<(Vec, asm::Register), ()> { - let mut instrs = Vec::new(); - - match constant { - // TODO: consider width and signed option in the future - ir::Constant::Int { value, .. } => { - // TODO: select register which is not occupied - let rd = asm::Register::A5; - instrs.push(asm::Instruction::Pseudo(asm::Pseudo::Li { - rd, - imm: *value as isize, - })); - Ok((instrs, rd)) - } - ir::Constant::Undef { .. } => { - // TODO: select register which is not occupied - Ok((instrs, asm::Register::A5)) - } - _ => todo!(), - } - } - - fn translate_register( - &self, - stack_info: &StackInfo, - rid: &ir::RegisterId, - dtype: &ir::Dtype, - ) -> Result<(Vec, asm::Register), ()> { - let mut instrs = Vec::new(); - - let dist_s0_to_ptr = stack_info - .stack_offsets_registers - .get(rid) - .expect("address matched with `rid` must exist"); - let load_instr = asm::IType::load(dtype.clone()); - - // TODO: select register which is not occupied - let rd = asm::Register::A5; - instrs.push(asm::Instruction::IType { - instr: load_instr, - rd, - rs1: asm::Register::S0, - imm: -(*dist_s0_to_ptr as isize), - }); - - Ok((instrs, rd)) + fn translate(&mut self, _source: &ir::TranslationUnit) -> Result { + todo!("homework 7") } } diff --git a/src/ir/dtype.rs b/src/ir/dtype.rs index 6fb0fa6..4c16bff 100644 --- a/src/ir/dtype.rs +++ b/src/ir/dtype.rs @@ -467,7 +467,7 @@ impl Dtype { pub const BITS_OF_BYTE: usize = 8; pub const SIZE_OF_BYTE: usize = 1; // TODO: consider architecture dependency in the future - pub const SIZE_OF_POINTER: usize = 4; + pub const SIZE_OF_POINTER: usize = 8; pub const SIZE_OF_CHAR: usize = 1; pub const SIZE_OF_SHORT: usize = 2; diff --git a/src/opt/simplify_cfg.rs b/src/opt/simplify_cfg.rs index 12eb1e8..e147a31 100644 --- a/src/opt/simplify_cfg.rs +++ b/src/opt/simplify_cfg.rs @@ -2,8 +2,12 @@ use crate::ir::*; use crate::opt::FunctionPass; use crate::*; -pub type SimplifyCfg = - FunctionPass>; +pub type SimplifyCfg = FunctionPass< + Repeat<( + SimplifyCfgConstProp, + (SimplifyCfgReach, (SimplifyCfgMerge, SimplifyCfgEmpty)), + )>, +>; /// Simplifies block exits by propagating constants. #[derive(Default)] diff --git a/src/tests.rs b/src/tests.rs index 51cc52f..f479ac0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -220,3 +220,139 @@ pub fn test_opt, P2: AsRef, O: Optimize ir, + Err(irgen_error) => panic!("{}", irgen_error), + }; + + let asm = Asmgen::default() + .translate(&ir) + .expect("fail to create riscv assembly code"); + let asm_path = path.with_extension("S").as_path().display().to_string(); + let mut buffer = File::create(Path::new(&asm_path)).expect("need to success creating file"); + write(&asm, &mut buffer).unwrap(); + + // Link to an RISC-V executable + let bin_path = path.with_extension("asm").as_path().display().to_string(); + if !Command::new("riscv64-linux-gnu-gcc-10") + .args(&["-static", &asm_path, "-o", &bin_path]) + .stderr(Stdio::null()) + .status() + .unwrap() + .success() + { + ::std::process::exit(SKIP_TEST); + } + + Command::new("rm") + .arg(asm_path) + .status() + .expect("failed to remove assembly code file"); + + // Emulate the executable + let mut child = Command::new("qemu-riscv64-static") + .args(&[&bin_path]) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to execute the compiled executable"); + + Command::new("rm") + .arg(bin_path) + .status() + .expect("failed to remove compiled executable"); + + let status = some_or!( + child + .wait_timeout_ms(500) + .expect("failed to obtain exit status from child process"), + { + println!("timeout occurs"); + child.kill().unwrap(); + child.wait().unwrap(); + ::std::process::exit(SKIP_TEST); + } + ); + + if child + .stderr + .expect("`stderr` of `child` must be `Some`") + .bytes() + .next() + .is_some() + { + println!("error occurs"); + ::std::process::exit(SKIP_TEST); + } + + let kecc_status = some_or_exit!(status.code(), SKIP_TEST); + + println!("gcc: {}, kecc: {}", gcc_status, kecc_status); + assert_eq!(gcc_status, kecc_status); +} diff --git a/tests/test_examples.rs b/tests/test_examples.rs index 0cc7d75..00c5298 100644 --- a/tests/test_examples.rs +++ b/tests/test_examples.rs @@ -101,3 +101,9 @@ fn test_examples_deadcode() { &mut Deadcode::default(), ); } + +#[test] +#[ignore] +fn test_examples_asmgen() { + test_dir(Path::new("examples/asm"), &OsStr::new("c"), test_asmgen); +}