diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75503ba9..bf7cbcee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ jobs: - name: Run Tests run: | PATH=$(realpath uxn11/bin):$PATH ./build/btest -xt *darwin + - name: Run Tests With Opts + run: | + PATH=$(realpath uxn11/bin):$PATH ./build/btest -xt *darwin -O macos-aarch64: runs-on: macos-latest steps: @@ -37,6 +40,8 @@ jobs: run: make -B - name: Run Tests run: ./build/btest -t gas-aarch64-darwin + - name: Run Tests With Opts + run: ./build/btest -t gas-aarch64-darwin -O macos-x86_64: runs-on: macos-13 steps: @@ -48,3 +53,5 @@ jobs: run: make -B - name: Run Tests run: ./build/btest -t gas-x86_64-darwin + - name: Run Tests With Opts + run: ./build/btest -t gas-x86_64-darwin -O diff --git a/.gitignore b/.gitignore index d1638636..7689a97f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -build/ \ No newline at end of file +build/ + +# used for rust-analyzer +rust-project.json diff --git a/Makefile b/Makefile index 5a81dd59..da6e218d 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ CRUST_FLAGS=-g --edition 2021 -C opt-level=0 -C panic="abort" RSS=\ $(SRC)/arena.rs \ + $(SRC)/opt.rs \ $(SRC)/b.rs \ $(SRC)/crust.rs \ $(SRC)/flag.rs \ diff --git a/src/b.rs b/src/b.rs index 5a9723f0..a6e4a6bf 100644 --- a/src/b.rs +++ b/src/b.rs @@ -35,6 +35,7 @@ pub mod codegen; pub mod runner; pub mod lexer; pub mod targets; +pub mod opt; use core::ffi::*; use core::mem::zeroed; @@ -214,7 +215,7 @@ pub unsafe fn define_goto_label(c: *mut Compiler, name: *const c_char, loc: Loc, Some(()) } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum Arg { /// Bogus value of an Arg. /// @@ -322,13 +323,13 @@ impl Binop { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub struct AsmStmt { line: *const c_char, loc: Loc, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum Op { Bogus, UnaryNot {result: usize, arg: Arg}, @@ -343,7 +344,8 @@ pub enum Op { Label {label: usize}, JmpLabel {label: usize}, JmpIfNotLabel {label: usize, arg: Arg}, - Return {arg: Option}, + SetRetReg {arg: Arg}, + Return, } #[derive(Clone, Copy)] @@ -846,12 +848,13 @@ pub unsafe fn compile_statement(l: *mut Lexer, c: *mut Compiler) -> Option<()> { Token::Return => { get_and_expect_tokens(l, &[Token::SemiColon, Token::OParen])?; if (*l).token == Token::SemiColon { - push_opcode(Op::Return {arg: None}, (*l).loc, c); + push_opcode(Op::Return, (*l).loc, c); } else if (*l).token == Token::OParen { let (arg, _) = compile_expression(l, c)?; get_and_expect_token_but_continue(l, c, Token::CParen)?; get_and_expect_token_but_continue(l, c, Token::SemiColon)?; - push_opcode(Op::Return {arg: Some(arg)}, (*l).loc, c); + push_opcode(Op::SetRetReg { arg }, (*l).loc, c); + push_opcode(Op::Return, (*l).loc, c); } else { unreachable!(); } @@ -973,6 +976,7 @@ pub struct Func { body: Array, params_count: usize, auto_vars_count: usize, + label_count: usize, } #[derive(Clone, Copy)] @@ -1125,6 +1129,16 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { } } compile_statement(l, c)?; + // setup function epilogue + if let Some(last_op) = da_last_mut(&mut (*c).func_body) { + if (*last_op).opcode != Op::Return { + push_opcode(Op::SetRetReg { arg: Arg::Literal(0) }, (*l).loc, c); + push_opcode(Op::Return, (*l).loc, c); + } + } else { + push_opcode(Op::SetRetReg { arg: Arg::Literal(0) }, (*l).loc, c); + push_opcode(Op::Return, (*l).loc, c); + } scope_pop(&mut (*c).vars); // end function scope for i in 0..(*c).func_gotos.count { @@ -1144,6 +1158,7 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { body: (*c).func_body, params_count, auto_vars_count: (*c).auto_vars_ator.max, + label_count: (*c).op_label_count, }); (*c).func_body = zeroed(); (*c).func_goto_labels.count = 0; @@ -1286,6 +1301,7 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> { let output_path = flag_str(c!("o"), ptr::null(), c!("Output path")); let run = flag_bool(c!("run"), false, c!("Run the compiled program (if applicable for the target)")); let help = flag_bool(c!("help"), false, c!("Print this help message")); + let opt = flag_bool(c!("O"), false, c!("Enable optimizations")); let linker = flag_list(c!("L"), c!("Append a flag to the linker of the target platform")); let nostdlib = flag_bool(c!("nostdlib"), false, c!("Do not link with standard libraries like libb and/or libc on some platforms")); let ir = flag_bool(c!("ir"), false, c!("Instead of compiling, dump the IR of the program to stdout")); @@ -1409,6 +1425,10 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> { return None } + if *opt { + opt::optimize(&mut c); + } + let garbage_base = if (*output_path).is_null() { get_garbage_base(*input_paths.items, target)? } else { diff --git a/src/btest.rs b/src/btest.rs index f8f1a084..1f1efab7 100644 --- a/src/btest.rs +++ b/src/btest.rs @@ -160,7 +160,7 @@ pub struct Report { pub unsafe fn execute_test( // Inputs - test_folder: *const c_char, name: *const c_char, target: Target, + test_folder: *const c_char, name: *const c_char, target: Target, opt: bool, // Outputs cmd: *mut Cmd, sb: *mut String_Builder, ) -> Option { @@ -185,6 +185,9 @@ pub unsafe fn execute_test( c!("-t"), target.name(), c!("-o"), program_path, } + if opt { + da_append(cmd, c!("-O")); + } if !cmd_run_sync_and_reset(cmd) { return Some(Outcome::BuildFail); } @@ -300,7 +303,7 @@ pub unsafe fn matches_glob(pattern: *const c_char, text: *const c_char) -> Optio pub unsafe fn record_tests( // Inputs - test_folder: *const c_char, cases: *const [*const c_char], targets: *const [Target], bat: *mut Bat, + test_folder: *const c_char, cases: *const [*const c_char], targets: *const [Target], bat: *mut Bat, opt: bool, // Outputs cmd: *mut Cmd, sb: *mut String_Builder, reports: *mut Array, stats_by_target: *mut Array, @@ -329,7 +332,7 @@ pub unsafe fn record_tests( TestState::Enabled => { let outcome = execute_test( // Inputs - test_folder, case_name, target, + test_folder, case_name, target, opt, // Outputs cmd, sb, )?; @@ -347,7 +350,7 @@ pub unsafe fn record_tests( } else { let outcome = execute_test( // Inputs - test_folder, case_name, target, + test_folder, case_name, target, opt, // Outputs cmd, sb, )?; @@ -514,7 +517,7 @@ pub unsafe fn save_bat_to_json_file( pub unsafe fn replay_tests( // TODO: The Inputs and the Outputs want to be their own entity. But what should they be called? // Inputs - test_folder: *const c_char, cases: *const [*const c_char], targets: *const [Target], bat: Bat, + test_folder: *const c_char, cases: *const [*const c_char], targets: *const [Target], bat: Bat, opt: bool, // Outputs cmd: *mut Cmd, sb: *mut String_Builder, reports: *mut Array, stats_by_target: *mut Array, jim: *mut Jim, ) -> Option<()> { @@ -542,7 +545,7 @@ pub unsafe fn replay_tests( TestState::Enabled => { let outcome = execute_test( // Inputs - test_folder, case_name, target, + test_folder, case_name, target, opt, // Outputs cmd, sb, )?; @@ -569,7 +572,7 @@ pub unsafe fn replay_tests( } else { let outcome = execute_test( // Inputs - test_folder, case_name, target, + test_folder, case_name, target, opt, // Outputs cmd, sb, )?; @@ -640,6 +643,8 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { let record = flag_bool(c!("record"), false, strdup(temp_sprintf(c!("DEPRECATED! Please use `-%s %s` flag instead."), flag_name(action_flag), Action::Record.name()))); // TODO: memory leak let comment = flag_str(c!("comment"), ptr::null(), strdup(temp_sprintf(c!("Set the comment on disabled test cases when you do `-%s %s`"), flag_name(action_flag), Action::Disable.name()))); // TODO: memory leak + let opt = flag_bool(c!("O"), false, c!("Enable compiler optimizations")); + let test_folder = flag_str(c!("dir"), c!("./tests/"), c!("Test folder")); let help = flag_bool(c!("help"), false, c!("Print this help message")); @@ -815,7 +820,7 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { let mut bat = load_bat_from_json_file_if_exists(json_path, *test_folder, &mut sb, &mut jimp)?; record_tests( // Inputs - *test_folder, da_slice(cases), da_slice(targets), &mut bat, + *test_folder, da_slice(cases), da_slice(targets), &mut bat, *opt, // Outputs &mut cmd, &mut sb, &mut reports, &mut stats_by_target, )?; @@ -825,7 +830,7 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { let bat = load_bat_from_json_file_if_exists(json_path, *test_folder, &mut sb, &mut jimp)?; replay_tests( // Inputs - *test_folder, da_slice(cases), da_slice(targets), bat, + *test_folder, da_slice(cases), da_slice(targets), bat, *opt, // Outputs &mut cmd, &mut sb, &mut reports, &mut stats_by_target, &mut jim, ); diff --git a/src/codegen/gas_aarch64.rs b/src/codegen/gas_aarch64.rs index 62f8047a..b88a4e9a 100644 --- a/src/codegen/gas_aarch64.rs +++ b/src/codegen/gas_aarch64.rs @@ -154,10 +154,10 @@ pub unsafe fn generate_function(name: *const c_char, _name_loc: Loc, params_coun let op = (*body)[i]; match op.opcode { Op::Bogus => unreachable!("bogus-amogus"), - Op::Return {arg} => { - if let Some(arg) = arg { - load_arg_to_reg(arg, c!("x0"), output, op.loc, os); - } + Op::SetRetReg { arg } => { + load_arg_to_reg(arg, c!("x0"), output, op.loc, os); + } + Op::Return => { sb_appendf(output, c!(" add sp, sp, %zu\n"), stack_size); sb_appendf(output, c!(" ldp x29, x30, [sp], 2*8\n")); sb_appendf(output, c!(" ret\n")); @@ -376,10 +376,6 @@ pub unsafe fn generate_function(name: *const c_char, _name_loc: Loc, params_coun }, } } - sb_appendf(output, c!(" mov x0, 0\n")); - sb_appendf(output, c!(" add sp, sp, %zu\n"), stack_size); - sb_appendf(output, c!(" ldp x29, x30, [sp], 2*8\n")); - sb_appendf(output, c!(" ret\n")); } pub unsafe fn generate_funcs(output: *mut String_Builder, funcs: *const [Func], variadics: *const [(*const c_char, Variadic)], os: Os) { diff --git a/src/codegen/gas_x86_64.rs b/src/codegen/gas_x86_64.rs index 2f7ae816..2b56f040 100644 --- a/src/codegen/gas_x86_64.rs +++ b/src/codegen/gas_x86_64.rs @@ -84,10 +84,10 @@ pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_v let op = (*body)[i]; match op.opcode { Op::Bogus => unreachable!("bogus-amogus"), - Op::Return { arg } => { - if let Some(arg) = arg { - load_arg_to_reg(arg, c!("rax"), output, os); - } + Op::SetRetReg { arg } => { + load_arg_to_reg(arg, c!("rax"), output, os); + } + Op::Return => { sb_appendf(output, c!(" movq %%rbp, %%rsp\n")); sb_appendf(output, c!(" popq %%rbp\n")); sb_appendf(output, c!(" ret\n")); @@ -241,10 +241,6 @@ pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_v }, } } - sb_appendf(output, c!(" movq $0, %%rax\n")); - sb_appendf(output, c!(" movq %%rbp, %%rsp\n")); - sb_appendf(output, c!(" popq %%rbp\n")); - sb_appendf(output, c!(" ret\n")); } pub unsafe fn generate_funcs(output: *mut String_Builder, funcs: *const [Func], os: Os) { diff --git a/src/codegen/ir.rs b/src/codegen/ir.rs index 787d38de..ae0aeed7 100644 --- a/src/codegen/ir.rs +++ b/src/codegen/ir.rs @@ -31,12 +31,13 @@ pub unsafe fn dump_arg(output: *mut String_Builder, arg: Arg) { pub unsafe fn dump_op(op: OpWithLocation, output: *mut String_Builder) { match op.opcode { Op::Bogus => unreachable!("bogus-amogus"), - Op::Return {arg} => { - sb_appendf(output, c!(" return ")); - if let Some(arg) = arg { - dump_arg(output, arg); - } + Op::SetRetReg { arg } => { + sb_appendf(output, c!(" ret_reg = ")); + dump_arg(output, arg); sb_appendf(output, c!("\n")); + } + Op::Return => { + sb_appendf(output, c!(" return\n")); }, Op::Store{index, arg} => { sb_appendf(output, c!(" store deref[%zu], "), index); diff --git a/src/codegen/mos6502.rs b/src/codegen/mos6502.rs index a6821b94..854d8f82 100644 --- a/src/codegen/mos6502.rs +++ b/src/codegen/mos6502.rs @@ -756,15 +756,17 @@ pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_v let op = (*body)[i]; match op.opcode { Op::Bogus => unreachable!("bogus-amogus"), - Op::Return {arg} => { - if let Some(arg) = arg { - load_arg(arg, op.loc, out, asm); + Op::SetRetReg { arg } => { + load_arg(arg, op.loc, out, asm); + } + Op::Return => { + if stack_size > 0 { + // seriously... we don't have enough registers to save A to... + instr8(out, STA, ZP, ZP_TMP_0); + add_sp(out, stack_size, asm); + instr8(out, LDA, ZP, ZP_TMP_0); } - - // jump to ret statement - instr0(out, JMP, ABS); - add_reloc(out, RelocationKind::AddressAbs - {idx: *op_addresses.items.add(body.len())}, asm); + instr(out, RTS); }, Op::Store {index, arg} => { load_auto_var(out, index, asm); @@ -1268,20 +1270,6 @@ pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_v }, } } - - instr8(out, LDA, IMM, 0); - instr(out, TAY); - - let addr_idx = *op_addresses.items.add(body.len()); - *(*asm).addresses.items.add(addr_idx) = (*out).count as u16; - - if stack_size > 0 { - // seriously... we don't have enough registers to save A to... - instr8(out, STA, ZP, ZP_TMP_0); - add_sp(out, stack_size, asm); - instr8(out, LDA, ZP, ZP_TMP_0); - } - instr(out, RTS); } pub unsafe fn generate_funcs(out: *mut String_Builder, funcs: *const [Func], asm: *mut Assembler) { diff --git a/src/codegen/uxn.rs b/src/codegen/uxn.rs index 73e8327d..d2c8c07d 100644 --- a/src/codegen/uxn.rs +++ b/src/codegen/uxn.rs @@ -482,15 +482,12 @@ pub unsafe fn generate_function(name: *const c_char, name_loc: Loc, params_count write_op(output, UxnOp::JCI); write_label_rel(output, *labels.items.add(label), assembler, 0); } - Op::Return {arg} => { + Op::SetRetReg { arg } => { // Put return value in the FIRST_ARG - if let Some(arg) = arg { - load_arg(arg, op.loc, output, assembler); - } else { - write_lit2(output, 0); - } + load_arg(arg, op.loc, output, assembler); write_lit_stz2(output, FIRST_ARG); - + } + Op::Return => { // restore SP from BP write_lit_ldz2(output, BP); write_lit_stz2(output, SP); @@ -519,26 +516,6 @@ pub unsafe fn generate_function(name: *const c_char, name_loc: Loc, params_count } free(labels.items); - - // return value is 0 - write_lit2(output, 0); - write_lit_stz2(output, FIRST_ARG); - - // restore SP from BP - write_lit_ldz2(output, BP); - write_lit_stz2(output, SP); - - // pop BP from stack - write_lit_ldz2(output, SP); - write_op(output, UxnOp::LDA2); - write_lit_stz2(output, BP); - write_lit_ldz2(output, SP); - write_lit2(output, 2); - write_op(output, UxnOp::ADD2); - write_lit_stz2(output, SP); - - // return - write_op(output, UxnOp::JMP2r); } pub unsafe fn write_op(output: *mut String_Builder, op: UxnOp) { @@ -552,14 +529,14 @@ pub unsafe fn write_byte(output: *mut String_Builder, byte: u8) { pub unsafe fn write_label_rel(output: *mut String_Builder, label: usize, a: *mut Assembler, offset: usize) { da_append(&mut (*a).patches, Patch{ kind: PatchKind::UpperRelative, - label: label, + label, addr: (*output).count as u16, offset: offset as u16, }); write_byte(output, 0xff); da_append(&mut (*a).patches, Patch{ kind: PatchKind::LowerRelative, - label: label, + label, addr: (*output).count as u16, offset: offset as u16, }); @@ -569,14 +546,14 @@ pub unsafe fn write_label_rel(output: *mut String_Builder, label: usize, a: *mut pub unsafe fn write_label_abs(output: *mut String_Builder, label: usize, a: *mut Assembler, offset: usize) { da_append(&mut (*a).patches, Patch{ kind: PatchKind::UpperAbsolute, - label: label, + label, addr: (*output).count as u16, offset: offset as u16, }); write_byte(output, 0xff); da_append(&mut (*a).patches, Patch{ kind: PatchKind::LowerAbsolute, - label: label, + label, addr: (*output).count as u16, offset: offset as u16, }); diff --git a/src/lexer.rs b/src/lexer.rs index 926d6472..253454f3 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -3,7 +3,7 @@ use core::mem::zeroed; use crate::nob::*; use crate::crust::libc::*; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub struct Loc { pub input_path: *const c_char, pub line_number: c_int, diff --git a/src/nob.rs b/src/nob.rs index d175c74f..370eda3d 100644 --- a/src/nob.rs +++ b/src/nob.rs @@ -3,7 +3,7 @@ use core::slice; use crate::crust::libc; #[repr(C)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub struct Array { pub items: *mut T, pub count: usize, diff --git a/src/opt.rs b/src/opt.rs new file mode 100644 index 00000000..82c31bbd --- /dev/null +++ b/src/opt.rs @@ -0,0 +1,28 @@ +use crate::*; + +pub unsafe fn optimize(c: *mut Compiler) { + for i in 0..(*c).funcs.count { + let func = (*c).funcs.items.add(i); + merge_returns_pass(func); + } +} + +pub unsafe fn merge_returns_pass(func: *mut Func) { + for i in 0..(*func).body.count.saturating_add_signed(-1) { + let op = (*func).body.items.add(i); + let opcode = (*op).opcode; + + if opcode == Op::Return { + (*op).opcode = Op::JmpLabel { label: (*func).label_count }; + } + } + + if let Some(last) = da_last_mut(&mut (*func).body) { + if (*last).opcode != Op::Return { + fprintf(stderr(), c!("Incorrectly generated function %s: last op must be return"), (*func).name); + abort(); + } + (*last).opcode = Op::Label { label: (*func).label_count }; + da_append(&mut (*func).body, OpWithLocation { opcode: Op::Return, loc: (*last).loc }); + } +} diff --git a/tests.json b/tests.json index 5a05646b..4a02bc47 100644 --- a/tests.json +++ b/tests.json @@ -191,37 +191,37 @@ }, "return": { "gas-x86_64-windows": { - "expected_stdout": "69\r\n", + "expected_stdout": "69\r\n69\r\n", "state": "Enabled", "comment": "" }, "gas-x86_64-linux": { - "expected_stdout": "69\n", + "expected_stdout": "69\n69\n", "state": "Enabled", "comment": "" }, "gas-aarch64-linux": { - "expected_stdout": "69\n", + "expected_stdout": "69\n69\n", "state": "Enabled", "comment": "" }, "gas-aarch64-darwin": { - "expected_stdout": "69\n", + "expected_stdout": "69\n69\n", "state": "Enabled", "comment": "" }, "uxn": { - "expected_stdout": "69\n", + "expected_stdout": "69\n69\n", "state": "Enabled", "comment": "" }, "6502": { - "expected_stdout": "69\r\n", + "expected_stdout": "69\r\n69\r\n", "state": "Enabled", "comment": "" }, "gas-x86_64-darwin": { - "expected_stdout": "69\n", + "expected_stdout": "69\n69\n", "state": "Enabled", "comment": "" } diff --git a/tests/return.b b/tests/return.b index 6791727c..79667ed9 100644 --- a/tests/return.b +++ b/tests/return.b @@ -4,10 +4,20 @@ nop() { add(a, b) { return (a + b); + return; +} + +try_ret() { + goto skip; + return (-1); +skip: + return (69); } main() { extrn printf; nop(); + printf("%d\n", try_ret()); printf("%d\n", add(34, 35)); + return (0); }