diff --git a/crates/tools/pbxgen-stress/Cargo.toml b/crates/tools/pbxgen-stress/Cargo.toml new file mode 100644 index 00000000..b2ce9839 --- /dev/null +++ b/crates/tools/pbxgen-stress/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pbxgen-stress" +version = "0.1.0" +edition = "2021" + +[dependencies] +prometeu-bytecode = { path = "../../console/prometeu-bytecode" } +anyhow = "1" diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs new file mode 100644 index 00000000..c0cd326d --- /dev/null +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -0,0 +1,180 @@ +use anyhow::Result; +use prometeu_bytecode::assembler::assemble; +use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; +use std::fs; +use std::path::PathBuf; + +fn asm(s: &str) -> Vec { assemble(s).expect("assemble") } + +pub fn generate() -> Result<()> { + let mut rom: Vec = Vec::new(); + + 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()), + ], + 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 }], + }; + + 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)?; + if !out_dir.join("assets.pa").exists() { + fs::write(out_dir.join("assets.pa"), &[] as &[u8])?; + } + if !out_dir.join("manifest.json").exists() { + 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 \"entrypoint\": \"main\"\n}\n")?; + } + Ok(()) +} + +#[allow(dead_code)] +fn heavy_load(mut rom: &mut Vec) { + // 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 100 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\nSYSCALL 0x1010")); + + // --- 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")); + // SYSCALL GfxDrawDisc (x, y, r, border, fill) + rom.extend(asm("SYSCALL 0x1005")); + + // 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")); + 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")); + // SYSCALL GfxDrawText (x, y, str, color) + rom.extend(asm("SYSCALL 0x1008")); + + // 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\nSYSCALL 0x5001")); + let after_log = rom.len() as u32; + + // --- end of function --- + rom.extend(asm("FRAME_SYNC\nRET")); + + // --- Patch jump targets --- + let patch = |buf: &mut Vec, imm_offset: usize, target: u32| { + buf[imm_offset..imm_offset + 4].copy_from_slice(&target.to_le_bytes()); + }; + + patch(&mut rom, jif_disc_end_offset, disc_loop_end); + patch(&mut rom, jmp_disc_loop_offset, disc_loop_start); + + patch(&mut rom, jif_text_end_offset, text_loop_end); + patch(&mut rom, jif_text_alt_offset, text_alt_target); + patch(&mut rom, jmp_text_join_offset, text_join_target); + patch(&mut rom, jmp_text_loop_offset, text_loop_start); + + patch(&mut rom, jif_log_offset, after_log); +} + +// fn light_load(rom: &mut Vec) { +// // Single function 0: main +// // Only paints Purple: 0x780F once. +// // The runtime handles calling this function repeatedly every tick. +// +// // --- clear screen (Purple 0x780F) --- +// rom.extend(asm("PUSH_I32 30735\nSYSCALL 0x1010\nFRAME_SYNC\nRET")); // 30735 is 0x780F +// } diff --git a/crates/tools/pbxgen-stress/src/main.rs b/crates/tools/pbxgen-stress/src/main.rs new file mode 100644 index 00000000..5ad26402 --- /dev/null +++ b/crates/tools/pbxgen-stress/src/main.rs @@ -0,0 +1,3 @@ +use anyhow::Result; + +fn main() -> Result<()> { pbxgen_stress::generate() } diff --git a/test-cartridges/stress-console/assets.pa b/test-cartridges/stress-console/assets.pa new file mode 100644 index 00000000..e69de29b diff --git a/test-cartridges/stress-console/manifest.json b/test-cartridges/stress-console/manifest.json new file mode 100644 index 00000000..c28e4851 --- /dev/null +++ b/test-cartridges/stress-console/manifest.json @@ -0,0 +1,9 @@ +{ + "magic": "PMTU", + "cartridge_version": 1, + "app_id": 1, + "title": "Stress Console", + "app_version": "0.1.0", + "app_mode": "Game", + "entrypoint": "main" +} diff --git a/test-cartridges/stress-console/program.pbx b/test-cartridges/stress-console/program.pbx new file mode 100644 index 00000000..a52551b1 Binary files /dev/null and b/test-cartridges/stress-console/program.pbx differ