All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Reviewed-on: #12 Co-authored-by: bQUARKz <bquarkz@gmail.com> Co-committed-by: bQUARKz <bquarkz@gmail.com>
218 lines
7.4 KiB
Rust
218 lines
7.4 KiB
Rust
use anyhow::Result;
|
|
use prometeu_bytecode::assembler::assemble;
|
|
use prometeu_bytecode::model::{
|
|
BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta, SyscallDecl,
|
|
};
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
fn asm(s: &str) -> Vec<u8> {
|
|
assemble(s).expect("assemble")
|
|
}
|
|
|
|
pub fn generate() -> Result<()> {
|
|
let mut rom: Vec<u8> = Vec::new();
|
|
let syscalls = vec![
|
|
SyscallDecl {
|
|
module: "gfx".into(),
|
|
name: "clear_565".into(),
|
|
version: 1,
|
|
arg_slots: 1,
|
|
ret_slots: 0,
|
|
},
|
|
SyscallDecl {
|
|
module: "gfx".into(),
|
|
name: "draw_disc".into(),
|
|
version: 1,
|
|
arg_slots: 5,
|
|
ret_slots: 0,
|
|
},
|
|
SyscallDecl {
|
|
module: "gfx".into(),
|
|
name: "draw_text".into(),
|
|
version: 1,
|
|
arg_slots: 4,
|
|
ret_slots: 0,
|
|
},
|
|
SyscallDecl {
|
|
module: "log".into(),
|
|
name: "write".into(),
|
|
version: 1,
|
|
arg_slots: 2,
|
|
ret_slots: 0,
|
|
},
|
|
SyscallDecl {
|
|
module: "gfx".into(),
|
|
name: "set_sprite".into(),
|
|
version: 1,
|
|
arg_slots: 10,
|
|
ret_slots: 1,
|
|
},
|
|
];
|
|
|
|
heavy_load(&mut rom);
|
|
// light_load(&mut rom);
|
|
|
|
let functions = vec![FunctionMeta {
|
|
code_offset: 0,
|
|
code_len: rom.len() as u32,
|
|
param_slots: 0,
|
|
local_slots: 2,
|
|
return_slots: 0,
|
|
max_stack_slots: 16,
|
|
}];
|
|
|
|
let module = BytecodeModule {
|
|
version: 0,
|
|
const_pool: vec![
|
|
ConstantPoolEntry::String("stress".into()),
|
|
ConstantPoolEntry::String("frame".into()),
|
|
ConstantPoolEntry::String("missing_glyph_bank".into()),
|
|
],
|
|
functions,
|
|
code: rom,
|
|
debug_info: Some(DebugInfo {
|
|
pc_to_span: vec![],
|
|
function_names: vec![(0, "main".into())],
|
|
}),
|
|
exports: vec![Export { symbol: "main".into(), func_idx: 0 }],
|
|
syscalls,
|
|
};
|
|
|
|
let bytes = module.serialize();
|
|
|
|
let mut out_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
out_dir.pop(); // pbxgen-stress
|
|
out_dir.pop(); // tools
|
|
out_dir.pop(); // crates
|
|
out_dir.push("test-cartridges");
|
|
out_dir.push("stress-console");
|
|
fs::create_dir_all(&out_dir)?;
|
|
fs::write(out_dir.join("program.pbx"), bytes)?;
|
|
let assets_pa_path = out_dir.join("assets.pa");
|
|
if assets_pa_path.exists() {
|
|
fs::remove_file(&assets_pa_path)?;
|
|
}
|
|
fs::write(out_dir.join("manifest.json"), b"{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": 1,\n \"title\": \"Stress Console\",\n \"app_version\": \"0.1.0\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"gfx\", \"log\"]\n}\n")?;
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn heavy_load(rom: &mut Vec<u8>) {
|
|
// Single function 0: main
|
|
// Everything runs here — no coroutines, no SPAWN, no YIELD.
|
|
//
|
|
// Global 0 = t (frame counter)
|
|
// Local 0 = scratch
|
|
// Local 1 = loop counter for discs
|
|
//
|
|
// Loop:
|
|
// t = (t + 1)
|
|
// clear screen
|
|
// draw 500 discs using t for animation
|
|
// draw 20 texts using t for animation
|
|
// RET (runtime handles the frame loop)
|
|
|
|
// --- init locals ---
|
|
// local 0: scratch
|
|
// local 1: loop counter for discs
|
|
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 0\nPUSH_I32 0\nSET_LOCAL 1"));
|
|
|
|
// --- t = (t + 1) ---
|
|
// t is global 0 to persist across prepare_call resets
|
|
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 1\nADD\nSET_GLOBAL 0"));
|
|
|
|
// --- clear screen ---
|
|
rom.extend(asm("PUSH_I32 0\nHOSTCALL 0"));
|
|
// --- call status-first syscall path once per frame and drop status ---
|
|
rom.extend(asm(
|
|
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 4\nPOP_N 1",
|
|
));
|
|
|
|
// --- draw 500 discs ---
|
|
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
|
let disc_loop_start = rom.len() as u32;
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 500\nLT"));
|
|
let jif_disc_end_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
|
|
|
// x = (t * (i+7) + i * 13) % 320
|
|
rom.extend(asm("GET_GLOBAL 0\nGET_LOCAL 1\nPUSH_I32 7\nADD\nMUL\nGET_LOCAL 1\nPUSH_I32 13\nMUL\nADD\nPUSH_I32 320\nMOD"));
|
|
// y = (t * (i+11) + i * 17) % 180
|
|
rom.extend(asm("GET_GLOBAL 0\nGET_LOCAL 1\nPUSH_I32 11\nADD\nMUL\nGET_LOCAL 1\nPUSH_I32 17\nMUL\nADD\nPUSH_I32 180\nMOD"));
|
|
// r = ( (i*13) % 20 ) + 5
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 13\nMUL\nPUSH_I32 20\nMOD\nPUSH_I32 5\nADD"));
|
|
// border color = (i * 1234) & 0xFFFF
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1234\nMUL\nPUSH_I32 65535\nBIT_AND"));
|
|
// fill color = (i * 5678 + t) & 0xFFFF
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 5678\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 65535\nBIT_AND"));
|
|
// HOSTCALL gfx.draw_disc (x, y, r, border, fill)
|
|
rom.extend(asm("HOSTCALL 1"));
|
|
|
|
// i++
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nADD\nSET_LOCAL 1"));
|
|
let jmp_disc_loop_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP 0"));
|
|
let disc_loop_end = rom.len() as u32;
|
|
|
|
// --- draw 20 texts ---
|
|
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
|
let text_loop_start = rom.len() as u32;
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 20\nLT"));
|
|
let jif_text_end_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
|
|
|
// x = (t * 3 + i * 40) % 320
|
|
rom.extend(asm(
|
|
"GET_GLOBAL 0\nPUSH_I32 3\nMUL\nGET_LOCAL 1\nPUSH_I32 40\nMUL\nADD\nPUSH_I32 320\nMOD",
|
|
));
|
|
// y = (i * 30 + t) % 180
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 30\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 180\nMOD"));
|
|
// string (toggle between "stress" and "frame")
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nBIT_AND\nPUSH_I32 0\nNEQ"));
|
|
let jif_text_alt_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
|
rom.extend(asm("PUSH_CONST 0")); // "stress"
|
|
let jmp_text_join_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP 0"));
|
|
let text_alt_target = rom.len() as u32;
|
|
rom.extend(asm("PUSH_CONST 1")); // "frame"
|
|
let text_join_target = rom.len() as u32;
|
|
|
|
// color = (t * 10 + i * 1000) & 0xFFFF
|
|
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 10\nMUL\nGET_LOCAL 1\nPUSH_I32 1000\nMUL\nADD\nPUSH_I32 65535\nBIT_AND"));
|
|
// HOSTCALL gfx.draw_text (x, y, str, color)
|
|
rom.extend(asm("HOSTCALL 2"));
|
|
|
|
// i++
|
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nADD\nSET_LOCAL 1"));
|
|
let jmp_text_loop_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP 0"));
|
|
let text_loop_end = rom.len() as u32;
|
|
|
|
// --- log every 60 frames ---
|
|
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 60\nMOD\nPUSH_I32 0\nEQ"));
|
|
let jif_log_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
|
rom.extend(asm("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 3"));
|
|
let after_log = rom.len() as u32;
|
|
|
|
// --- end of function ---
|
|
rom.extend(asm("FRAME_SYNC\nRET"));
|
|
|
|
// --- Patch jump targets ---
|
|
let patch = |buf: &mut Vec<u8>, imm_offset: usize, target: u32| {
|
|
buf[imm_offset..imm_offset + 4].copy_from_slice(&target.to_le_bytes());
|
|
};
|
|
|
|
patch(rom, jif_disc_end_offset, disc_loop_end);
|
|
patch(rom, jmp_disc_loop_offset, disc_loop_start);
|
|
|
|
patch(rom, jif_text_end_offset, text_loop_end);
|
|
patch(rom, jif_text_alt_offset, text_alt_target);
|
|
patch(rom, jmp_text_join_offset, text_join_target);
|
|
patch(rom, jmp_text_loop_offset, text_loop_start);
|
|
|
|
patch(rom, jif_log_offset, after_log);
|
|
}
|