From ef3e8cb503a81437bcfd2056efb3f25cc94c2a91 Mon Sep 17 00:00:00 2001 From: kyon4ik Date: Tue, 8 Jul 2025 12:26:08 +0500 Subject: [PATCH 1/6] ignore rust-project.json --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 6aba81f89a3537429e626d9ad802ad190923616d Mon Sep 17 00:00:00 2001 From: kyon4ik Date: Tue, 8 Jul 2025 16:01:17 +0500 Subject: [PATCH 2/6] Remove redundant returns --- src/b.rs | 19 +++++++++++++++++-- src/codegen/fasm_x86_64.rs | 4 ---- src/codegen/gas_aarch64.rs | 4 ---- src/codegen/gas_x86_64.rs | 4 ---- src/codegen/mos6502.rs | 25 +++++++------------------ src/codegen/uxn.rs | 28 ++++------------------------ 6 files changed, 28 insertions(+), 56 deletions(-) diff --git a/src/b.rs b/src/b.rs index 5fbdb0c0..ef04a2ac 100644 --- a/src/b.rs +++ b/src/b.rs @@ -824,12 +824,14 @@ 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::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); + push_opcode(Op::JmpLabel { label: (*c).return_label }, (*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::AutoAssign { index: (*c).return_auto, arg }, (*l).loc, c); + push_opcode(Op::JmpLabel { label: (*c).return_label }, (*l).loc, c); } else { unreachable!(); } @@ -997,6 +999,8 @@ pub struct Compiler { pub variadics: Array<(*const c_char, Variadic)>, pub globals: Array, pub asm_funcs: Array, + pub return_label: usize, + pub return_auto: usize, /// Arena into which the Compiler allocates all the names and /// objects that need to live for the duration of the /// compilation. Even if some object/names don't need to live that @@ -1102,7 +1106,18 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { } } } + (*c).return_auto = allocate_auto_var(&mut (*c).auto_vars_ator); + (*c).return_label = allocate_label_index(c); compile_statement(l, c)?; + if let Some(last_op) = da_last(&(*c).func_body) { + if !matches!((*last_op).opcode, Op::JmpLabel { .. }) { + push_opcode(Op::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); + } + } else { + push_opcode(Op::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); + } + push_opcode(Op::Label { label: (*c).return_label }, (*l).loc, c); + push_opcode(Op::Return { arg: Some(Arg::AutoVar((*c).return_auto)) }, (*l).loc, c); scope_pop(&mut (*c).vars); // end function scope for i in 0..(*c).func_gotos.count { diff --git a/src/codegen/fasm_x86_64.rs b/src/codegen/fasm_x86_64.rs index 3ab836d7..ce55528e 100644 --- a/src/codegen/fasm_x86_64.rs +++ b/src/codegen/fasm_x86_64.rs @@ -271,10 +271,6 @@ pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_v }, } } - sb_appendf(output, c!(" mov rax, 0\n")); - sb_appendf(output, c!(" mov rsp, rbp\n")); - sb_appendf(output, c!(" pop 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/gas_aarch64.rs b/src/codegen/gas_aarch64.rs index 62f8047a..ebfb0567 100644 --- a/src/codegen/gas_aarch64.rs +++ b/src/codegen/gas_aarch64.rs @@ -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..f32b78ef 100644 --- a/src/codegen/gas_x86_64.rs +++ b/src/codegen/gas_x86_64.rs @@ -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/mos6502.rs b/src/codegen/mos6502.rs index 6608419e..ea83948e 100644 --- a/src/codegen/mos6502.rs +++ b/src/codegen/mos6502.rs @@ -762,10 +762,13 @@ pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_v load_arg(arg, op.loc, out, asm); } - // jump to ret statement - instr0(out, JMP, ABS); - add_reloc(out, RelocationKind::AddressAbs - {idx: *op_addresses.items.add(body.len())}, asm); + 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); }, Op::Store {index, arg} => { load_auto_var(out, index, asm); @@ -1269,20 +1272,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 02f3f8ee..2465c709 100644 --- a/src/codegen/uxn.rs +++ b/src/codegen/uxn.rs @@ -519,26 +519,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 +532,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 +549,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, }); From 2e12e5bf2bd3d3ac2539f850bfd103263e83c3cf Mon Sep 17 00:00:00 2001 From: kyon4ik Date: Tue, 8 Jul 2025 17:45:56 +0500 Subject: [PATCH 3/6] Remove unused jump --- src/b.rs | 16 +++++++++------- src/lexer.rs | 2 +- src/nob.rs | 2 +- tests.json | 18 +++++++++--------- tests/return.b | 10 ++++++++++ 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/b.rs b/src/b.rs index ef04a2ac..cc27f295 100644 --- a/src/b.rs +++ b/src/b.rs @@ -195,7 +195,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. /// @@ -303,13 +303,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}, @@ -824,7 +824,6 @@ 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::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); push_opcode(Op::JmpLabel { label: (*c).return_label }, (*l).loc, c); } else if (*l).token == Token::OParen { let (arg, _) = compile_expression(l, c)?; @@ -1109,14 +1108,17 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { (*c).return_auto = allocate_auto_var(&mut (*c).auto_vars_ator); (*c).return_label = allocate_label_index(c); compile_statement(l, c)?; - if let Some(last_op) = da_last(&(*c).func_body) { - if !matches!((*last_op).opcode, Op::JmpLabel { .. }) { + // setup function epilogue + if let Some(last_op) = da_last_mut(&mut (*c).func_body) { + if (*last_op).opcode == (Op::JmpLabel { label: (*c).return_label }) { + *last_op = OpWithLocation { opcode: Op::Label { label: (*c).return_label }, loc: (*l).loc }; + } else { push_opcode(Op::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); + push_opcode(Op::Label { label: (*c).return_label }, (*l).loc, c); } } else { push_opcode(Op::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); } - push_opcode(Op::Label { label: (*c).return_label }, (*l).loc, c); push_opcode(Op::Return { arg: Some(Arg::AutoVar((*c).return_auto)) }, (*l).loc, c); scope_pop(&mut (*c).vars); // end function scope 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 3ded10db..14c16a42 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/tests.json b/tests.json index 72e33671..78bf22bf 100644 --- a/tests.json +++ b/tests.json @@ -330,47 +330,47 @@ }, "return": { "fasm-x86_64-windows": { - "expected_stdout": "69\r\n", + "expected_stdout": "69\r\n69\r\n", "state": "Enabled", "comment": "" }, "fasm-x86_64-linux": { - "expected_stdout": "69\n", + "expected_stdout": "69\n69\n", "state": "Enabled", "comment": "" }, "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); } From f17aa3d44d12f5adab55f2173c2c43f57dbb0e1b Mon Sep 17 00:00:00 2001 From: kyon4ik Date: Wed, 9 Jul 2025 19:50:44 +0500 Subject: [PATCH 4/6] Resolve conflicts --- .github/workflows/ci.yml | 2 +- Makefile | 3 - README.md | 13 +- docs/btest.md | 10 +- examples/raylib.b | 3 +- examples/seq.b | 2 +- examples/snake.b | 2 +- libb/README.md | 2 +- libb/fasm-x86_64-linux.b | 7 - libb/fasm-x86_64-windows.b | 7 - src/b.rs | 120 +------ src/btest.rs | 146 +++++++-- src/codegen/fasm_x86_64.rs | 381 ---------------------- src/codegen/mod.rs | 1 - src/jimp.rs | 1 + src/runner/fasm_x86_64_linux.rs | 27 -- src/runner/fasm_x86_64_windows.rs | 26 -- src/runner/mod.rs | 2 - src/targets.rs | 4 - tests.json | 491 ++--------------------------- tests/asm_fasm_x86_64_linux.b | 13 - tests/asm_func_fasm_x86_64_linux.b | 10 - tests/statements.b | 10 + 23 files changed, 183 insertions(+), 1100 deletions(-) delete mode 100644 libb/fasm-x86_64-linux.b delete mode 100644 libb/fasm-x86_64-windows.b delete mode 100644 src/codegen/fasm_x86_64.rs delete mode 100644 src/runner/fasm_x86_64_linux.rs delete mode 100644 src/runner/fasm_x86_64_windows.rs delete mode 100644 tests/asm_fasm_x86_64_linux.b delete mode 100644 tests/asm_func_fasm_x86_64_linux.b create mode 100644 tests/statements.b diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8efc6f0d..102f5b53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install -qq -y clang make fasm mingw-w64 wine64 gcc-aarch64-linux-gnu qemu-user + sudo apt-get install -qq -y clang make mingw-w64 wine64 gcc-aarch64-linux-gnu qemu-user git clone https://git.sr.ht/~rabbits/uxn11 cd uxn11 make cli diff --git a/Makefile b/Makefile index d17443ad..5a81dd59 100644 --- a/Makefile +++ b/Makefile @@ -23,15 +23,12 @@ RSS=\ $(SRC)/targets.rs \ $(SRC)/jim.rs \ $(SRC)/jimp.rs \ - $(SRC)/codegen/fasm_x86_64.rs \ $(SRC)/codegen/gas_aarch64.rs \ $(SRC)/codegen/gas_x86_64.rs \ $(SRC)/codegen/mos6502.rs \ $(SRC)/codegen/uxn.rs \ $(SRC)/codegen/ir.rs \ $(SRC)/codegen/mod.rs \ - $(SRC)/runner/fasm_x86_64_linux.rs \ - $(SRC)/runner/fasm_x86_64_windows.rs \ $(SRC)/runner/gas_x86_64_linux.rs \ $(SRC)/runner/gas_x86_64_windows.rs \ $(SRC)/runner/gas_x86_64_darwin.rs \ diff --git a/README.md b/README.md index 3c9578ff..7d32c20d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # B Programming Language > [!WARNING] -> Compiler is not fully implemented yet. +> The Compiler is not fully implemented yet. Plus on its own it's probably not useful for any serious Software Development. It is fun for Recreational Programming though.

@@ -11,14 +11,14 @@ Logo by Strawberry 🍓

-Compiler for the B Programming Language implemented in [Crust](https://github.com/tsoding/crust) +Compiler for the [B Programming Language](https://en.wikipedia.org/wiki/B_(programming_language)) implemented in [Crust](https://github.com/tsoding/crust). ## Dependencies - [Rust](https://www.rust-lang.org/) - the compiler is written in it; -- [fasm](https://flatassembler.net/) - used as the compiler backend; +- [GCC](https://gcc.gnu.org/) or [Clang](https://clang.llvm.org/) (whatever surves as the `cc` on your POSIX platform) - the `x86_64` and `aarch64` targets generate assembly and pass it to `cc` to assemble and link. - + ## Quick Start @@ -34,6 +34,5 @@ Find the project documentation at [./docs/](./docs/). - https://en.wikipedia.org/wiki/B_(programming_language) - https://www.nokia.com/bell-labs/about/dennis-m-ritchie/kbman.html -- https://github.com/tsoding/good_training_language -- https://www.felixcloutier.com/x86/ -- https://www.scs.stanford.edu/~zyedidia/arm64/ +- https://www.nokia.com/bell-labs/about/dennis-m-ritchie/bref.html +- https://www.nokia.com/bell-labs/about/dennis-m-ritchie/btut.html diff --git a/docs/btest.md b/docs/btest.md index 0dcfe036..5bbf2e92 100644 --- a/docs/btest.md +++ b/docs/btest.md @@ -16,13 +16,13 @@ It doesn't crash when it encounters errors, it just collects the statuses of the If you want to test only on a specific platform you can supply the flag `-t`. ```console -$ ./build/btest -t fasm-x86_64-linux +$ ./build/btest -t gas-x86_64-linux ``` You can supply several platforms. ```console -$ ./build/btest -t fasm-x86_64-linux -t uxn +$ ./build/btest -t gas-x86_64-linux -t uxn ``` If you want to run a specific test case you can supply flag `-c`. @@ -40,7 +40,7 @@ $ ./build/btest -c upper -c vector And of course you can combine both `-c` and `-t` flags to slice the Test Matrix however you want. ```console -$ ./build/btest -c upper -c vector -t fasm-x86_64-linux -t uxn +$ ./build/btest -c upper -c vector -t gas-x86_64-linux -t uxn ``` Both flags accept [glob](https://en.wikipedia.org/wiki/Glob_(programming)) patterns. @@ -52,7 +52,7 @@ $ ./build/btest -t *linux -c *linux If you want to exclude a specific platform you can supply the flag `-xt`. ```console -$ ./build/btest -xt fasm-x86_64-linux +$ ./build/btest -xt gas-x86_64-linux ``` `-xt` also accepts [glob](https://en.wikipedia.org/wiki/Glob_(programming)) patterns. @@ -66,3 +66,5 @@ $ ./build/btest -xt *linux ```console $ ./build/btest -xt *linux -xc asm* ``` + + diff --git a/examples/raylib.b b/examples/raylib.b index 732954db..7831462a 100644 --- a/examples/raylib.b +++ b/examples/raylib.b @@ -7,7 +7,7 @@ // $ b raylib.b -L -L/path/to/raylib-version_linux_amd64/lib/ -L -l:libraylib.a -L -lm -run // // # Windows mingw32-w64 -// > b -t fasm-x86_64-windows raylib.b -L -L$HOME/opt/raylib-version_win64_mingw-w64/lib/ -L -l:libraylib.a -L -lwinmm -L -lgdi32 -run +// > b -t gas-x86_64-windows raylib.b -L -L$HOME/opt/raylib-version_win64_mingw-w64/lib/ -L -l:libraylib.a -L -lwinmm -L -lgdi32 -run W; @@ -49,7 +49,6 @@ update_obj(obj) { draw_obj(obj) DrawRectangle(obj[OBJ_X], obj[OBJ_Y], 100, 100, COLORS[obj[OBJ_C]]); -// TODO: Crashing during runtime when compiled with -t fasm-x86_64-windows and running via wine main() { W = &0[1]; diff --git a/examples/seq.b b/examples/seq.b index 1d370f5f..8c9c5da1 100644 --- a/examples/seq.b +++ b/examples/seq.b @@ -1,6 +1,6 @@ // -*- mode: simpc -*- -// TODO: doesn't work on fasm_x86_64_windows target due to linking error when using stderr +// TODO: doesn't work on gas-x86_64-windows target due to linking error when using stderr main(argc, argv) { extrn printf, fprintf, stderr, atoi; diff --git a/examples/snake.b b/examples/snake.b index 06d25284..5d38dead 100644 --- a/examples/snake.b +++ b/examples/snake.b @@ -403,6 +403,6 @@ main() { disable_raw_mode(); } -// TODO: does not work on fasm_x86_64_windows due to using POSIX stuff +// TODO: does not work on gas-x86_64-windows due to using POSIX stuff // Would be nice to research how hard/easy it is to add the Window support. // But this is not critical. diff --git a/libb/README.md b/libb/README.md index 1f4824cb..12bcefd5 100644 --- a/libb/README.md +++ b/libb/README.md @@ -2,7 +2,7 @@ Here we describe the bare minium of the functionality that libb must provide. It may provide more on some platforms if necessary. -Some platforms like `fasm-x86_64-linux`, `gas-aarch64-linux`, etc also link with libc, which means some of the functionality of libb is covered by libc. For platforms that do not link with libc (like `uxn`, `6502`, etc) the required functionality should be implemented from scratch. +Some platforms like `gas-x86_64-linux`, `gas-aarch64-linux`, etc also link with libc, which means some of the functionality of libb is covered by libc. For platforms that do not link with libc (like `uxn`, `6502`, etc) the required functionality should be implemented from scratch. If you don't want to link with libb (and libc on the platforms where it's available) use the flag `-nostdlib`. diff --git a/libb/fasm-x86_64-linux.b b/libb/fasm-x86_64-linux.b deleted file mode 100644 index c199c5a5..00000000 --- a/libb/fasm-x86_64-linux.b +++ /dev/null @@ -1,7 +0,0 @@ -sx64 __asm__("movsxd rax, edi", "ret"); -char __asm__("xor rax, rax", "mov al, [rdi + rsi]", "ret"); -lchar __asm__("mov [rdi + rsi], dl", "ret"); -extrn printf; -extrn putchar; -extrn getchar; -extrn exit; diff --git a/libb/fasm-x86_64-windows.b b/libb/fasm-x86_64-windows.b deleted file mode 100644 index a058a37e..00000000 --- a/libb/fasm-x86_64-windows.b +++ /dev/null @@ -1,7 +0,0 @@ -sx64 __asm__("movsxd rax, ecx", "ret"); -char __asm__("xor rax, rax", "mov al, [rcx + rdx]", "ret"); -lchar __asm__("mov [rcx + rdx], r8l", "ret"); -extrn printf; -extrn putchar; -extrn getchar; -extrn exit; diff --git a/src/b.rs b/src/b.rs index cc27f295..c6078b7b 100644 --- a/src/b.rs +++ b/src/b.rs @@ -733,6 +733,9 @@ pub unsafe fn compile_statement(l: *mut Lexer, c: *mut Compiler) -> Option<()> { lexer::get_token(l)?; match (*l).token { + Token::SemiColon => { + Some(()) + }, Token::OCurly => { scope_push(&mut (*c).vars); let saved_auto_vars_count = (*c).auto_vars_ator.count; @@ -749,7 +752,7 @@ pub unsafe fn compile_statement(l: *mut Lexer, c: *mut Compiler) -> Option<()> { declare_var(c, name, (*l).loc, Storage::External {name})?; get_and_expect_tokens(l, &[Token::SemiColon, Token::Comma])?; } - Some(()) + compile_statement(l, c) } Token::Auto => { while (*l).token != Token::SemiColon { @@ -774,7 +777,7 @@ pub unsafe fn compile_statement(l: *mut Lexer, c: *mut Compiler) -> Option<()> { get_and_expect_tokens(l, &[Token::SemiColon, Token::Comma])?; } } - Some(()) + compile_statement(l, c) } Token::If => { get_and_expect_token_but_continue(l, c, Token::OParen)?; @@ -1362,11 +1365,6 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> { // Logging what files are actually being compiled so nothing is hidden from the user. // TODO: There should be some sort of -q mode which suppress all the logging like this. - // Including the logging from external tools like fasm, but this is already a bit harder. - // May require some stdout redirecting capabilities of nob.h. - // -q mode might be important for behavioral testing in a style of https://github.com/tsoding/rere.py. - // I do not plan to actually use rere.py in this project since I don't want to depend on yet another language. - // But I do plan to have similar testing tool written in Crust. // // - rexim (2025-06-12 20:18:02) printf(c!("INFO: Compiling files ")); @@ -1694,114 +1692,6 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> { runner::gas_aarch64_darwin::run(&mut cmd, effective_output_path, da_slice(run_args), None)?; } } - Target::Fasm_x86_64_Linux => { - codegen::fasm_x86_64::generate_program(&mut output, &c, targets::Os::Linux); - - let effective_output_path; - if (*output_path).is_null() { - if let Some(base_path) = temp_strip_suffix(*input_paths.items, c!(".b")) { - effective_output_path = base_path; - } else { - effective_output_path = temp_sprintf(c!("%s.out"), *input_paths.items); - } - } else { - effective_output_path = *output_path; - } - - let output_asm_path = temp_sprintf(c!("%s.asm"), garbage_base); - write_entire_file(output_asm_path, output.items as *const c_void, output.count)?; - printf(c!("INFO: Generated %s\n"), output_asm_path); - - if !(cfg!(target_arch = "x86_64") && cfg!(target_os = "linux")) { - // TODO: think how to approach cross-compilation - fprintf(stderr(), c!("ERROR: Cross-compilation of x86_64 linux is not supported for now\n")); - return None; - } - - let output_obj_path = temp_sprintf(c!("%s.o"), garbage_base); - cmd_append! { - &mut cmd, - c!("fasm"), output_asm_path, output_obj_path, - } - if !cmd_run_sync_and_reset(&mut cmd) { return None; } - cmd_append! { - &mut cmd, - c!("cc"), c!("-no-pie"), c!("-o"), effective_output_path, output_obj_path, - } - if *nostdlib { - cmd_append! { - &mut cmd, - c!("-nostdlib"), - } - } - for i in 0..(*linker).count { - cmd_append!{ - &mut cmd, - *(*linker).items.add(i), - } - } - if !cmd_run_sync_and_reset(&mut cmd) { return None; } - if *run { - runner::fasm_x86_64_linux::run(&mut cmd, effective_output_path, da_slice(run_args), None)? - } - } - Target::Fasm_x86_64_Windows => { - codegen::fasm_x86_64::generate_program(&mut output, &c, targets::Os::Windows); - - let base_path; - if (*output_path).is_null() { - if let Some(path) = temp_strip_suffix(*input_paths.items, c!(".b")) { - base_path = path; - } else { - base_path = *input_paths.items; - } - } else { - if let Some(path) = temp_strip_suffix(*output_path, c!(".exe")) { - base_path = path; - } else { - base_path = *output_path; - } - } - - let effective_output_path = temp_sprintf(c!("%s.exe"), base_path); - - let output_asm_path = temp_sprintf(c!("%s.asm"), garbage_base); - write_entire_file(output_asm_path, output.items as *const c_void, output.count)?; - printf(c!("INFO: Generated %s\n"), output_asm_path); - - let cc = if cfg!(target_arch = "x86_64") && cfg!(target_os = "windows") { - c!("cc") - } else { - c!("x86_64-w64-mingw32-gcc") - }; - - let output_obj_path = temp_sprintf(c!("%s.obj"), garbage_base); - cmd_append! { - &mut cmd, - c!("fasm"), output_asm_path, output_obj_path, - } - if !cmd_run_sync_and_reset(&mut cmd) { return None; } - cmd_append! { - &mut cmd, - cc, c!("-no-pie"), c!("-o"), effective_output_path, output_obj_path, - } - if *nostdlib { - cmd_append! { - &mut cmd, - c!("-nostdlib"), - } - } - for i in 0..(*linker).count { - cmd_append!{ - &mut cmd, - *(*linker).items.add(i), - } - } - if !cmd_run_sync_and_reset(&mut cmd) { return None; } - if *run { - runner::fasm_x86_64_windows::run(&mut cmd, effective_output_path, da_slice(run_args), None)?; - } - } Target::Uxn => { codegen::uxn::generate_program(&mut output, &c); diff --git a/src/btest.rs b/src/btest.rs index fd59c9e6..a8079f71 100644 --- a/src/btest.rs +++ b/src/btest.rs @@ -166,8 +166,6 @@ pub unsafe fn execute_test( // TODO: add timeouts for running and building in case they go into infinite loop or something let input_path = temp_sprintf(c!("%s/%s.b"), test_folder, name); let program_path = temp_sprintf(c!("%s/%s.%s"), GARBAGE_FOLDER, name, match target { - Target::Fasm_x86_64_Windows => c!("exe"), - Target::Fasm_x86_64_Linux => c!("fasm-x86_64-linux"), Target::Gas_x86_64_Darwin => c!("gas-x86_64-darwin"), Target::Gas_AArch64_Linux => c!("gas-aarch64-linux"), Target::Gas_AArch64_Darwin => c!("gas-aarch64-darwin"), @@ -188,8 +186,6 @@ pub unsafe fn execute_test( return Some(Outcome::BuildFail); } let run_result = match target { - Target::Fasm_x86_64_Linux => runner::fasm_x86_64_linux::run(cmd, program_path, &[], Some(stdout_path)), - Target::Fasm_x86_64_Windows => runner::fasm_x86_64_windows::run(cmd, program_path, &[], Some(stdout_path)), Target::Gas_AArch64_Linux => runner::gas_aarch64_linux::run(cmd, program_path, &[], Some(stdout_path)), Target::Gas_AArch64_Darwin => runner::gas_aarch64_darwin::run(cmd, program_path, &[], Some(stdout_path)), Target::Gas_x86_64_Linux => runner::gas_x86_64_linux::run(cmd, program_path, &[], Some(stdout_path)), @@ -436,11 +432,12 @@ pub unsafe fn generate_report(reports: *const [Report], stats_by_target: *const type Bat = Array<(*const c_char, Array<(Target, TestConfig)>)>; // (Big Ass Table) pub unsafe fn load_bat_from_json_file_if_exists( - json_path: *const c_char, + json_path: *const c_char, test_folder: *const c_char, sb: *mut String_Builder, jimp: *mut Jimp ) -> Option { let mut bat: Bat = zeroed(); if file_exists(json_path)? { + printf(c!("INFO: loading file %s...\n"), json_path); // TODO: file may stop existing between file_exists() and read_entire_file() cools // It would be much better if read_entire_file() returned the reason of failure so // it's easy to check if it failed due to ENOENT, but that requires significant @@ -453,21 +450,36 @@ pub unsafe fn load_bat_from_json_file_if_exists( jimp_object_begin(jimp)?; while let Some(()) = jimp_object_member(jimp) { let case_name = strdup((*jimp).string); // TODO: memory leak + let case_start = (*jimp).token_start; let mut target_test_config_table: Array<(Target, TestConfig)> = zeroed(); jimp_object_begin(jimp)?; while let Some(()) = jimp_object_member(jimp) { - if let Some(target) = Target::by_name((*jimp).string) { - let test_config: TestConfig = test_config_deserialize(jimp)?; + let target_name = strdup((*jimp).string); // TODO: memory leak + let target = Target::by_name(target_name); + let target_start = (*jimp).token_start; + let test_config: TestConfig = test_config_deserialize(jimp)?; + if let Some(target) = target { da_append(&mut target_test_config_table, (target, test_config)); } else { - jimp_unknown_member(jimp); - return None; + (*jimp).token_start = target_start; + // TODO: memory leak, we are dropping the whole test_config here + jimp_diagf(jimp, c!("WARNING: Unknown target: %s. Ignoring it...\n"), target_name); } } jimp_object_end(jimp)?; - da_append(&mut bat, (case_name, target_test_config_table)); + + let case_path = temp_sprintf(c!("%s/%s.b"), test_folder, case_name); + if file_exists(case_path)? { + da_append(&mut bat, (case_name, target_test_config_table)); + } else { + (*jimp).token_start = case_start; + // TODO: memory leak, we are dropping the whole target_test_config_table here + jimp_diagf(jimp, c!("WARNING: %s does not exist. Ignoring %s test case...\n"), case_path, case_name); + } } jimp_object_end(jimp)?; + } else { + printf(c!("INFO: %s doesn't exist. Nothing to load.\n"), json_path); } Some(bat) } @@ -476,6 +488,7 @@ pub unsafe fn save_bat_to_json_file( json_path: *const c_char, bat: Bat, jim: *mut Jim, ) -> Option<()> { + printf(c!("INFO: saving file %s...\n"), json_path); jim_begin(jim); jim_object_begin(jim); for i in 0..bat.count { @@ -576,7 +589,38 @@ pub unsafe fn replay_tests( Some(()) } +enum_with_order! { + #[derive(Copy, Clone)] + enum Action in ACTION_ORDER { + Replay, + Record, + Prune, + } +} + +impl Action { + unsafe fn name(self) -> *const c_char { + match self { + Self::Replay => c!("replay"), + Self::Record => c!("record"), + Self::Prune => c!("prune"), + } + } + + unsafe fn from_name(name: *const c_char) -> Option { + for i in 0..ACTION_ORDER.len() { + let action = (*ACTION_ORDER)[i]; + if strcmp(name, action.name()) == 0 { + return Some(action) + } + } + None + } +} + pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { + let default_action = Action::Replay; + let target_flags = flag_list(c!("t"), c!("Compilation targets to select for testing. Can be a glob pattern.")); let exclude_target_flags = flag_list(c!("xt"), c!("Compilation targets to exclude from selected ones. Can be a glob pattern")); let list_targets = flag_bool(c!("tlist"), false, c!("Print the list of selected compilation targets.")); @@ -585,9 +629,12 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { let exclude_cases_flags = flag_list(c!("xc"), c!("Test cases to exclude from selected ones. Can be a glob pattern")); let list_cases = flag_bool(c!("clist"), false, c!("Print the list of selected test cases")); + let action_flag = flag_str(c!("a"), default_action.name(), c!("Action to perform. Use -alist to get the list of available actions")); + let list_actions = flag_bool(c!("alist"), false, c!("Print the list of all available actions.")); + 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 test_folder = flag_str(c!("dir"), c!("./tests/"), c!("Test folder")); let help = flag_bool(c!("help"), false, c!("Print this help message")); - let record = flag_bool(c!("record"), false, c!("Record test cases instead of replaying them")); if !flag_parse(argc, argv) { usage(); @@ -600,6 +647,38 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { return Some(()); } + let Some(action) = Action::from_name(*action_flag) else { + fprintf(stderr(), c!("ERROR: unknown action `%s`\n"), *action_flag); + return None; + }; + + let json_path = c!("tests.json"); + + if *list_actions { + fprintf(stderr(), c!("Available actions:\n")); + let mut width = 0; + for i in 0..ACTION_ORDER.len() { + width = cmp::max(width, strlen((*ACTION_ORDER)[i].name())); + } + for i in 0..ACTION_ORDER.len() { + let action = (*ACTION_ORDER)[i]; + match action { + Action::Replay => { + printf(c!(" %*s - Replay the selected Test Matrix slice with expected outputs from %s.\n"), width, action.name(), json_path); + } + Action::Record => { + printf(c!(" %*s - Record the selected Test Matrix slice into %s.\n"), width, action.name(), json_path); + } + Action::Prune => { + printf(c!(" %*s - Prune all the recordings from %s that are outside of the selected Test Matrix slice.\n"), width, action.name(), json_path); + printf(c!(" %*s Useful when you delete targets or test cases. Just delete a target or a test case and\n"), width, c!("")); + printf(c!(" %*s run `%s -%s %s` without any additional flags.\n"), width, c!(""), flag_program_name(), flag_name(action_flag), Action::Prune.name()); + } + }; + } + return Some(()); + } + let mut sb: String_Builder = zeroed(); let mut cmd: Cmd = zeroed(); let mut jim: Jim = zeroed(); @@ -715,24 +794,35 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { if !mkdir_if_not_exists(GARBAGE_FOLDER) { return None; } - let json_path = c!("tests.json"); if *record { - let mut bat = load_bat_from_json_file_if_exists(json_path, &mut sb, &mut jimp)?; - record_tests( - // Inputs - *test_folder, da_slice(cases), da_slice(targets), &mut bat, - // Outputs - &mut cmd, &mut sb, &mut reports, &mut stats_by_target, - )?; - save_bat_to_json_file(json_path, bat, &mut jim)?; - } else { - let bat = load_bat_from_json_file_if_exists(json_path, &mut sb, &mut jimp)?; - replay_tests( - // Inputs - *test_folder, da_slice(cases), da_slice(targets), bat, - // Outputs - &mut cmd, &mut sb, &mut reports, &mut stats_by_target, &mut jim, - ); + fprintf(stderr(), c!("ERROR: Flag `-%s` is DEPRECATED! Please use `-%s %s` instead.\n"), flag_name(record), flag_name(action_flag), Action::Record.name()); + return None + } + + match action { + Action::Record => { + 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, + // Outputs + &mut cmd, &mut sb, &mut reports, &mut stats_by_target, + )?; + save_bat_to_json_file(json_path, bat, &mut jim)?; + } + Action::Replay => { + 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, + // Outputs + &mut cmd, &mut sb, &mut reports, &mut stats_by_target, &mut jim, + ); + } + Action::Prune => { + let bat = load_bat_from_json_file_if_exists(json_path, *test_folder, &mut sb, &mut jimp)?; + save_bat_to_json_file(json_path, bat, &mut jim)?; + } } Some(()) diff --git a/src/codegen/fasm_x86_64.rs b/src/codegen/fasm_x86_64.rs deleted file mode 100644 index ce55528e..00000000 --- a/src/codegen/fasm_x86_64.rs +++ /dev/null @@ -1,381 +0,0 @@ -use core::ffi::*; -use core::cmp; -use crate::{Op, Binop, OpWithLocation, Arg, Func, Global, ImmediateValue, AsmFunc, Compiler, align_bytes}; -use crate::nob::*; -use crate::crust::libc::*; -use crate::targets::Os; - -pub unsafe fn call_arg(arg: Arg, output: *mut String_Builder) { - match arg { - Arg::RefExternal(name) | Arg::External(name) => sb_appendf(output, c!(" call _%s\n"), name), - arg => { - load_arg_to_reg(arg, c!("rax"), output); - sb_appendf(output, c!(" call rax\n")) - }, - }; -} - -pub unsafe fn load_arg_to_reg(arg: Arg, reg: *const c_char, output: *mut String_Builder) { - match arg { - Arg::Deref(index) => { - sb_appendf(output, c!(" mov %s, [rbp-%zu]\n"), reg, index*8); - sb_appendf(output, c!(" mov %s, [%s]\n"), reg, reg) - } - Arg::RefAutoVar(index) => sb_appendf(output, c!(" lea %s, [rbp-%zu]\n"), reg, index*8), - Arg::RefExternal(name) => sb_appendf(output, c!(" lea %s, [_%s]\n"), reg, name), - Arg::External(name) => sb_appendf(output, c!(" mov %s, [_%s]\n"), reg, name), - Arg::AutoVar(index) => sb_appendf(output, c!(" mov %s, [rbp-%zu]\n"), reg, index*8), - Arg::Literal(value) => sb_appendf(output, c!(" mov %s, %ld\n"), reg, value), - Arg::DataOffset(offset) => sb_appendf(output, c!(" mov %s, dat+%zu\n"), reg, offset), - Arg::Bogus => unreachable!("bogus-amogus") - }; -} - -pub unsafe fn generate_function(name: *const c_char, params_count: usize, auto_vars_count: usize, body: *const [OpWithLocation], output: *mut String_Builder, os: Os) { - let stack_size = align_bytes(auto_vars_count*8, 16); - sb_appendf(output, c!("public _%s as '%s'\n"), name, name); - sb_appendf(output, c!("_%s:\n"), name); - sb_appendf(output, c!(" push rbp\n")); - sb_appendf(output, c!(" mov rbp, rsp\n")); - if stack_size > 0 { - sb_appendf(output, c!(" sub rsp, %zu\n"), stack_size); - } - assert!(auto_vars_count >= params_count); - let registers: *const[*const c_char] = match os { - Os::Linux | Os::Darwin => &[c!("rdi"), c!("rsi"), c!("rdx"), c!("rcx"), c!("r8"), c!("r9")], - Os::Windows => &[c!("rcx"), c!("rdx"), c!("r8"), c!("r9")], // https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention - }; - - let mut i = 0; - while i < cmp::min(params_count, registers.len()) { - let reg = (*registers)[i]; - sb_appendf(output, c!(" mov QWORD [rbp-%zu], %s\n"), (i + 1)*8, reg); - i += 1; - } - for j in i..params_count { - match os { - Os::Linux | Os::Darwin => sb_appendf(output, c!(" mov QWORD rax, [rbp+%zu]\n"), ((j - i) + 2)*8), - Os::Windows => sb_appendf(output, c!(" mov QWORD rax, [rbp+%zu]\n"), ((j - i) + 6)*8), - }; - sb_appendf(output, c!(" mov QWORD [rbp-%zu], rax\n"), (j + 1)*8); - } - - for i in 0..body.len() { - 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); - } - sb_appendf(output, c!(" mov rsp, rbp\n")); - sb_appendf(output, c!(" pop rbp\n")); - sb_appendf(output, c!(" ret\n")); - } - Op::Store {index, arg} => { - sb_appendf(output, c!(" mov rax, [rbp-%zu]\n"), index*8); - load_arg_to_reg(arg, c!("rbx"), output); - sb_appendf(output, c!(" mov [rax], rbx\n")); - } - Op::ExternalAssign{name, arg} => { - load_arg_to_reg(arg, c!("rax"), output); - sb_appendf(output, c!(" mov [_%s], rax\n"), name); - }, - Op::AutoAssign{index, arg} => { - load_arg_to_reg(arg, c!("rax"), output); - sb_appendf(output, c!(" mov QWORD [rbp-%zu], rax\n"), index*8); - }, - Op::Negate {result, arg} => { - load_arg_to_reg(arg, c!("rax"), output); - sb_appendf(output, c!(" neg rax\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), result*8); - } - Op::UnaryNot{result, arg} => { - sb_appendf(output, c!(" xor rbx, rbx\n")); - load_arg_to_reg(arg, c!("rax"), output); - sb_appendf(output, c!(" test rax, rax\n")); - sb_appendf(output, c!(" setz bl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rbx\n"), result*8); - }, - Op::Binop {binop, index, lhs, rhs} => { - match binop { - Binop::BitOr => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" or rax, rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::BitAnd => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" and rax, rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::BitShl => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rcx"), output); - sb_appendf(output, c!(" shl rax, cl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::BitShr => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rcx"), output); - sb_appendf(output, c!(" shr rax, cl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::Plus => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" add rax, rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::Minus => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" sub rax, rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::Mod => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" cqo\n")); - sb_appendf(output, c!(" idiv rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - Binop::Div => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" cqo\n")); - sb_appendf(output, c!(" idiv rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::Mult => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" imul rbx\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), index*8); - } - Binop::Less => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" cmp rax, rbx\n")); - sb_appendf(output, c!(" setl dl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - Binop::Greater => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" cmp rax, rbx\n")); - sb_appendf(output, c!(" setg dl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - Binop::Equal => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" cmp rax, rbx\n")); - sb_appendf(output, c!(" sete dl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - Binop::NotEqual => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" cmp rax, rbx\n")); - sb_appendf(output, c!(" setne dl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - Binop::GreaterEqual => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" cmp rax, rbx\n")); - sb_appendf(output, c!(" setge dl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - Binop::LessEqual => { - load_arg_to_reg(lhs, c!("rax"), output); - load_arg_to_reg(rhs, c!("rbx"), output); - sb_appendf(output, c!(" xor rdx, rdx\n")); - sb_appendf(output, c!(" cmp rax, rbx\n")); - sb_appendf(output, c!(" setle dl\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rdx\n"), index*8); - } - } - } - Op::Funcall{result, fun, args} => { - let reg_args_count = cmp::min(args.count, registers.len()); - for i in 0..reg_args_count { - let reg = (*registers)[i]; - load_arg_to_reg(*args.items.add(i), reg, output); - } - - let stack_args_count = args.count - reg_args_count; - let stack_args_size = align_bytes(stack_args_count*8, 16); - if stack_args_count > 0 { - sb_appendf(output, c!(" sub rsp, %zu\n"), stack_args_size); - for i in 0..stack_args_count { - load_arg_to_reg(*args.items.add(reg_args_count + i), c!("rax"), output); - sb_appendf(output, c!(" mov [rsp+%zu], rax\n"), i*8); - } - } - - match os { - Os::Linux | Os::Darwin => { - sb_appendf(output, c!(" mov al, 0\n")); // x86_64 Linux ABI passes the amount of - // floating point args via al. Since B - // does not distinguish regular and - // variadic functions we set al to 0 just - // in case. - call_arg(fun, output); - } - Os::Windows => { - // allocate 32 bytes for "shadow space" - // it must be allocated at the top of the stack after all arguments are pushed - // so we can't allocate it at function prologue - sb_appendf(output, c!(" sub rsp, 32\n")); - call_arg(fun, output); - sb_appendf(output, c!(" add rsp, 32\n")); // deallocate "shadow space" - } - } - if stack_args_count > 0 { - sb_appendf(output, c!(" add rsp, %zu\n"), stack_args_size); - } - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), result*8); - }, - Op::Asm {stmts} => { - for i in 0..stmts.count { - let stmt = *stmts.items.add(i); - sb_appendf(output, c!(" %s\n"), stmt.line); - } - } - Op::Label {label} => { - sb_appendf(output, c!(".label_%zu:\n"), label); - } - Op::JmpLabel {label} => { - sb_appendf(output, c!(" jmp .label_%zu\n"), label); - } - Op::JmpIfNotLabel {label, arg} => { - load_arg_to_reg(arg, c!("rax"), output); - sb_appendf(output, c!(" test rax, rax\n")); - sb_appendf(output, c!(" jz .label_%zu\n"), label); - } - Op::Index {result, arg, offset} => { - load_arg_to_reg(arg, c!("rax"), output); - load_arg_to_reg(offset, c!("rcx"), output); - sb_appendf(output, c!(" lea rax, [rax+rcx*8]\n")); - sb_appendf(output, c!(" mov [rbp-%zu], rax\n"), result * 8); - }, - } - } -} - -pub unsafe fn generate_funcs(output: *mut String_Builder, funcs: *const [Func], os: Os) { - for i in 0..funcs.len() { - generate_function((*funcs)[i].name, (*funcs)[i].params_count, (*funcs)[i].auto_vars_count, da_slice((*funcs)[i].body), output, os); - } -} - -pub unsafe fn generate_extrns(output: *mut String_Builder, extrns: *const [*const c_char], funcs: *const [Func], globals: *const [Global], asm_funcs: *const [AsmFunc]) { - 'skip: for i in 0..extrns.len() { - let name = (*extrns)[i]; - - for j in 0..funcs.len() { - let func = (*funcs)[j]; - if strcmp(func.name, name) == 0 { - continue 'skip - } - } - - for j in 0..globals.len() { - let global = (*globals)[j].name; - if strcmp(global, name) == 0 { - continue 'skip - } - } - - for j in 0..asm_funcs.len() { - let asm_func = (*asm_funcs)[j].name; - if strcmp(asm_func, name) == 0 { - continue 'skip - } - } - - sb_appendf(output, c!("extrn '%s' as _%s\n"), name, name); - } -} - -pub unsafe fn generate_asm_funcs(output: *mut String_Builder, asm_funcs: *const [AsmFunc]) { - for i in 0..asm_funcs.len() { - let asm_func = (*asm_funcs)[i]; - sb_appendf(output, c!("public _%s as '%s'\n"), asm_func.name, asm_func.name); - sb_appendf(output, c!("_%s:\n"), asm_func.name); - for j in 0..asm_func.body.count { - let stmt = *asm_func.body.items.add(j); - sb_appendf(output, c!(" %s\n"), stmt.line); - } - } -} - -pub unsafe fn generate_globals(output: *mut String_Builder, globals: *const [Global]) { - for i in 0..globals.len() { - let global = (*globals)[i]; - sb_appendf(output, c!("public _%s as '%s'\n"), global.name, global.name); - sb_appendf(output, c!("_%s: "), global.name); - - if global.is_vec { - sb_appendf(output, c!("dq $+8\n")); - } - - if global.values.count > 0 { - sb_appendf(output, c!("dq "), global.name); - for j in 0..global.values.count { - if j > 0 { - sb_appendf(output, c!(",")); - } - match *global.values.items.add(j) { - ImmediateValue::Literal(lit) => sb_appendf(output, c!("0x%llX"), lit), - ImmediateValue::Name(name) => sb_appendf(output, c!("_%s"), name), - ImmediateValue::DataOffset(offset) => sb_appendf(output, c!("dat+%zu"), offset), - }; - } - } - - if global.values.count < global.minimum_size { - sb_appendf(output, c!("\nrq %zu"), global.minimum_size - global.values.count); - } - - sb_appendf(output, c!("\n")); - } -} - -pub unsafe fn generate_data_section(output: *mut String_Builder, data: *const [u8]) { - if data.len() > 0 { - sb_appendf(output, c!("dat: db ")); - for i in 0..data.len() { - if i > 0 { - sb_appendf(output, c!(",")); - } - sb_appendf(output, c!("0x%02X"), (*data)[i] as c_uint); - } - sb_appendf(output, c!("\n")); - } -} - -pub unsafe fn generate_program(output: *mut String_Builder, c: *const Compiler, os: Os) { - match os { - Os::Linux => sb_appendf(output, c!("format ELF64\n")), - Os::Windows => sb_appendf(output, c!("format MS64 COFF\n")), - Os::Darwin => sb_appendf(output, c!("format MachO64\n")), - }; - sb_appendf(output, c!("section \".text\" executable\n")); - generate_funcs(output, da_slice((*c).funcs), os); - generate_asm_funcs(output, da_slice((*c).asm_funcs)); - generate_extrns(output, da_slice((*c).extrns), da_slice((*c).funcs), da_slice((*c).globals), da_slice((*c).asm_funcs)); - sb_appendf(output, c!("section \".data\"\n")); - generate_data_section(output, da_slice((*c).data)); - generate_globals(output, da_slice((*c).globals)); -} diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index ee714aa4..359ceacc 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -1,6 +1,5 @@ pub mod gas_aarch64; pub mod gas_x86_64; -pub mod fasm_x86_64; pub mod ir; pub mod mos6502; pub mod uxn; diff --git a/src/jimp.rs b/src/jimp.rs index 83817d52..d6c78bb3 100644 --- a/src/jimp.rs +++ b/src/jimp.rs @@ -93,4 +93,5 @@ pub unsafe fn jimp_object_end(jimp: *mut Jimp) -> Option<()> { extern "C" { pub fn jimp_begin(jimp: *mut Jimp, file_path: *const c_char, input: *const c_char, input_size: usize); pub fn jimp_unknown_member(jimp: *mut Jimp); + pub fn jimp_diagf(jimp: *mut Jimp, fmt: *const c_char, ...); } diff --git a/src/runner/fasm_x86_64_linux.rs b/src/runner/fasm_x86_64_linux.rs deleted file mode 100644 index 6686a146..00000000 --- a/src/runner/fasm_x86_64_linux.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::nob::*; -use crate::crust::libc::*; -use core::ffi::*; -use core::mem::zeroed; - -pub unsafe fn run(cmd: *mut Cmd, program_path: *const c_char, run_args: *const [*const c_char], stdout_path: Option<*const c_char>) -> Option<()> { - // if the user does `b program.b -run` the compiler tries to run `program` which is not possible on Linux. It has to be `./program`. - let run_path: *const c_char; - if (strchr(program_path, '/' as c_int)).is_null() { - run_path = temp_sprintf(c!("./%s"), program_path); - } else { - run_path = program_path; - } - - cmd_append! {cmd, run_path,} - da_append_many(cmd, run_args); - - if let Some(stdout_path) = stdout_path { - let mut fdout = fd_open_for_write(stdout_path); - let mut redirect: Cmd_Redirect = zeroed(); - redirect.fdout = &mut fdout; - if !cmd_run_sync_redirect_and_reset(cmd, redirect) { return None; } - } else { - if !cmd_run_sync_and_reset(cmd) { return None; } - } - Some(()) -} diff --git a/src/runner/fasm_x86_64_windows.rs b/src/runner/fasm_x86_64_windows.rs deleted file mode 100644 index f6c87156..00000000 --- a/src/runner/fasm_x86_64_windows.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::nob::*; -use core::mem::zeroed; -use core::ffi::*; - -pub unsafe fn run(cmd: *mut Cmd, program_path: *const c_char, run_args: *const [*const c_char], stdout_path: Option<*const c_char>) -> Option<()>{ - // TODO: document that you may need wine as a system package to cross-run fasm-x86_64-windows - if !cfg!(target_os = "windows") { - cmd_append! { - cmd, - c!("wine"), - } - } - - cmd_append! {cmd, program_path,} - da_append_many(cmd, run_args); - - if let Some(stdout_path) = stdout_path { - let mut fdout = fd_open_for_write(stdout_path); - let mut redirect: Cmd_Redirect = zeroed(); - redirect.fdout = &mut fdout; - if !cmd_run_sync_redirect_and_reset(cmd, redirect) { return None; } - } else { - if !cmd_run_sync_and_reset(cmd) { return None; } - } - Some(()) -} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index acd3bf80..5f8315a0 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -1,8 +1,6 @@ pub mod gas_x86_64_linux; pub mod gas_x86_64_windows; pub mod gas_x86_64_darwin; -pub mod fasm_x86_64_linux; -pub mod fasm_x86_64_windows; pub mod gas_aarch64_linux; pub mod gas_aarch64_darwin; pub mod mos6502; diff --git a/src/targets.rs b/src/targets.rs index 4d832cf6..a73b05bb 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -13,16 +13,12 @@ enum_with_order! { Gas_AArch64_Darwin, Uxn, Mos6502, - Fasm_x86_64_Windows, - Fasm_x86_64_Linux, } } impl Target { pub unsafe fn name(self) -> *const c_char { match self { - Self::Fasm_x86_64_Windows => c!("fasm-x86_64-windows"), - Self::Fasm_x86_64_Linux => c!("fasm-x86_64-linux"), Self::Gas_x86_64_Windows => c!("gas-x86_64-windows"), Self::Gas_x86_64_Linux => c!("gas-x86_64-linux"), Self::Gas_x86_64_Darwin => c!("gas-x86_64-darwin"), diff --git a/tests.json b/tests.json index 78bf22bf..1f7f08db 100644 --- a/tests.json +++ b/tests.json @@ -1,15 +1,5 @@ { "rvalue_call": { - "fasm-x86_64-windows": { - "expected_stdout": "Foo\r\nBar\r\nBaz\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Foo\nBar\nBaz\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Foo\r\nBar\r\nBaz\r\n", "state": "Enabled", @@ -47,16 +37,6 @@ } }, "multiple-postfix": { - "fasm-x86_64-windows": { - "expected_stdout": "34 35\r\n70 419\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "34 35\n70 419\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "34 35\r\n70 419\r\n", "state": "Enabled", @@ -94,16 +74,6 @@ } }, "ternary": { - "fasm-x86_64-windows": { - "expected_stdout": "0:\t..69\r\n42:\t..69\r\n69:\t69\r\n96:\t69..420\r\n420:\t420\r\n690:\t420..=1337\r\n1337:\t420..=1337\r\n4269:\t1337..\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "0:\t..69\n42:\t..69\n69:\t69\n96:\t69..420\n420:\t420\n690:\t420..=1337\n1337:\t420..=1337\n4269:\t1337..\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "0:\t..69\r\n42:\t..69\r\n69:\t69\r\n96:\t69..420\r\n420:\t420\r\n690:\t420..=1337\r\n1337:\t420..=1337\r\n4269:\t1337..\r\n", "state": "Enabled", @@ -140,64 +110,7 @@ "comment": "" } }, - "asm_func_fasm_x86_64_linux": { - "fasm-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "fasm-x86_64-linux": { - "expected_stdout": "69\n", - "state": "Enabled", - "comment": "" - }, - "gas-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-x86_64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-aarch64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-aarch64-darwin": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "uxn": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "6502": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-x86_64-darwin": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Failed to build on record" - } - }, "deref_assign": { - "fasm-x86_64-windows": { - "expected_stdout": "*v = 1 v=1\r\n*v |= 16 v=17\r\n*v *= 2 v=34\r\n*v += 35 v=69\r\n*v <<= 1 v=138\r\n*v &= 127 v=10\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "*v = 1 v=1\n*v |= 16 v=17\n*v *= 2 v=34\n*v += 35 v=69\n*v <<= 1 v=138\n*v &= 127 v=10\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "*v = 1 v=1\r\n*v |= 16 v=17\r\n*v *= 2 v=34\r\n*v += 35 v=69\r\n*v <<= 1 v=138\r\n*v &= 127 v=10\r\n", "state": "Enabled", @@ -235,16 +148,6 @@ } }, "asm_gas_x86_64_linux": { - "fasm-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "fasm-x86_64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, "gas-x86_64-windows": { "expected_stdout": "", "state": "Disabled", @@ -281,64 +184,7 @@ "comment": "" } }, - "asm_fasm_x86_64_linux": { - "fasm-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "fasm-x86_64-linux": { - "expected_stdout": "0\n", - "state": "Enabled", - "comment": "" - }, - "gas-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-x86_64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-aarch64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-aarch64-darwin": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "uxn": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "6502": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "gas-x86_64-darwin": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Failed to build on record" - } - }, "return": { - "fasm-x86_64-windows": { - "expected_stdout": "69\r\n69\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "69\n69\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "69\r\n69\r\n", "state": "Enabled", @@ -376,16 +222,6 @@ } }, "asm_func_gas_x86_64_linux": { - "fasm-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "fasm-x86_64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, "gas-x86_64-windows": { "expected_stdout": "", "state": "Disabled", @@ -423,16 +259,6 @@ } }, "asm_func_6502": { - "fasm-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "fasm-x86_64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, "gas-x86_64-windows": { "expected_stdout": "", "state": "Disabled", @@ -470,16 +296,6 @@ } }, "call_stack_args": { - "fasm-x86_64-windows": { - "expected_stdout": "1 2 3 4 5 6 7 8 12 11\r\n1 2 3 4 5 6 7 8 12 11 10\r\n1 2 3 4 5 6 7 8 12 11 10 9\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "1 2 3 4 5 6 7 8 12 11\n1 2 3 4 5 6 7 8 12 11 10\n1 2 3 4 5 6 7 8 12 11 10 9\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "1 2 3 4 5 6 7 8 12 11\r\n1 2 3 4 5 6 7 8 12 11 10\r\n1 2 3 4 5 6 7 8 12 11 10 9\r\n", "state": "Enabled", @@ -517,16 +333,6 @@ } }, "globals": { - "fasm-x86_64-windows": { - "expected_stdout": "foo == 0x0102030405060708: OK\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "foo == 0x0102030405060708: OK\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "foo == 0x0102030405060708: OK\r\n", "state": "Enabled", @@ -564,16 +370,6 @@ } }, "recursion": { - "fasm-x86_64-windows": { - "expected_stdout": "10\r\n9\r\n8\r\n7\r\n6\r\n5\r\n4\r\n3\r\n2\r\n1\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "10\n9\n8\n7\n6\n5\n4\n3\n2\n1\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "10\r\n9\r\n8\r\n7\r\n6\r\n5\r\n4\r\n3\r\n2\r\n1\r\n", "state": "Enabled", @@ -611,16 +407,6 @@ } }, "lexer": { - "fasm-x86_64-windows": { - "expected_stdout": "0105 == 69: OK\r\n0x45 == 69: OK\r\n'E' == 0x45: OK\r\n'EF' == 0x4546: OK\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "0105 == 69: OK\n0x45 == 69: OK\n'E' == 0x45: OK\n'EF' == 0x4546: OK\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "0105 == 69: OK\r\n0x45 == 69: OK\r\n'E' == 0x45: OK\r\n'EF' == 0x4546: OK\r\n", "state": "Enabled", @@ -658,16 +444,6 @@ } }, "forward-declare": { - "fasm-x86_64-windows": { - "expected_stdout": "Foo\r\nBar\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Foo\nBar\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Foo\r\nBar\r\n", "state": "Enabled", @@ -705,16 +481,6 @@ } }, "ternary-side-effect": { - "fasm-x86_64-windows": { - "expected_stdout": "Only Foo should be printed bellow:\r\n Foo\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Only Foo should be printed bellow:\n Foo\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Only Foo should be printed bellow:\r\n Foo\r\n", "state": "Enabled", @@ -752,16 +518,6 @@ } }, "compare": { - "fasm-x86_64-windows": { - "expected_stdout": "5 == 3: OK\r\n3 == 3: OK\r\n5 != 3: OK\r\n3 != 3: OK\r\n5 >= 3: OK\r\n3 >= 5: OK\r\n3 >= 3: OK\r\n3 > 3: OK\r\n5 > 3: OK\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "5 == 3: OK\n3 == 3: OK\n5 != 3: OK\n3 != 3: OK\n5 >= 3: OK\n3 >= 5: OK\n3 >= 3: OK\n3 > 3: OK\n5 > 3: OK\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "5 == 3: OK\r\n3 == 3: OK\r\n5 != 3: OK\r\n3 != 3: OK\r\n5 >= 3: OK\r\n3 >= 5: OK\r\n3 >= 3: OK\r\n3 > 3: OK\r\n5 > 3: OK\r\n", "state": "Enabled", @@ -799,16 +555,6 @@ } }, "literals": { - "fasm-x86_64-windows": { - "expected_stdout": "69\r\n1000000\r\n123456789987654321\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "69\n1000000\n123456789987654321\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "69\r\n1000000\r\n123456789987654321\r\n", "state": "Enabled", @@ -846,16 +592,6 @@ } }, "minus_2": { - "fasm-x86_64-windows": { - "expected_stdout": "-4\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "-4\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "-4\r\n", "state": "Enabled", @@ -893,16 +629,6 @@ } }, "e": { - "fasm-x86_64-windows": { - "expected_stdout": "EEEEEEE\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "EEEEEEE\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "EEEEEEE\r\n", "state": "Enabled", @@ -940,16 +666,6 @@ } }, "divmod": { - "fasm-x86_64-windows": { - "expected_stdout": "Division:\r\n1/100 = 0\r\n-1/100 = 0\r\n100/100 = 1\r\n-100/100 = -1\r\n101/100 = 1\r\n-101/100 = -1\r\n201/100 = 2\r\n-201/100 = -2\r\n\r\nRemainder:\r\n1%100 = 1\r\n99%100 = 99\r\n100%100 = 0\r\n101%100 = 1\r\n201%100 = 1\r\n-1%100 = -1\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Division:\n1/100 = 0\n-1/100 = 0\n100/100 = 1\n-100/100 = -1\n101/100 = 1\n-101/100 = -1\n201/100 = 2\n-201/100 = -2\n\nRemainder:\n1%100 = 1\n99%100 = 99\n100%100 = 0\n101%100 = 1\n201%100 = 1\n-1%100 = -1\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Division:\r\n1/100 = 0\r\n-1/100 = 0\r\n100/100 = 1\r\n-100/100 = -1\r\n101/100 = 1\r\n-101/100 = -1\r\n201/100 = 2\r\n-201/100 = -2\r\n\r\nRemainder:\r\n1%100 = 1\r\n99%100 = 99\r\n100%100 = 0\r\n101%100 = 1\r\n201%100 = 1\r\n-1%100 = -1\r\n", "state": "Enabled", @@ -987,16 +703,6 @@ } }, "switch": { - "fasm-x86_64-windows": { - "expected_stdout": "(69,69) => 690: OK\r\n(420,420) => 42: OK\r\n(420,1337) => 7331: OK\r\n(420,69) => -2: OK\r\n(34,35) => -1: OK\r\n------------------------------\r\n0\r\n1\r\n2\r\n3\r\n4\r\n------------------------------\r\n3\r\n4\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "(69,69) => 690: OK\n(420,420) => 42: OK\n(420,1337) => 7331: OK\n(420,69) => -2: OK\n(34,35) => -1: OK\n------------------------------\n0\n1\n2\n3\n4\n------------------------------\n3\n4\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "(69,69) => 690: OK\r\n(420,420) => 42: OK\r\n(420,1337) => 7331: OK\r\n(420,69) => -2: OK\r\n(34,35) => -1: OK\r\n------------------------------\r\n0\r\n1\r\n2\r\n3\r\n4\r\n------------------------------\r\n3\r\n4\r\n", "state": "Enabled", @@ -1034,16 +740,6 @@ } }, "args11": { - "fasm-x86_64-windows": { - "expected_stdout": "Testing how well passing 11 arguments to a function we defined works.\r\nExpected output is `23`\r\n23\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Testing how well passing 11 arguments to a function we defined works.\nExpected output is `23`\n23\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Testing how well passing 11 arguments to a function we defined works.\r\nExpected output is `23`\r\n23\r\n", "state": "Enabled", @@ -1081,16 +777,6 @@ } }, "negative-ivals": { - "fasm-x86_64-windows": { - "expected_stdout": "Hello, World\r\n-1\r\n-2\r\n-3\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Hello, World\n-1\n-2\n-3\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Hello, World\r\n-1\r\n-2\r\n-3\r\n", "state": "Enabled", @@ -1128,16 +814,6 @@ } }, "ternary-assign": { - "fasm-x86_64-windows": { - "expected_stdout": "a = 1 ? 69 : 420; a == 69: OK\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "a = 1 ? 69 : 420; a == 69: OK\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "a = 1 ? 69 : 420; a == 69: OK\r\n", "state": "Enabled", @@ -1175,16 +851,6 @@ } }, "asm_6502": { - "fasm-x86_64-windows": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, - "fasm-x86_64-linux": { - "expected_stdout": "", - "state": "Disabled", - "comment": "Doesn't make sense for this target" - }, "gas-x86_64-windows": { "expected_stdout": "", "state": "Disabled", @@ -1222,16 +888,6 @@ } }, "ref": { - "fasm-x86_64-windows": { - "expected_stdout": "x: 69 69 69 69 69\r\ny: 420 420 420 420 420\r\na: 1337\r\nxs: [13, 42]\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "x: 69 69 69 69 69\ny: 420 420 420 420 420\na: 1337\nxs: [13, 42]\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "x: 69 69 69 69 69\r\ny: 420 420 420 420 420\r\na: 1337\r\nxs: [13, 42]\r\n", "state": "Enabled", @@ -1269,16 +925,6 @@ } }, "vector": { - "fasm-x86_64-windows": { - "expected_stdout": "34 + 35 = 69\r\nJust\r\nTesting\r\nGlobals\r\n1 => 2\r\n2 => 4\r\n3 => 6\r\n4 => 8\r\n5 => 10\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "34 + 35 = 69\nJust\nTesting\nGlobals\n1 => 2\n2 => 4\n3 => 6\n4 => 8\n5 => 10\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "34 + 35 = 69\r\nJust\r\nTesting\r\nGlobals\r\n1 => 2\r\n2 => 4\r\n3 => 6\r\n4 => 8\r\n5 => 10\r\n", "state": "Enabled", @@ -1316,16 +962,6 @@ } }, "args11-extrn": { - "fasm-x86_64-windows": { - "expected_stdout": "Testing how well passing 11 arguments works.\r\nExpected output is `1 2 3 4 5 6 7 8 9 10`\r\n1 2 3 4 5 6 7 8 9 10\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Testing how well passing 11 arguments works.\nExpected output is `1 2 3 4 5 6 7 8 9 10`\n1 2 3 4 5 6 7 8 9 10\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Testing how well passing 11 arguments works.\r\nExpected output is `1 2 3 4 5 6 7 8 9 10`\r\n1 2 3 4 5 6 7 8 9 10\r\n", "state": "Enabled", @@ -1363,16 +999,6 @@ } }, "goto": { - "fasm-x86_64-windows": { - "expected_stdout": "0\r\n1\r\n2\r\n3\r\n4\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "0\n1\n2\n3\n4\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "0\r\n1\r\n2\r\n3\r\n4\r\n", "state": "Enabled", @@ -1410,16 +1036,6 @@ } }, "unary_priority": { - "fasm-x86_64-windows": { - "expected_stdout": "69\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "69\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "69\r\n", "state": "Enabled", @@ -1457,16 +1073,6 @@ } }, "args6": { - "fasm-x86_64-windows": { - "expected_stdout": "Testing how well passing 6 arguments works.\r\nExpected output is `1 2 3 4 5`\r\n1 2 3 4 5\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "Testing how well passing 6 arguments works.\nExpected output is `1 2 3 4 5`\n1 2 3 4 5\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "Testing how well passing 6 arguments works.\r\nExpected output is `1 2 3 4 5`\r\n1 2 3 4 5\r\n", "state": "Enabled", @@ -1504,16 +1110,6 @@ } }, "stack_alloc": { - "fasm-x86_64-windows": { - "expected_stdout": "a = 1\r\nb = 2\r\nc = 3\r\n69\r\n420\r\n1337\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "a = 1\nb = 2\nc = 3\n69\n420\n1337\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "a = 1\r\nb = 2\r\nc = 3\r\n69\r\n420\r\n1337\r\n", "state": "Enabled", @@ -1551,16 +1147,6 @@ } }, "hello": { - "fasm-x86_64-windows": { - "expected_stdout": "HELLOOOOO\r\nHELLOOOOO\r\nHELLOOOOO\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "HELLOOOOO\nHELLOOOOO\nHELLOOOOO\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "HELLOOOOO\r\nHELLOOOOO\r\nHELLOOOOO\r\n", "state": "Enabled", @@ -1598,16 +1184,6 @@ } }, "inc_dec": { - "fasm-x86_64-windows": { - "expected_stdout": "x: 3\r\n++x: 4\r\nx++: 4\r\nx: 5\r\nx--: 5\r\n--x: 3\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "x: 3\n++x: 4\nx++: 4\nx: 5\nx--: 5\n--x: 3\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "x: 3\r\n++x: 4\r\nx++: 4\r\nx: 5\r\nx--: 5\r\n--x: 3\r\n", "state": "Enabled", @@ -1645,16 +1221,6 @@ } }, "upper": { - "fasm-x86_64-windows": { - "expected_stdout": "lower: hello, world\r\nUPPER: HELLO, WORLD\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "lower: hello, world\nUPPER: HELLO, WORLD\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "lower: hello, world\r\nUPPER: HELLO, WORLD\r\n", "state": "Enabled", @@ -1692,16 +1258,6 @@ } }, "compile-overflow": { - "fasm-x86_64-windows": { - "expected_stdout": "x = 8000000000000000\r\ny = 8000000000000000\r\nz = 8000000000000000\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "x = 8000000000000000\ny = 8000000000000000\nz = 8000000000000000\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-windows": { "expected_stdout": "x = 8000000000000000\r\ny = 8000000000000000\r\nz = 8000000000000000\r\n", "state": "Enabled", @@ -1769,16 +1325,6 @@ "state": "Disabled", "comment": "Does not make sense on this platform" }, - "fasm-x86_64-windows": { - "expected_stdout": "OK\r\n", - "state": "Enabled", - "comment": "" - }, - "fasm-x86_64-linux": { - "expected_stdout": "OK\n", - "state": "Enabled", - "comment": "" - }, "gas-x86_64-darwin": { "expected_stdout": "OK\n", "state": "Enabled", @@ -1816,18 +1362,45 @@ "state": "Enabled", "comment": "" }, - "fasm-x86_64-windows": { - "expected_stdout": "No forward declaration is required\r\n", + "gas-x86_64-darwin": { + "expected_stdout": "No forward declaration is required\n", + "state": "Enabled", + "comment": "" + } + }, + "statements": { + "gas-x86_64-windows": { + "expected_stdout": "HELO\r\n", "state": "Enabled", "comment": "" }, - "fasm-x86_64-linux": { - "expected_stdout": "No forward declaration is required\n", + "gas-x86_64-linux": { + "expected_stdout": "HELO\n", "state": "Enabled", "comment": "" }, "gas-x86_64-darwin": { - "expected_stdout": "No forward declaration is required\n", + "expected_stdout": "HELO\n", + "state": "Enabled", + "comment": "" + }, + "gas-aarch64-linux": { + "expected_stdout": "HELO\n", + "state": "Enabled", + "comment": "" + }, + "gas-aarch64-darwin": { + "expected_stdout": "HELO\n", + "state": "Enabled", + "comment": "" + }, + "uxn": { + "expected_stdout": "HELO\n", + "state": "Enabled", + "comment": "" + }, + "6502": { + "expected_stdout": "HELO\r\n", "state": "Enabled", "comment": "" } diff --git a/tests/asm_fasm_x86_64_linux.b b/tests/asm_fasm_x86_64_linux.b deleted file mode 100644 index 3f3bfabe..00000000 --- a/tests/asm_fasm_x86_64_linux.b +++ /dev/null @@ -1,13 +0,0 @@ -foo() { - __asm__( - "mov rax, 0", - "mov rsp, rbp", - "pop rbp", - "ret" - ); -} - -main() { - extrn printf; - printf("%d\n", foo()); -} diff --git a/tests/asm_func_fasm_x86_64_linux.b b/tests/asm_func_fasm_x86_64_linux.b deleted file mode 100644 index 888b42a8..00000000 --- a/tests/asm_func_fasm_x86_64_linux.b +++ /dev/null @@ -1,10 +0,0 @@ -add __asm__( - "add rdi, rsi", - "mov rax, rdi", - "ret" -); - -main() { - extrn printf; - printf("%d\n", add(34, 35)); -} diff --git a/tests/statements.b b/tests/statements.b new file mode 100644 index 00000000..3d3230b9 --- /dev/null +++ b/tests/statements.b @@ -0,0 +1,10 @@ +// https://github.com/tsoding/b/issues/209 +test1(); + +// https://github.com/tsoding/b/issues/210 +test2() auto a; extrn printf; printf("HELO\n"); + +main() { + test1(); + test2(); +} From d8fe902634c0181d541a3f2fc90effd1d6167f0f Mon Sep 17 00:00:00 2001 From: kyon4ik Date: Wed, 9 Jul 2025 20:51:25 +0500 Subject: [PATCH 5/6] Introduce SetRetReg --- src/b.rs | 13 ++++++------- src/codegen/gas_aarch64.rs | 8 ++++---- src/codegen/gas_x86_64.rs | 8 ++++---- src/codegen/ir.rs | 11 ++++++----- src/codegen/mos6502.rs | 9 ++++----- src/codegen/uxn.rs | 11 ++++------- 6 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/b.rs b/src/b.rs index c6078b7b..d3776c20 100644 --- a/src/b.rs +++ b/src/b.rs @@ -324,7 +324,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)] @@ -832,7 +833,7 @@ pub unsafe fn compile_statement(l: *mut Lexer, c: *mut Compiler) -> Option<()> { 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::AutoAssign { index: (*c).return_auto, arg }, (*l).loc, c); + push_opcode(Op::SetRetReg { arg }, (*l).loc, c); push_opcode(Op::JmpLabel { label: (*c).return_label }, (*l).loc, c); } else { unreachable!(); @@ -1002,7 +1003,6 @@ pub struct Compiler { pub globals: Array, pub asm_funcs: Array, pub return_label: usize, - pub return_auto: usize, /// Arena into which the Compiler allocates all the names and /// objects that need to live for the duration of the /// compilation. Even if some object/names don't need to live that @@ -1108,7 +1108,6 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { } } } - (*c).return_auto = allocate_auto_var(&mut (*c).auto_vars_ator); (*c).return_label = allocate_label_index(c); compile_statement(l, c)?; // setup function epilogue @@ -1116,13 +1115,13 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { if (*last_op).opcode == (Op::JmpLabel { label: (*c).return_label }) { *last_op = OpWithLocation { opcode: Op::Label { label: (*c).return_label }, loc: (*l).loc }; } else { - push_opcode(Op::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); + push_opcode(Op::SetRetReg { arg: Arg::Literal(0) }, (*l).loc, c); push_opcode(Op::Label { label: (*c).return_label }, (*l).loc, c); } } else { - push_opcode(Op::AutoAssign { index: (*c).return_auto, arg: Arg::Literal(0) }, (*l).loc, c); + push_opcode(Op::SetRetReg { arg: Arg::Literal(0) }, (*l).loc, c); } - push_opcode(Op::Return { arg: Some(Arg::AutoVar((*c).return_auto)) }, (*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 { diff --git a/src/codegen/gas_aarch64.rs b/src/codegen/gas_aarch64.rs index ebfb0567..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")); diff --git a/src/codegen/gas_x86_64.rs b/src/codegen/gas_x86_64.rs index f32b78ef..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")); diff --git a/src/codegen/ir.rs b/src/codegen/ir.rs index 13e9506f..3e0505ad 100644 --- a/src/codegen/ir.rs +++ b/src/codegen/ir.rs @@ -34,12 +34,13 @@ 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} => { - 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 ea83948e..89110bd0 100644 --- a/src/codegen/mos6502.rs +++ b/src/codegen/mos6502.rs @@ -757,11 +757,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(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); diff --git a/src/codegen/uxn.rs b/src/codegen/uxn.rs index 2465c709..f505664e 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); From 6ca63e9f4f5d9cd8b251c097fe33c9e1576805b2 Mon Sep 17 00:00:00 2001 From: kyon4ik Date: Wed, 9 Jul 2025 22:25:54 +0500 Subject: [PATCH 6/6] Move to opt --- .github/workflows/ci.yml | 7 +++++++ Makefile | 1 + src/b.rs | 22 +++++++++++++--------- src/btest.rs | 23 ++++++++++++++--------- src/opt.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 src/opt.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 102f5b53..4cbd729d 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/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 d3776c20..5060612b 100644 --- a/src/b.rs +++ b/src/b.rs @@ -16,6 +16,7 @@ pub mod codegen; pub mod runner; pub mod lexer; pub mod targets; +pub mod opt; use core::ffi::*; use core::mem::zeroed; @@ -828,13 +829,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::JmpLabel { label: (*c).return_label }, (*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::SetRetReg { arg }, (*l).loc, c); - push_opcode(Op::JmpLabel { label: (*c).return_label }, (*l).loc, c); + push_opcode(Op::Return, (*l).loc, c); } else { unreachable!(); } @@ -956,6 +957,7 @@ pub struct Func { body: Array, params_count: usize, auto_vars_count: usize, + label_count: usize, } #[derive(Clone, Copy)] @@ -1002,7 +1004,6 @@ pub struct Compiler { pub variadics: Array<(*const c_char, Variadic)>, pub globals: Array, pub asm_funcs: Array, - pub return_label: usize, /// Arena into which the Compiler allocates all the names and /// objects that need to live for the duration of the /// compilation. Even if some object/names don't need to live that @@ -1108,20 +1109,17 @@ pub unsafe fn compile_program(l: *mut Lexer, c: *mut Compiler) -> Option<()> { } } } - (*c).return_label = allocate_label_index(c); compile_statement(l, c)?; // setup function epilogue if let Some(last_op) = da_last_mut(&mut (*c).func_body) { - if (*last_op).opcode == (Op::JmpLabel { label: (*c).return_label }) { - *last_op = OpWithLocation { opcode: Op::Label { label: (*c).return_label }, loc: (*l).loc }; - } else { + if (*last_op).opcode != Op::Return { push_opcode(Op::SetRetReg { arg: Arg::Literal(0) }, (*l).loc, c); - push_opcode(Op::Label { label: (*c).return_label }, (*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); } - push_opcode(Op::Return, (*l).loc, c); scope_pop(&mut (*c).vars); // end function scope for i in 0..(*c).func_gotos.count { @@ -1141,6 +1139,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; @@ -1283,6 +1282,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")); @@ -1404,6 +1404,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 a8079f71..b9d1e42e 100644 --- a/src/btest.rs +++ b/src/btest.rs @@ -159,7 +159,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 { @@ -182,6 +182,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); } @@ -296,7 +299,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, @@ -325,7 +328,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, )?; @@ -343,7 +346,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, )?; @@ -510,7 +513,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<()> { @@ -538,7 +541,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, )?; @@ -565,7 +568,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, )?; @@ -633,6 +636,8 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { let list_actions = flag_bool(c!("alist"), false, c!("Print the list of all available actions.")); 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 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")); @@ -804,7 +809,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, )?; @@ -814,7 +819,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/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 }); + } +}