Skip to content

Commit f77509f

Browse files
authored
Merge pull request #257 from bext-lang/posix6502
Decouple the 6502 runner from the compiler
2 parents 4027b2a + fc47fe5 commit f77509f

10 files changed

Lines changed: 176 additions & 1133 deletions

File tree

.github/workflows/ci.yml

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on: [push, pull_request]
55
# May require a special support for build-only mode in btest
66

77
jobs:
8-
ubuntu-excluding-macos:
8+
ubuntu-linux-windows:
99
runs-on: ubuntu-latest
1010
steps:
1111
- name: Checkout
@@ -16,6 +16,23 @@ jobs:
1616
run: |
1717
sudo apt-get update
1818
sudo apt-get install -qq -y mono-devel clang make mingw-w64 wine64 gcc-aarch64-linux-gnu qemu-user
19+
- name: Build Toolchain
20+
run: |
21+
make -B
22+
- name: Run Tests
23+
run: |
24+
PATH=$(realpath uxn11/bin):$PATH ./build/btest -t *linux* -t *windows*
25+
ubuntu-uxn:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
- name: Install Rust
31+
run: rustup toolchain install stable --no-self-update --profile minimal
32+
- name: Install Dependencies
33+
run: |
34+
sudo apt-get update
35+
sudo apt-get install -qq -y clang make
1936
git clone https://git.sr.ht/~rabbits/uxn11
2037
cd uxn11
2138
make cli
@@ -24,7 +41,45 @@ jobs:
2441
make -B
2542
- name: Run Tests
2643
run: |
27-
PATH=$(realpath uxn11/bin):$PATH ./build/btest -xt *darwin*
44+
PATH=$(realpath uxn11/bin):$PATH ./build/btest -t uxn
45+
ubuntu-6502-posix:
46+
runs-on: ubuntu-latest
47+
steps:
48+
- name: Checkout
49+
uses: actions/checkout@v4
50+
- name: Install Rust
51+
run: rustup toolchain install stable --no-self-update --profile minimal
52+
- name: Install Dependencies
53+
run: |
54+
sudo apt-get update
55+
sudo apt-get install -qq -y clang make
56+
git clone https://github.com/bext-lang/posix6502
57+
cd posix6502
58+
cc -o nob nob.c
59+
./nob
60+
- name: Build Toolchain
61+
run: |
62+
make -B
63+
- name: Run Tests
64+
run: |
65+
PATH=$(realpath posix6502/build):$PATH ./build/btest -t 6502-posix
66+
ubuntu-mono:
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Checkout
70+
uses: actions/checkout@v4
71+
- name: Install Rust
72+
run: rustup toolchain install stable --no-self-update --profile minimal
73+
- name: Install Dependencies
74+
run: |
75+
sudo apt-get update
76+
sudo apt-get install -qq -y mono-devel clang make
77+
- name: Build Toolchain
78+
run: |
79+
make -B
80+
- name: Run Tests
81+
run: |
82+
./build/btest -t *ilasm-mono*
2883
macos-aarch64:
2984
runs-on: macos-latest
3085
steps:

Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ POSIX_OBJS=\
3838
$(BUILD)/glob.posix.o \
3939
$(BUILD)/libc.posix.o \
4040
$(BUILD)/arena.posix.o \
41-
$(BUILD)/fake6502.posix.o \
4241
$(BUILD)/jim.posix.o \
4342
$(BUILD)/jimp.posix.o
4443

@@ -48,7 +47,6 @@ MINGW32_OBJS=\
4847
$(BUILD)/glob.mingw32.o \
4948
$(BUILD)/libc.mingw32.o \
5049
$(BUILD)/arena.mingw32.o \
51-
$(BUILD)/fake6502.mingw32.o \
5250
$(BUILD)/jim.mingw32.o \
5351
$(BUILD)/jimp.mingw32.o
5452

README.md

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,6 @@
1313

