Compare commits

...

3 Commits

Author SHA1 Message Date
f50a3f127f
regenerate PBX test cartridges
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
2026-07-15 08:04:29 +01:00
f0f3f972ce
fix PBX executable format identity 2026-07-15 08:01:47 +01:00
b2d2533acb
Fix PBX problems 2026-07-15 07:55:52 +01:00
12 changed files with 113 additions and 41 deletions

View File

@ -19,6 +19,6 @@ pub use assembler::{assemble, AsmError};
pub use decoder::{decode_next, DecodeError};
pub use disassembler::disassemble;
pub use layout::{compute_function_layouts, FunctionLayout};
pub use model::{BytecodeLoader, FunctionMeta, LoadError, SyscallDecl};
pub use model::{BytecodeLoader, FunctionMeta, LoadError, SyscallDecl, PBX_MAGIC};
pub use program_image::ProgramImage;
pub use value::{string_materialization_count, HeapRef, Value};

View File

@ -80,9 +80,11 @@ const SECTION_KIND_DEBUG: u32 = 3;
const SECTION_KIND_EXPORTS: u32 = 4;
const SECTION_KIND_SYSCALLS: u32 = 5;
/// Represents the final serialized format of a PBS v0 module.
pub const PBX_MAGIC: [u8; 4] = *b"PBX\0";
/// Represents the final serialized format of a PBX v0 module.
///
/// This structure is a pure data container for the PBS format. It does NOT
/// This structure is a pure data container for the PBX format. It does NOT
/// contain any linker-like logic (symbol resolution, patching, etc.).
/// All multi-module programs must be flattened and linked by the compiler
/// before being serialized into this format.
@ -126,8 +128,8 @@ impl BytecodeModule {
final_sections.push((SECTION_KIND_SYSCALLS, syscall_data));
let mut out = Vec::new();
// Magic "PBS\0"
out.extend_from_slice(b"PBS\0");
// Magic "PBX\0"
out.extend_from_slice(&PBX_MAGIC);
// Version 0
out.extend_from_slice(&0u16.to_le_bytes());
// Endianness 0 (Little Endian), Reserved
@ -272,8 +274,8 @@ impl BytecodeLoader {
return Err(LoadError::UnexpectedEof);
}
// Magic "PBS\0"
if &bytes[0..4] != b"PBS\0" {
// Magic "PBX\0"
if bytes[0..4] != PBX_MAGIC[..] {
return Err(LoadError::InvalidMagic);
}
@ -708,7 +710,7 @@ mod tests {
fn create_header(section_count: u32) -> Vec<u8> {
let mut h = vec![0u8; 32];
h[0..4].copy_from_slice(b"PBS\0");
h[0..4].copy_from_slice(&PBX_MAGIC);
h[4..6].copy_from_slice(&0u16.to_le_bytes()); // version
h[6] = 0; // endianness
h[8..12].copy_from_slice(&section_count.to_le_bytes());
@ -727,7 +729,7 @@ mod tests {
}
}
fn build_pbs_with_sections(sections: Vec<(u32, Vec<u8>)>) -> Vec<u8> {
fn build_pbx_with_sections(sections: Vec<(u32, Vec<u8>)>) -> Vec<u8> {
let mut data = create_header(sections.len() as u32);
let mut offset = 32 + (sections.len() as u32 * 12);
@ -897,7 +899,7 @@ mod tests {
#[test]
fn test_malformed_sysc_section_is_rejected() {
let data = build_pbs_with_sections(vec![(SECTION_KIND_SYSCALLS, vec![1, 0, 0])]);
let data = build_pbx_with_sections(vec![(SECTION_KIND_SYSCALLS, vec![1, 0, 0])]);
assert_eq!(BytecodeLoader::load(&data), Err(LoadError::MalformedSection));
}
@ -914,7 +916,7 @@ mod tests {
sysc.extend_from_slice(&0u16.to_le_bytes());
sysc.extend_from_slice(&0u16.to_le_bytes());
let data = build_pbs_with_sections(vec![(SECTION_KIND_SYSCALLS, sysc)]);
let data = build_pbx_with_sections(vec![(SECTION_KIND_SYSCALLS, sysc)]);
assert_eq!(BytecodeLoader::load(&data), Err(LoadError::InvalidUtf8));
}

View File

@ -4,7 +4,7 @@ use crate::value::Value;
use std::collections::HashMap;
use std::sync::Arc;
/// Represents a fully linked, executable PBS program image.
/// Represents a fully linked, executable PBX program image.
///
/// Under the Prometeu architecture, the ProgramImage is a "closed-world" artifact
/// produced by the compiler. All linking, relocation, and symbol resolution

View File

@ -2627,7 +2627,7 @@ mod tests {
fn test_loader_hardening_unsupported_version() {
let mut vm = VirtualMachine::default();
let mut header = vec![0u8; 32];
header[0..4].copy_from_slice(b"PBS\0");
header[0..4].copy_from_slice(&prometeu_bytecode::PBX_MAGIC);
header[4..6].copy_from_slice(&1u16.to_le_bytes()); // version 1 (unsupported)
let res = vm.initialize(header);
@ -2635,16 +2635,16 @@ mod tests {
}
#[test]
fn test_loader_hardening_malformed_pbs_v0() {
fn test_loader_hardening_malformed_pbx_v0() {
let mut vm = VirtualMachine::default();
let mut header = vec![0u8; 32];
header[0..4].copy_from_slice(b"PBS\0");
header[0..4].copy_from_slice(&prometeu_bytecode::PBX_MAGIC);
header[8..12].copy_from_slice(&1u32.to_le_bytes()); // 1 section claimed but none provided
let res = vm.initialize(header);
match res {
Err(VmInitError::ImageLoadFailed(prometeu_bytecode::LoadError::UnexpectedEof)) => {}
_ => panic!("Expected PbsV0LoadFailed(UnexpectedEof), got {:?}", res),
_ => panic!("Expected PBX v0 load failure UnexpectedEof, got {:?}", res),
}
}
@ -3084,7 +3084,7 @@ mod tests {
fn test_loader_hardening_missing_sysc_section() {
let mut vm = VirtualMachine::default();
let mut header = vec![0u8; 32];
header[0..4].copy_from_slice(b"PBS\0");
header[0..4].copy_from_slice(&prometeu_bytecode::PBX_MAGIC);
let res = vm.initialize(header);

View File

@ -83,30 +83,32 @@ impl VirtualMachine {
self.scheduler = Scheduler::new();
self.current_coro = None;
let program = if program_bytes.starts_with(b"PBS\0") {
match prometeu_bytecode::BytecodeLoader::load(&program_bytes) {
Ok(mut module) => {
patch_module_hostcalls(&mut module, self.capabilities)
.map_err(VmInitError::LoaderPatchFailed)?;
let program = match prometeu_bytecode::BytecodeLoader::load(&program_bytes) {
Ok(mut module) => {
patch_module_hostcalls(&mut module, self.capabilities)
.map_err(VmInitError::LoaderPatchFailed)?;
let max_stacks = Verifier::verify(&module.code, &module.functions)
.map_err(|e| VmInitError::VerificationFailed(format!("{:?}", e)))?;
let max_stacks = Verifier::verify(&module.code, &module.functions)
.map_err(|e| VmInitError::VerificationFailed(format!("{:?}", e)))?;
let mut program = ProgramImage::from(module);
let mut functions = program.functions.as_ref().to_vec();
for (func, max_stack) in functions.iter_mut().zip(max_stacks) {
func.max_stack_slots = max_stack;
}
program.functions = std::sync::Arc::from(functions);
program
let mut program = ProgramImage::from(module);
let mut functions = program.functions.as_ref().to_vec();
for (func, max_stack) in functions.iter_mut().zip(max_stacks) {
func.max_stack_slots = max_stack;
}
Err(prometeu_bytecode::LoadError::InvalidVersion) => {
return Err(VmInitError::UnsupportedFormat);
}
Err(e) => return Err(VmInitError::ImageLoadFailed(e)),
program.functions = std::sync::Arc::from(functions);
program
}
} else {
return Err(VmInitError::InvalidFormat);
Err(prometeu_bytecode::LoadError::InvalidVersion) => {
return Err(VmInitError::UnsupportedFormat);
}
Err(prometeu_bytecode::LoadError::InvalidMagic) => {
return Err(VmInitError::InvalidFormat);
}
Err(prometeu_bytecode::LoadError::UnexpectedEof) if program_bytes.len() < 32 => {
return Err(VmInitError::InvalidFormat);
}
Err(e) => return Err(VmInitError::ImageLoadFailed(e)),
};
let pc = program

View File

@ -2,8 +2,8 @@ use prometeu_vm::{VirtualMachine, VmInitError};
#[test]
fn invalid_image_format_is_rejected_before_execution() {
// Provide bytes that are not a valid PBS image. The VM must reject it with InvalidFormat.
let program_bytes = b"NOT_PBS_IMAGE".to_vec();
// Provide bytes that are not a valid PBX image. The VM must reject it with InvalidFormat.
let program_bytes = b"NOT_PBX_IMAGE".to_vec();
let mut vm = VirtualMachine::default();
let result = vm.initialize(program_bytes);
assert!(matches!(result, Err(VmInitError::InvalidFormat)));

View File

@ -130,7 +130,7 @@ mod tests {
fn generated_program_is_pbx_module_with_expected_syscalls() {
let bytes = build_program();
assert!(bytes.starts_with(b"PBS\0"));
assert!(bytes.starts_with(&prometeu_bytecode::PBX_MAGIC));
assert!(bytes.len() > 64);
}

View File

@ -1,4 +1,4 @@
{"type":"meta","next_id":{"DSC":48,"AGD":51,"DEC":42,"PLN":173,"LSN":56,"CLSN":1}}
{"type":"meta","next_id":{"DSC":49,"AGD":52,"DEC":43,"PLN":174,"LSN":57,"CLSN":1}}
{"type":"discussion","id":"DSC-0044","status":"done","ticket":"hub-suspended-game-kill-affordance","title":"Hub Suspended Game Kill Affordance","created_at":"2026-07-05","updated_at":"2026-07-05","tags":["hub","lifecycle","game","ui"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0054","file":"discussion/lessons/DSC-0044-hub-suspended-game-kill-affordance/LSN-0054-manual-hub-kill-must-share-game-termination-cleanup.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]}
{"type":"discussion","id":"DSC-0043","status":"done","ticket":"system-os-cartridge-switch-orchestrator","title":"SystemOS Cartridge Switch Orchestrator","created_at":"2026-07-03","updated_at":"2026-07-05","tags":["runtime","os","lifecycle","game","cartridge","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0043-system-os-cartridge-switch-orchestrator/LSN-0053-game-switching-is-lifecycle-replacement-not-loader-work.md","status":"done","created_at":"2026-07-05","updated_at":"2026-07-05"}]}
{"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}
@ -46,3 +46,4 @@
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0046","file":"discussion/lessons/DSC-0037-rgba8888-framebuffer-and-pixel-format-direction/LSN-0046-pixel-format-contracts-must-move-as-one-surface.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23"}]}
{"type":"discussion","id":"DSC-0046","status":"done","ticket":"runtime-owned-variable-glyph-bank-palette-protocol","title":"Runtime-Owned Variable Glyph Bank Palette Protocol","created_at":"2026-07-14","updated_at":"2026-07-14","tags":["runtime","gfx","assets","glyph-bank","palette-serialization","protocol"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0055","file":"discussion/lessons/DSC-0046-runtime-owned-variable-glyph-bank-palette-protocol/LSN-0055-runtime-owned-asset-protocols-must-align-payload-residency-and-lookup.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]}
{"type":"discussion","id":"DSC-0047","status":"open","ticket":"portable-host-backend-strategy","title":"Portable Host Backend Strategy","created_at":"2026-07-14","updated_at":"2026-07-14","tags":["host","desktop","portable","sdl","backend","rendering","input","audio","linux","android","handheld"],"agendas":[{"id":"AGD-0050","file":"AGD-0050-portable-host-backend-strategy.md","status":"open","created_at":"2026-07-14","updated_at":"2026-07-14"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0048","status":"done","ticket":"corrigir-identificacao-formato-pbx","title":"Corrigir Identificacao do Formato PBX","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["runtime","bytecode","pbx","loader","format","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0048-corrigir-identificacao-formato-pbx/LSN-0056-runtime-artifact-identity-must-not-borrow-frontend-names.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}

View File

@ -0,0 +1,61 @@
---
id: LSN-0056
ticket: corrigir-identificacao-formato-pbx
title: Runtime Artifact Identity Must Not Borrow Frontend Names
created: 2026-07-15
tags: [runtime, bytecode, pbx, frontend, format-boundary]
---
## Context
Prometeu accepts bytecode produced by multiple frontends. PBS is the base
frontend language, while PBX is the executable bytecode artifact consumed by the
runtime.
The runtime had inherited PBS terminology in the executable bytecode path,
including the serialized magic number, comments, tests, and VM loader checks.
That made one frontend appear to own the runtime executable format.
## Key Decisions
### PBX Executable Format Identity
**What:** PBX is the runtime executable bytecode format, and `PBX\0` is its only
valid magic number.
**Why:** Runtime artifact identity must be independent from any frontend that can
produce the artifact. Naming the executable format after PBS would make the
runtime boundary look language-specific.
**Trade-offs:** The correction intentionally rejects old PBS-prefixed executable
artifacts instead of carrying a compatibility alias. This keeps the format
contract singular, but requires stale generated fixtures to be regenerated or
updated.
## Patterns and Algorithms
Put file-format identity in the module that serializes and deserializes the
format. Loaders and higher-level runtime code should call that boundary instead
of duplicating magic-number checks.
When renaming an artifact identity, update all fixture construction and generator
assertions in the same pass as production serialization. Mixed fixture identity
usually means tests are asserting historical spelling rather than current
contract.
## Pitfalls
Do not globally erase frontend names. PBS remains correct when the subject is
the PBS language, stdlib, or frontend-facing API. It is wrong only when it names
the executable bytecode artifact.
Do not preserve old magic values just to make a rename feel less disruptive. If
there is no compatibility requirement for old artifacts, aliases keep the
incorrect model alive.
## Takeaways
- Runtime artifact names should describe the runtime contract, not the compiler
or frontend that produced the artifact.
- Magic numbers are normative format identity, not cosmetic labels.
- Loader code should delegate file-format validation to the format owner.

View File

@ -65,6 +65,12 @@ dedicated `System` profile ABI and stdlib/framework.
## 3 Syscall Resolution
PBX, Prometeu Bytecode Executable, is the runtime executable bytecode format.
Its canonical file magic is `PBX\0`. The former PBS-prefixed magic is not valid
for PBX artifacts: PBS names a frontend language, while PBX names the executable
artifact accepted by the runtime. This distinction is required because Prometeu
bytecode can be produced by multiple frontends.
The host maintains a registry:
```