1414
Compiler for the [B Programming Language](https://en.wikipedia.org/wiki/B_(programming_language)) implemented in [Crust](https://github.com/tsoding/crust).
1515

16-
## Dependencies
17-
18-
- [Rust](https://www.rust-lang.org/) - the compiler is written in it;
19-
- [GCC](https://gcc.gnu.org/) or [Clang](https://clang.llvm.org/) (whatever serves as the `cc` on your POSIX platform) - the `x86_64` and `aarch64` targets generate assembly and pass it to `cc` to assemble and link.
20-
21-
<!-- TODO: document specific dependencies for the rest of the targets. Like mingw32-w64 and wine on Linux for gas-x86_64-Windows, etc. -->
22-
2316
## Quick Start
2417

2518
```console
@@ -30,6 +23,49 @@ $ ./build/b -run ./examples/hello_world.b
3023
Also check out more examples at [./examples/](./examples/).
3124
Find the project documentation at [./docs/](./docs/).
3225

26+
## Dependencies
27+
28+
Generally, to write programs for the three major contemporary platforms (Linux, Windows, Darwin) you need only these things:
29+
30+
- [Rust](https://www.rust-lang.org/) - the compiler is written in it;
31+
- [GCC](https://gcc.gnu.org/) or [Clang](https://clang.llvm.org/) or [mingw-w64](https://www.mingw-w64.org/) (whatever serves as the `cc` on your POSIX platform) - the `x86_64` and `aarch64` targets generate assembly and pass it to `cc` to assemble and link.
32+
33+
If you feel like playing with our "spicy" targets, you will need to setup few additional things.
34+
35+
### uxn
36+
37+
[Uxn](https://100r.co/site/uxn.html) is a pretty cool small virtual machine designed to be simple and portable.
38+
39+
This compiler toolchain expects the `uxnemu` and `uxncli` executables to be available in the `$PATH` environment variable. We recommend to build them from the source code available at [https://git.sr.ht/~rabbits/uxn](https://git.sr.ht/~rabbits/uxn). Follow their build instructions and then copy the contents of the `bin/` folder somewhere were the `$PATH` points at.
40+
41+
### 6502-posix
42+
43+
[MOS Technology 6502](https://en.wikipedia.org/wiki/MOS_Technology_6502) is a legendary processor that was used in such systems as the Atari 2600, Atari 8-bit computers, Apple II, Nintendo Entertainment System, Commodore 64, Atari Lynx, BBC Micro and others.
44+
45+
Since "targeting 6502" doesn't really mean anything, given the diverse array of hardware, we created a special target called [posix6502](https://github.com/bext-lang/posix6502). It's a simple 6502 emulator that exposes some POSIX functionality to programs running on the 6502 processor, allowing them to operate in a POSIX environment. We use it as a testing and playground target for 6502 codegen. In the future, we may add more 6502-related targets.
46+
47+
It consist of a single `posix6502` executable which is expected to be available to the compiler toolchain via the `$PATH` environment variable. We recommend to build it from the source code:
48+
49+
```console
50+
$ git clone https://github.com/bext-lang/posix6502 && cd posix6502
51+
$ cc -o nob nob.c
52+
$ ./nob
53+
```
54+
55+
Copy the `build/posix6502` executable somewhere were the `$PATH` points at.
56+
57+
### ilasm-mono
58+
59+
This target tries to produce [.NET](https://dotnet.microsoft.com/en-us/)/[Mono](https://www.mono-project.com/) compatible binaries. It's currently a work-in-progress and does not have anything useful implemented in it.
60+
61+
The compiler toolchain expects `ilasm` and `mono` executables available in the `$PATH` environment variables. Lots of Linux distros make them available via the mono packages in their official repos:
62+
63+
```consols
64+
$ sudo xbps-install mono # Void Linux
65+
$ sudo apt install mono-devel # Ubuntu
66+
...
67+
```
68+
3369
## Contribution
3470

3571
Accepting Pull Requests is currently paused. We are in the middle of [Decentralizing](https://github.com/tsoding/b/issues/62) this repo. The plan is
@@ -38,7 +74,7 @@ Accepting Pull Requests is currently paused. We are in the middle of [Decentrali
3874
2. Keep `*x86_64*` and `*aarch64*` codegens in the main repo.
3975
3. Move codegens [6502](./src/codegen/mos6502.rs) (owner [@Miezekatze64](https://github.com/miezekatze64)), [uxn](./src/codegen/uxn.rs) (owner [@deniska](https://github.com/deniska)), [gas-sh4dsp-prizm](https://github.com/tsoding/b/pull/175) (owner [@seija-amanojaku](https://github.com/seija-amanojaku)) to separate repos within the organization and give the owners full admin access to them.
4076

41-
You can still submit PRs in the meantime. Just don't expect them to be reviewed any time soon since decouping codegens requires extensive refactoring. They will be addressed eventually.
77+
You can still submit PRs in the meantime. Just don't expect them to be reviewed any time soon since decoupling codegens requires extensive refactoring. The PRs will be addressed eventually.
4278

4379
## References
4480

File renamed without changes.

src/b.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,13 @@ pub unsafe fn get_garbage_base(path: *const c_char, target: Target) -> Option<*m
11611161
Some(temp_sprintf(c!("%s/%s.%s"), garbage_dir, filename, target.name()))
11621162
}
11631163

1164+
pub unsafe fn print_available_targets() {
1165+
fprintf(stderr(), c!("Compilation targets:\n"));
1166+
for i in 0..TARGET_ORDER.len() {
1167+
fprintf(stderr(), c!(" %s\n"), (*TARGET_ORDER)[i].name());
1168+
}
1169+
}
1170+
11641171
pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> {
11651172
let default_target;
11661173
if cfg!(target_arch = "aarch64") && (cfg!(target_os = "linux") || cfg!(target_os = "android")) {
@@ -1228,15 +1235,13 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> {
12281235
}
12291236

12301237
if strcmp(*target_name, c!("list")) == 0 {
1231-
fprintf(stderr(), c!("Compilation targets:\n"));
1232-
for i in 0..TARGET_ORDER.len() {
1233-
fprintf(stderr(), c!(" %s\n"), (*TARGET_ORDER)[i].name());
1234-
}
1238+
print_available_targets();
12351239
return Some(());
12361240
}
12371241

12381242
let Some(target) = Target::by_name(*target_name) else {
12391243
usage();
1244+
print_available_targets();
12401245
log(Log_Level::ERROR, c!("Unknown target `%s`"), *target_name);
12411246
return None;
12421247
};
@@ -1416,7 +1421,7 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> {
14161421
codegen::uxn::run_program(&mut cmd, c!("uxnemu"), program_path, da_slice(run_args), None)?;
14171422
}
14181423
}
1419-
Target::Mos6502 => {
1424+
Target::Mos6502_Posix => {
14201425
let config = codegen::mos6502::parse_config_from_link_flags(da_slice(*linker))?;
14211426

14221427
codegen::mos6502::generate_program(
@@ -1427,7 +1432,7 @@ pub unsafe fn main(mut argc: i32, mut argv: *mut*mut c_char) -> Option<()> {
14271432
)?;
14281433

14291434
if *run {
1430-
codegen::mos6502::run_program(&mut output, config, program_path, None)?;
1435+
codegen::mos6502::run_program(&mut cmd, config, program_path, da_slice(run_args), None)?;
14311436
}
14321437
}
14331438
Target::ILasm_Mono => {

src/btest.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ pub unsafe fn execute_test(
137137
Target::Gas_x86_64_Linux => c!("gas-x86_64-linux"),
138138
Target::Gas_x86_64_Windows => c!("exe"),
139139
Target::Uxn => c!("rom"),
140-
Target::Mos6502 => c!("6502"),
140+
Target::Mos6502_Posix => c!("6502"),
141141
// TODO: ILasm_Mono may collide with Gas_x86_64_Windows if we introduce parallel runner
142142
Target::ILasm_Mono => c!("exe"),
143143
});
@@ -163,9 +163,9 @@ pub unsafe fn execute_test(
163163
Target::Gas_x86_64_Windows => codegen::gas_x86_64::run_program(cmd, program_path, &[], Some(stdout_path), Os::Windows),
164164
Target::Gas_x86_64_Darwin => codegen::gas_x86_64::run_program(cmd, program_path, &[], Some(stdout_path), Os::Darwin),
165165
Target::Uxn => codegen::uxn::run_program(cmd, c!("uxncli"), program_path, &[], Some(stdout_path)),
166-
Target::Mos6502 => codegen::mos6502::run_program(sb, Config {
166+
Target::Mos6502_Posix => codegen::mos6502::run_program(cmd, Config {
167167
load_offset: DEFAULT_LOAD_OFFSET
168-
}, program_path, Some(stdout_path)),
168+
}, program_path, &[], Some(stdout_path)),
169169
Target::ILasm_Mono => codegen::ilasm_mono::run_program(cmd, program_path, &[], Some(stdout_path)),
170170
};
171171

@@ -463,10 +463,6 @@ pub unsafe fn load_tt_from_json_file_if_exists(
463463
target = Some(parsed_target);
464464
} else {
465465
jimp_diagf(jimp, c!("WARNING: invalid target name `%s`\n"), (*jimp).string);
466-
jimp_diagf(jimp, c!("NOTE: Expected target values are:\n"));
467-
for i in 0..TARGET_ORDER.len() {
468-
jimp_diagf(jimp, c!("NOTE: %s\n"), (*TARGET_ORDER)[i]);
469-
}
470466
}
471467
continue 'row;
472468
}
@@ -481,10 +477,6 @@ pub unsafe fn load_tt_from_json_file_if_exists(
481477
state = parsed_state;
482478
} else {
483479
jimp_diagf(jimp, c!("WARNING: invalid state name `%s`\n"), (*jimp).string);
484-
jimp_diagf(jimp, c!("NOTE: Expected state values are:\n"));
485-
for i in 0..TEST_STATE_ORDER.len() {
486-
jimp_diagf(jimp, c!("NOTE: %s\n"), (*TEST_STATE_ORDER)[i]);
487-
}
488480
}
489481
continue 'row;
490482
}
@@ -501,14 +493,14 @@ pub unsafe fn load_tt_from_json_file_if_exists(
501493

502494
let Some(target) = target else {
503495
(*jimp).token_start = saved_point;
504-
jimp_diagf(jimp, c!("ERROR: `target` is not defined for this test row. Ignoring the entire row...\n"));
496+
jimp_diagf(jimp, c!("WARNING: no valid `target` field is defined for this test row. Ignoring the entire row...\n"));
505497
// TODO: memory leak, we are dropping the whole row here
506498
continue 'table;
507499
};
508500

509501
if case_name.is_null() {
510502
(*jimp).token_start = saved_point;
511-
jimp_diagf(jimp, c!("ERROR: `case_name` is not defined for this test row. Ignoring the entire row...\n"));
503+
jimp_diagf(jimp, c!("WARNING: no valid `case_name` field is defined for this test row. Ignoring the entire row...\n"));
512504
// TODO: memory leak, we are dropping the whole row here
513505
continue 'table;
514506
}

src/codegen/mos6502.rs

Lines changed: 15 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,95 +1493,23 @@ pub struct Config {
14931493
pub load_offset: u16,
14941494
}
14951495

1496-
pub mod fake6502 {
1497-
use core::mem::zeroed;
1498-
use crate::nob::*;
1499-
1500-
pub static mut MEMORY: [u8; 1<<16] = unsafe { zeroed() };
1501-
1502-
pub unsafe fn load_rom_at(rom: String_Builder, offset: u16) {
1503-
for i in 0..rom.count {
1504-
MEMORY[i + offset as usize] = *rom.items.add(i) as u8;
1505-
}
1506-
}
1507-
1508-
#[no_mangle]
1509-
pub unsafe extern "C" fn read6502(address: u16) -> u8 {
1510-
MEMORY[address as usize]
1511-
}
1512-
1513-
#[no_mangle]
1514-
pub unsafe extern "C" fn write6502(address: u16, value: u8) {
1515-
MEMORY[address as usize] = value;
1496+
pub unsafe fn run_program(cmd: *mut Cmd, config: Config, program_path: *const c_char, run_args: *const [*const c_char], stdout_path: Option<*const c_char>) -> Option<()> {
1497+
cmd_append!{
1498+
cmd,
1499+
c!("posix6502"), c!("-load-offset"), temp_sprintf(c!("%u"), config.load_offset as c_uint),
1500+
program_path
15161501
}
1517-
1518-
extern "C" {
1519-
#[link_name = "reset6502"]
1520-
pub fn reset();
1521-
#[link_name = "step6502"]
1522-
pub fn step();
1523-
pub fn rts();
1524-
pub static mut pc: u16;
1525-
pub static mut sp: u16;
1526-
pub static mut a: u8;
1527-
pub static mut x: u8;
1528-
pub static mut y: u8;
1502+
if run_args.len() > 0 {
1503+
cmd_append!(cmd, c!("--"));
1504+
da_append_many(cmd, run_args);
15291505
}
1530-
1531-
}
1532-
1533-
pub unsafe fn run_impl(output: *mut String_Builder, config: Config, stdout: *mut FILE) -> Option<()> {
1534-
fake6502::load_rom_at(*output, config.load_offset);
1535-
fake6502::reset();
1536-
fake6502::pc = config.load_offset;
1537-
1538-
// set reset to $0000 to exit on reset
1539-
fake6502::MEMORY[0xFFFC] = 0;
1540-
fake6502::MEMORY[0xFFFD] = 0;
1541-
1542-
while fake6502::pc != 0 { // The convetion is stop executing when pc == $0000
1543-
let prev_sp = fake6502::sp & 0xFF;
1544-
let opcode = fake6502::MEMORY[fake6502::pc as usize];
1545-
fake6502::step();
1546-
1547-
let curr_sp = fake6502::sp & 0xFF;
1548-
if opcode == 0x48 && curr_sp > prev_sp { // PHA instruction
1549-
log(Log_Level::ERROR, c!("Stack overflow detected"));
1550-
log(Log_Level::ERROR, c!("SP changed from $%02X to $%02X after PHA instruction"), prev_sp as c_uint, curr_sp as c_uint);
1551-
return None;
1552-
}
1553-
1554-
if fake6502::pc == 0xFFEF { // Emulating wozmon ECHO routine
1555-
fprintf(stdout, c!("%c"), fake6502::a as c_uint);
1556-
fake6502::rts();
1557-
}
1558-
}
1559-
// print exit code (in Y:A)
1560-
let code = ((fake6502::y as c_uint) << 8) | fake6502::a as c_uint;
1561-
log(Log_Level::INFO, c!("Exited with code %hd"), code);
1562-
1563-
if code != 0 {
1564-
return None;
1565-
}
1566-
Some(())
1567-
}
1568-
1569-
pub unsafe fn run_program(output: *mut String_Builder, config: Config, program_path: *const c_char, stdout_path: Option<*const c_char>) -> Option<()> {
1570-
(*output).count = 0;
1571-
read_entire_file(program_path, output)?;
1572-
1573-
let stdout = if let Some(stdout_path) = stdout_path {
1574-
let stdout = fopen(stdout_path, c!("wb"));
1575-
if stdout.is_null() {
1576-
return None
1577-
}
1578-
stdout
1506+
if let Some(stdout_path) = stdout_path {
1507+
let mut fdout = fd_open_for_write(stdout_path);
1508+
let mut redirect: Cmd_Redirect = zeroed();
1509+
redirect.fdout = &mut fdout;
1510+
if !cmd_run_sync_redirect_and_reset(cmd, redirect) { return None; }
15791511
} else {
1580-
stdout()
1581-
};
1582-
let result = run_impl(output, config, stdout);
1583-
if stdout_path.is_some() {
1584-
fclose(stdout);
1512+
if !cmd_run_sync_and_reset(cmd) { return None; }
15851513
}
1586-
result
1514+
Some(())
15871515
}

0 commit comments

Comments
 (0)