adjustments on capabilities on cartridges
This commit is contained in:
parent
cfdca93160
commit
d5ef8a2003
@ -1,22 +1,22 @@
|
|||||||
mod abi;
|
mod abi;
|
||||||
|
pub mod assembler;
|
||||||
mod decoder;
|
mod decoder;
|
||||||
|
mod disassembler;
|
||||||
|
pub mod isa; // canonical ISA boundary (core and future profiles)
|
||||||
mod layout;
|
mod layout;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
mod opcode;
|
mod opcode;
|
||||||
mod opcode_spec;
|
mod opcode_spec;
|
||||||
mod program_image;
|
mod program_image;
|
||||||
mod value;
|
mod value;
|
||||||
pub mod isa; // canonical ISA boundary (core and future profiles)
|
|
||||||
mod disassembler;
|
|
||||||
pub mod assembler;
|
|
||||||
|
|
||||||
pub use abi::{
|
pub use abi::{
|
||||||
TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_ILLEGAL_INSTRUCTION, TRAP_INVALID_FUNC,
|
TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_ILLEGAL_INSTRUCTION, TRAP_INVALID_FUNC,
|
||||||
TRAP_INVALID_LOCAL, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE,
|
TRAP_INVALID_LOCAL, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE,
|
||||||
};
|
};
|
||||||
|
pub use assembler::{assemble, AsmError};
|
||||||
pub use decoder::{decode_next, DecodeError};
|
pub use decoder::{decode_next, DecodeError};
|
||||||
pub use disassembler::disassemble;
|
pub use disassembler::disassemble;
|
||||||
pub use assembler::{assemble, AsmError};
|
|
||||||
pub use layout::{compute_function_layouts, FunctionLayout};
|
pub use layout::{compute_function_layouts, FunctionLayout};
|
||||||
pub use model::{BytecodeLoader, FunctionMeta, LoadError};
|
pub use model::{BytecodeLoader, FunctionMeta, LoadError};
|
||||||
pub use program_image::ProgramImage;
|
pub use program_image::ProgramImage;
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
use prometeu_bytecode::{assemble, disassemble};
|
|
||||||
use prometeu_bytecode::isa::core::CoreOpCode;
|
use prometeu_bytecode::isa::core::CoreOpCode;
|
||||||
|
use prometeu_bytecode::{assemble, disassemble};
|
||||||
|
|
||||||
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
||||||
out.extend_from_slice(&(op as u16).to_le_bytes());
|
out.extend_from_slice(&(op as u16).to_le_bytes());
|
||||||
if let Some(bytes) = imm { out.extend_from_slice(bytes); }
|
if let Some(bytes) = imm {
|
||||||
|
out.extend_from_slice(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use prometeu_bytecode::isa::core::CoreOpCode;
|
|||||||
|
|
||||||
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
||||||
out.extend_from_slice(&(op as u16).to_le_bytes());
|
out.extend_from_slice(&(op as u16).to_le_bytes());
|
||||||
if let Some(bytes) = imm { out.extend_from_slice(bytes); }
|
if let Some(bytes) = imm {
|
||||||
|
out.extend_from_slice(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use crate::asset::{AssetEntry, PreloadEntry};
|
use crate::asset::{AssetEntry, PreloadEntry};
|
||||||
|
use crate::syscalls::CapFlags;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
@ -14,6 +15,7 @@ pub struct Cartridge {
|
|||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
pub app_mode: AppMode,
|
pub app_mode: AppMode,
|
||||||
pub entrypoint: String,
|
pub entrypoint: String,
|
||||||
|
pub capabilities: CapFlags,
|
||||||
pub program: Vec<u8>,
|
pub program: Vec<u8>,
|
||||||
pub assets: Vec<u8>,
|
pub assets: Vec<u8>,
|
||||||
pub asset_table: Vec<AssetEntry>,
|
pub asset_table: Vec<AssetEntry>,
|
||||||
@ -27,6 +29,7 @@ pub struct CartridgeDTO {
|
|||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
pub app_mode: AppMode,
|
pub app_mode: AppMode,
|
||||||
pub entrypoint: String,
|
pub entrypoint: String,
|
||||||
|
pub capabilities: CapFlags,
|
||||||
pub program: Vec<u8>,
|
pub program: Vec<u8>,
|
||||||
pub assets: Vec<u8>,
|
pub assets: Vec<u8>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -43,6 +46,7 @@ impl From<CartridgeDTO> for Cartridge {
|
|||||||
app_version: dto.app_version,
|
app_version: dto.app_version,
|
||||||
app_mode: dto.app_mode,
|
app_mode: dto.app_mode,
|
||||||
entrypoint: dto.entrypoint,
|
entrypoint: dto.entrypoint,
|
||||||
|
capabilities: dto.capabilities,
|
||||||
program: dto.program,
|
program: dto.program,
|
||||||
assets: dto.assets,
|
assets: dto.assets,
|
||||||
asset_table: dto.asset_table,
|
asset_table: dto.asset_table,
|
||||||
@ -61,6 +65,21 @@ pub enum CartridgeError {
|
|||||||
IoError,
|
IoError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum Capability {
|
||||||
|
None,
|
||||||
|
System,
|
||||||
|
Gfx,
|
||||||
|
Input,
|
||||||
|
Audio,
|
||||||
|
Fs,
|
||||||
|
Log,
|
||||||
|
Asset,
|
||||||
|
Bank,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CartridgeManifest {
|
pub struct CartridgeManifest {
|
||||||
pub magic: String,
|
pub magic: String,
|
||||||
@ -71,6 +90,8 @@ pub struct CartridgeManifest {
|
|||||||
pub app_mode: AppMode,
|
pub app_mode: AppMode,
|
||||||
pub entrypoint: String,
|
pub entrypoint: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub capabilities: Vec<Capability>,
|
||||||
|
#[serde(default)]
|
||||||
pub asset_table: Vec<AssetEntry>,
|
pub asset_table: Vec<AssetEntry>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub preload: Vec<PreloadEntry>,
|
pub preload: Vec<PreloadEntry>,
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
use crate::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest};
|
use crate::cartridge::{Capability, Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest};
|
||||||
|
use crate::syscalls::{CapFlags, caps};
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@ -43,6 +45,8 @@ impl DirectoryCartridgeLoader {
|
|||||||
return Err(CartridgeError::UnsupportedVersion);
|
return Err(CartridgeError::UnsupportedVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let capabilities = normalize_capabilities(&manifest.capabilities)?;
|
||||||
|
|
||||||
let program_path = path.join("program.pbx");
|
let program_path = path.join("program.pbx");
|
||||||
if !program_path.exists() {
|
if !program_path.exists() {
|
||||||
return Err(CartridgeError::MissingProgram);
|
return Err(CartridgeError::MissingProgram);
|
||||||
@ -63,6 +67,7 @@ impl DirectoryCartridgeLoader {
|
|||||||
app_version: manifest.app_version,
|
app_version: manifest.app_version,
|
||||||
app_mode: manifest.app_mode,
|
app_mode: manifest.app_mode,
|
||||||
entrypoint: manifest.entrypoint,
|
entrypoint: manifest.entrypoint,
|
||||||
|
capabilities,
|
||||||
program,
|
program,
|
||||||
assets,
|
assets,
|
||||||
asset_table: manifest.asset_table,
|
asset_table: manifest.asset_table,
|
||||||
@ -80,3 +85,157 @@ impl PackedCartridgeLoader {
|
|||||||
Err(CartridgeError::InvalidFormat)
|
Err(CartridgeError::InvalidFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_capabilities(capabilities: &[Capability]) -> Result<CapFlags, CartridgeError> {
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let mut normalized = caps::NONE;
|
||||||
|
|
||||||
|
for capability in capabilities {
|
||||||
|
if !seen.insert(*capability) {
|
||||||
|
return Err(CartridgeError::InvalidManifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized |= match capability {
|
||||||
|
Capability::None => caps::NONE,
|
||||||
|
Capability::System => caps::SYSTEM,
|
||||||
|
Capability::Gfx => caps::GFX,
|
||||||
|
Capability::Input => caps::INPUT,
|
||||||
|
Capability::Audio => caps::AUDIO,
|
||||||
|
Capability::Fs => caps::FS,
|
||||||
|
Capability::Log => caps::LOG,
|
||||||
|
Capability::Asset => caps::ASSET,
|
||||||
|
Capability::Bank => caps::BANK,
|
||||||
|
Capability::All => caps::ALL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
static TEST_DIR_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
struct TestCartridgeDir {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCartridgeDir {
|
||||||
|
fn new(manifest: serde_json::Value) -> Self {
|
||||||
|
let unique = TEST_DIR_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("system time must be after unix epoch")
|
||||||
|
.as_nanos();
|
||||||
|
let path = std::env::temp_dir()
|
||||||
|
.join(format!("prometeu-hal-cartridge-loader-{}-{}", timestamp, unique));
|
||||||
|
|
||||||
|
fs::create_dir_all(&path).expect("must create temporary cartridge directory");
|
||||||
|
fs::write(
|
||||||
|
path.join("manifest.json"),
|
||||||
|
serde_json::to_vec_pretty(&manifest).expect("manifest must serialize"),
|
||||||
|
)
|
||||||
|
.expect("must write manifest.json");
|
||||||
|
fs::write(path.join("program.pbx"), [0x01_u8, 0x02, 0x03]).expect("must write program");
|
||||||
|
|
||||||
|
Self { path }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestCartridgeDir {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = fs::remove_dir_all(&self.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manifest_with_capabilities(capabilities: Option<Vec<&str>>) -> serde_json::Value {
|
||||||
|
let mut manifest = json!({
|
||||||
|
"magic": "PMTU",
|
||||||
|
"cartridge_version": 1,
|
||||||
|
"app_id": 1001,
|
||||||
|
"title": "Example",
|
||||||
|
"app_version": "1.0.0",
|
||||||
|
"app_mode": "Game",
|
||||||
|
"entrypoint": "main"
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(capabilities) = capabilities {
|
||||||
|
manifest["capabilities"] = json!(capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_without_capabilities_defaults_to_none() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(None));
|
||||||
|
|
||||||
|
let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load");
|
||||||
|
|
||||||
|
assert_eq!(cartridge.capabilities, caps::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_with_single_capability_normalizes_to_flag() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["gfx"])));
|
||||||
|
|
||||||
|
let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load");
|
||||||
|
|
||||||
|
assert_eq!(cartridge.capabilities, caps::GFX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_with_multiple_capabilities_combines_flags() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["gfx", "input"])));
|
||||||
|
|
||||||
|
let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load");
|
||||||
|
|
||||||
|
assert_eq!(cartridge.capabilities, caps::GFX | caps::INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_with_all_capability_normalizes_to_all_flags() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["all"])));
|
||||||
|
|
||||||
|
let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load");
|
||||||
|
|
||||||
|
assert_eq!(cartridge.capabilities, caps::ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_with_none_capability_keeps_zero_flags() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["none"])));
|
||||||
|
|
||||||
|
let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load");
|
||||||
|
|
||||||
|
assert_eq!(cartridge.capabilities, caps::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_with_duplicate_capabilities_fails() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["gfx", "gfx"])));
|
||||||
|
|
||||||
|
let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(error, CartridgeError::InvalidManifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_with_unknown_capability_fails() {
|
||||||
|
let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["network"])));
|
||||||
|
|
||||||
|
let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(error, CartridgeError::InvalidManifest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -226,11 +226,7 @@ pub struct SyscallResolved {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum LoadError {
|
pub enum LoadError {
|
||||||
/// The (module, name, version) triple is not known by the host.
|
/// The (module, name, version) triple is not known by the host.
|
||||||
UnknownSyscall {
|
UnknownSyscall { module: &'static str, name: &'static str, version: u16 },
|
||||||
module: &'static str,
|
|
||||||
name: &'static str,
|
|
||||||
version: u16,
|
|
||||||
},
|
|
||||||
/// The cartridge lacks required capabilities for the syscall.
|
/// The cartridge lacks required capabilities for the syscall.
|
||||||
MissingCapability {
|
MissingCapability {
|
||||||
required: CapFlags,
|
required: CapFlags,
|
||||||
@ -244,7 +240,11 @@ pub enum LoadError {
|
|||||||
/// Resolve a canonical syscall identity to its numeric id and metadata.
|
/// Resolve a canonical syscall identity to its numeric id and metadata.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the identity is unknown.
|
/// Returns `None` if the identity is unknown.
|
||||||
pub fn resolve_syscall(module: &'static str, name: &'static str, version: u16) -> Option<SyscallResolved> {
|
pub fn resolve_syscall(
|
||||||
|
module: &'static str,
|
||||||
|
name: &'static str,
|
||||||
|
version: u16,
|
||||||
|
) -> Option<SyscallResolved> {
|
||||||
for entry in SYSCALL_TABLE {
|
for entry in SYSCALL_TABLE {
|
||||||
if entry.meta.module == module && entry.meta.name == name && entry.meta.version == version {
|
if entry.meta.module == module && entry.meta.name == name && entry.meta.version == version {
|
||||||
return Some(SyscallResolved { id: entry.meta.id, meta: entry.meta });
|
return Some(SyscallResolved { id: entry.meta.id, meta: entry.meta });
|
||||||
@ -266,7 +266,11 @@ pub fn resolve_program_syscalls(
|
|||||||
let mut out = Vec::with_capacity(declared.len());
|
let mut out = Vec::with_capacity(declared.len());
|
||||||
for ident in declared {
|
for ident in declared {
|
||||||
let Some(res) = resolve_syscall(ident.module, ident.name, ident.version) else {
|
let Some(res) = resolve_syscall(ident.module, ident.name, ident.version) else {
|
||||||
return Err(LoadError::UnknownSyscall { module: ident.module, name: ident.name, version: ident.version });
|
return Err(LoadError::UnknownSyscall {
|
||||||
|
module: ident.module,
|
||||||
|
name: ident.name,
|
||||||
|
version: ident.version,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
// Capability gating: required must be subset of provided
|
// Capability gating: required must be subset of provided
|
||||||
let missing = res.meta.caps & !caps;
|
let missing = res.meta.caps & !caps;
|
||||||
@ -291,74 +295,808 @@ pub fn resolve_program_syscalls(
|
|||||||
/// this table.
|
/// this table.
|
||||||
pub const SYSCALL_TABLE: &[SyscallRegistryEntry] = &[
|
pub const SYSCALL_TABLE: &[SyscallRegistryEntry] = &[
|
||||||
// --- System ---
|
// --- System ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::SystemHasCart, meta: SyscallMeta { id: 0x0001, module: "system", name: "has_cart", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::SYSTEM, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::SystemRunCart, meta: SyscallMeta { id: 0x0002, module: "system", name: "run_cart", version: 1, arg_slots: 0, ret_slots: 0, caps: caps::SYSTEM, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 50 } },
|
syscall: Syscall::SystemHasCart,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x0001,
|
||||||
|
module: "system",
|
||||||
|
name: "has_cart",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::SYSTEM,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::SystemRunCart,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x0002,
|
||||||
|
module: "system",
|
||||||
|
name: "run_cart",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::SYSTEM,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- GFX ---
|
// --- GFX ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxClear, meta: SyscallMeta { id: 0x1001, module: "gfx", name: "clear", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxFillRect, meta: SyscallMeta { id: 0x1002, module: "gfx", name: "fill_rect", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } },
|
syscall: Syscall::GfxClear,
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawLine, meta: SyscallMeta { id: 0x1003, module: "gfx", name: "draw_line", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
meta: SyscallMeta {
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawCircle, meta: SyscallMeta { id: 0x1004, module: "gfx", name: "draw_circle", version: 1, arg_slots: 4, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
id: 0x1001,
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawDisc, meta: SyscallMeta { id: 0x1005, module: "gfx", name: "draw_disc", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
module: "gfx",
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawSquare, meta: SyscallMeta { id: 0x1006, module: "gfx", name: "draw_square", version: 1, arg_slots: 6, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
name: "clear",
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxSetSprite, meta: SyscallMeta { id: 0x1007, module: "gfx", name: "set_sprite", version: 1, arg_slots: 10, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
version: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawText, meta: SyscallMeta { id: 0x1008, module: "gfx", name: "draw_text", version: 1, arg_slots: 4, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } },
|
arg_slots: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::GfxClear565, meta: SyscallMeta { id: 0x1010, module: "gfx", name: "clear_565", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } },
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxFillRect,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1002,
|
||||||
|
module: "gfx",
|
||||||
|
name: "fill_rect",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 5,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxDrawLine,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1003,
|
||||||
|
module: "gfx",
|
||||||
|
name: "draw_line",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 5,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxDrawCircle,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1004,
|
||||||
|
module: "gfx",
|
||||||
|
name: "draw_circle",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 4,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxDrawDisc,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1005,
|
||||||
|
module: "gfx",
|
||||||
|
name: "draw_disc",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 5,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxDrawSquare,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1006,
|
||||||
|
module: "gfx",
|
||||||
|
name: "draw_square",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 6,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxSetSprite,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1007,
|
||||||
|
module: "gfx",
|
||||||
|
name: "set_sprite",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 10,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxDrawText,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1008,
|
||||||
|
module: "gfx",
|
||||||
|
name: "draw_text",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 4,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::GfxClear565,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x1010,
|
||||||
|
module: "gfx",
|
||||||
|
name: "clear_565",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::GFX,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- Input ---
|
// --- Input ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::InputGetPad, meta: SyscallMeta { id: 0x2001, module: "input", name: "get_pad", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::InputGetPadPressed, meta: SyscallMeta { id: 0x2002, module: "input", name: "get_pad_pressed", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
syscall: Syscall::InputGetPad,
|
||||||
SyscallRegistryEntry { syscall: Syscall::InputGetPadReleased, meta: SyscallMeta { id: 0x2003, module: "input", name: "get_pad_released", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
meta: SyscallMeta {
|
||||||
SyscallRegistryEntry { syscall: Syscall::InputGetPadHold, meta: SyscallMeta { id: 0x2004, module: "input", name: "get_pad_hold", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
id: 0x2001,
|
||||||
SyscallRegistryEntry { syscall: Syscall::InputPadSnapshot, meta: SyscallMeta { id: 0x2010, module: "input", name: "pad_snapshot", version: 1, arg_slots: 0, ret_slots: 48, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
module: "input",
|
||||||
SyscallRegistryEntry { syscall: Syscall::InputTouchSnapshot, meta: SyscallMeta { id: 0x2011, module: "input", name: "touch_snapshot", version: 1, arg_slots: 0, ret_slots: 6, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
name: "get_pad",
|
||||||
|
version: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchGetX, meta: SyscallMeta { id: 0x2101, module: "input", name: "touch_get_x", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
arg_slots: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchGetY, meta: SyscallMeta { id: 0x2102, module: "input", name: "touch_get_y", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
ret_slots: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchIsDown, meta: SyscallMeta { id: 0x2103, module: "input", name: "touch_is_down", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
caps: caps::INPUT,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchIsPressed, meta: SyscallMeta { id: 0x2104, module: "input", name: "touch_is_pressed", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
determinism: Determinism::Deterministic,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchIsReleased, meta: SyscallMeta { id: 0x2105, module: "input", name: "touch_is_released", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
may_allocate: false,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchGetHold, meta: SyscallMeta { id: 0x2106, module: "input", name: "touch_get_hold", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
cost_hint: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::TouchGetFinger, meta: SyscallMeta { id: 0x2107, module: "input", name: "touch_get_finger", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::InputGetPadPressed,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2002,
|
||||||
|
module: "input",
|
||||||
|
name: "get_pad_pressed",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::InputGetPadReleased,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2003,
|
||||||
|
module: "input",
|
||||||
|
name: "get_pad_released",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::InputGetPadHold,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2004,
|
||||||
|
module: "input",
|
||||||
|
name: "get_pad_hold",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::InputPadSnapshot,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2010,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_snapshot",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 48,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::InputTouchSnapshot,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2011,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_snapshot",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 6,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchGetX,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2101,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_get_x",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchGetY,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2102,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_get_y",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchIsDown,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2103,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_is_down",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchIsPressed,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2104,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_is_pressed",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchIsReleased,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2105,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_is_released",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchGetHold,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2106,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_get_hold",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::TouchGetFinger,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2107,
|
||||||
|
module: "input",
|
||||||
|
name: "touch_get_finger",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- Input (Pad service-based) ---
|
// --- Input (Pad service-based) ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetUp, meta: SyscallMeta { id: 0x2200, module: "input", name: "pad_get_up", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetDown, meta: SyscallMeta { id: 0x2201, module: "input", name: "pad_get_down", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
syscall: Syscall::PadGetUp,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetLeft, meta: SyscallMeta { id: 0x2202, module: "input", name: "pad_get_left", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
meta: SyscallMeta {
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetRight, meta: SyscallMeta { id: 0x2203, module: "input", name: "pad_get_right", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
id: 0x2200,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetA, meta: SyscallMeta { id: 0x2204, module: "input", name: "pad_get_a", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
module: "input",
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetB, meta: SyscallMeta { id: 0x2205, module: "input", name: "pad_get_b", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
name: "pad_get_up",
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetX, meta: SyscallMeta { id: 0x2206, module: "input", name: "pad_get_x", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
version: 1,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetY, meta: SyscallMeta { id: 0x2207, module: "input", name: "pad_get_y", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
arg_slots: 0,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetL, meta: SyscallMeta { id: 0x2208, module: "input", name: "pad_get_l", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
ret_slots: 4,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetR, meta: SyscallMeta { id: 0x2209, module: "input", name: "pad_get_r", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
caps: caps::INPUT,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetStart, meta: SyscallMeta { id: 0x220A, module: "input", name: "pad_get_start", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
determinism: Determinism::Deterministic,
|
||||||
SyscallRegistryEntry { syscall: Syscall::PadGetSelect, meta: SyscallMeta { id: 0x220B, module: "input", name: "pad_get_select", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetDown,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2201,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_down",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetLeft,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2202,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_left",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetRight,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2203,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_right",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetA,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2204,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_a",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetB,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2205,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_b",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetX,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2206,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_x",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetY,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2207,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_y",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetL,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2208,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_l",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetR,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x2209,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_r",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetStart,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x220A,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_start",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::PadGetSelect,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x220B,
|
||||||
|
module: "input",
|
||||||
|
name: "pad_get_select",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 0,
|
||||||
|
ret_slots: 4,
|
||||||
|
caps: caps::INPUT,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- Audio ---
|
// --- Audio ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::AudioPlaySample, meta: SyscallMeta { id: 0x3001, module: "audio", name: "play_sample", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::AUDIO, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::AudioPlay, meta: SyscallMeta { id: 0x3002, module: "audio", name: "play", version: 1, arg_slots: 7, ret_slots: 0, caps: caps::AUDIO, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
syscall: Syscall::AudioPlaySample,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x3001,
|
||||||
|
module: "audio",
|
||||||
|
name: "play_sample",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 5,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::AUDIO,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::AudioPlay,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x3002,
|
||||||
|
module: "audio",
|
||||||
|
name: "play",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 7,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::AUDIO,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- FS ---
|
// --- FS ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsOpen, meta: SyscallMeta { id: 0x4001, module: "fs", name: "open", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsRead, meta: SyscallMeta { id: 0x4002, module: "fs", name: "read", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
syscall: Syscall::FsOpen,
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsWrite, meta: SyscallMeta { id: 0x4003, module: "fs", name: "write", version: 1, arg_slots: 2, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
meta: SyscallMeta {
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsClose, meta: SyscallMeta { id: 0x4004, module: "fs", name: "close", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::FS, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } },
|
id: 0x4001,
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsListDir, meta: SyscallMeta { id: 0x4005, module: "fs", name: "list_dir", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
module: "fs",
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsExists, meta: SyscallMeta { id: 0x4006, module: "fs", name: "exists", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
name: "open",
|
||||||
SyscallRegistryEntry { syscall: Syscall::FsDelete, meta: SyscallMeta { id: 0x4007, module: "fs", name: "delete", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::FsRead,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x4002,
|
||||||
|
module: "fs",
|
||||||
|
name: "read",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::FsWrite,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x4003,
|
||||||
|
module: "fs",
|
||||||
|
name: "write",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 2,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::FsClose,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x4004,
|
||||||
|
module: "fs",
|
||||||
|
name: "close",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::FsListDir,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x4005,
|
||||||
|
module: "fs",
|
||||||
|
name: "list_dir",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::FsExists,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x4006,
|
||||||
|
module: "fs",
|
||||||
|
name: "exists",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::FsDelete,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x4007,
|
||||||
|
module: "fs",
|
||||||
|
name: "delete",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::FS,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- Log ---
|
// --- Log ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::LogWrite, meta: SyscallMeta { id: 0x5001, module: "log", name: "write", version: 1, arg_slots: 2, ret_slots: 0, caps: caps::LOG, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 5 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::LogWriteTag, meta: SyscallMeta { id: 0x5002, module: "log", name: "write_tag", version: 1, arg_slots: 3, ret_slots: 0, caps: caps::LOG, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 5 } },
|
syscall: Syscall::LogWrite,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x5001,
|
||||||
|
module: "log",
|
||||||
|
name: "write",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 2,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::LOG,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::LogWriteTag,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x5002,
|
||||||
|
module: "log",
|
||||||
|
name: "write_tag",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 3,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::LOG,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
// --- Asset/Bank ---
|
// --- Asset/Bank ---
|
||||||
SyscallRegistryEntry { syscall: Syscall::AssetLoad, meta: SyscallMeta { id: 0x6001, module: "asset", name: "load", version: 1, arg_slots: 3, ret_slots: 1, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
SyscallRegistryEntry {
|
||||||
SyscallRegistryEntry { syscall: Syscall::AssetStatus, meta: SyscallMeta { id: 0x6002, module: "asset", name: "status", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 1 } },
|
syscall: Syscall::AssetLoad,
|
||||||
SyscallRegistryEntry { syscall: Syscall::AssetCommit, meta: SyscallMeta { id: 0x6003, module: "asset", name: "commit", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
meta: SyscallMeta {
|
||||||
SyscallRegistryEntry { syscall: Syscall::AssetCancel, meta: SyscallMeta { id: 0x6004, module: "asset", name: "cancel", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } },
|
id: 0x6001,
|
||||||
SyscallRegistryEntry { syscall: Syscall::BankInfo, meta: SyscallMeta { id: 0x6101, module: "bank", name: "info", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::BANK, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
module: "asset",
|
||||||
SyscallRegistryEntry { syscall: Syscall::BankSlotInfo, meta: SyscallMeta { id: 0x6102, module: "bank", name: "slot_info", version: 1, arg_slots: 2, ret_slots: 1, caps: caps::BANK, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } },
|
name: "load",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 3,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::ASSET,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::AssetStatus,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x6002,
|
||||||
|
module: "asset",
|
||||||
|
name: "status",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::ASSET,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::AssetCommit,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x6003,
|
||||||
|
module: "asset",
|
||||||
|
name: "commit",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::ASSET,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::AssetCancel,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x6004,
|
||||||
|
module: "asset",
|
||||||
|
name: "cancel",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 0,
|
||||||
|
caps: caps::ASSET,
|
||||||
|
determinism: Determinism::NonDeterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::BankInfo,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x6101,
|
||||||
|
module: "bank",
|
||||||
|
name: "info",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::BANK,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SyscallRegistryEntry {
|
||||||
|
syscall: Syscall::BankSlotInfo,
|
||||||
|
meta: SyscallMeta {
|
||||||
|
id: 0x6102,
|
||||||
|
module: "bank",
|
||||||
|
name: "slot_info",
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 2,
|
||||||
|
ret_slots: 1,
|
||||||
|
caps: caps::BANK,
|
||||||
|
determinism: Determinism::Deterministic,
|
||||||
|
may_allocate: false,
|
||||||
|
cost_hint: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Returns the metadata associated with this syscall.
|
/// Returns the metadata associated with this syscall.
|
||||||
@ -593,8 +1331,13 @@ mod tests {
|
|||||||
|
|
||||||
// (module,name,version) must be unique
|
// (module,name,version) must be unique
|
||||||
let key = (e.meta.module, e.meta.name, e.meta.version);
|
let key = (e.meta.module, e.meta.name, e.meta.version);
|
||||||
assert!(identities.insert(key), "duplicate canonical identity: ({}.{}, v{})",
|
assert!(
|
||||||
e.meta.module, e.meta.name, e.meta.version);
|
identities.insert(key),
|
||||||
|
"duplicate canonical identity: ({}.{}, v{})",
|
||||||
|
e.meta.module,
|
||||||
|
e.meta.name,
|
||||||
|
e.meta.version
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Table and explicit list sizes must match (guard against omissions).
|
// 3) Table and explicit list sizes must match (guard against omissions).
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::object::{ObjectHeader, ObjectKind};
|
|
||||||
use crate::call_frame::CallFrame;
|
use crate::call_frame::CallFrame;
|
||||||
|
use crate::object::{ObjectHeader, ObjectKind};
|
||||||
use prometeu_bytecode::{HeapRef, Value};
|
use prometeu_bytecode::{HeapRef, Value};
|
||||||
|
|
||||||
/// Internal stored object: header plus opaque payload bytes.
|
/// Internal stored object: header plus opaque payload bytes.
|
||||||
@ -50,14 +50,22 @@ pub struct Heap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Heap {
|
impl Heap {
|
||||||
pub fn new() -> Self { Self { objects: Vec::new() } }
|
pub fn new() -> Self {
|
||||||
|
Self { objects: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
/// Allocate a new object with the given kind and raw payload bytes.
|
/// Allocate a new object with the given kind and raw payload bytes.
|
||||||
/// Returns an opaque `HeapRef` handle.
|
/// Returns an opaque `HeapRef` handle.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn allocate_object(&mut self, kind: ObjectKind, payload: &[u8]) -> HeapRef {
|
pub fn allocate_object(&mut self, kind: ObjectKind, payload: &[u8]) -> HeapRef {
|
||||||
let header = ObjectHeader::new(kind, payload.len() as u32);
|
let header = ObjectHeader::new(kind, payload.len() as u32);
|
||||||
let obj = StoredObject { header, payload: payload.to_vec(), array_elems: None, closure_env: None, coroutine: None };
|
let obj = StoredObject {
|
||||||
|
header,
|
||||||
|
payload: payload.to_vec(),
|
||||||
|
array_elems: None,
|
||||||
|
closure_env: None,
|
||||||
|
coroutine: None,
|
||||||
|
};
|
||||||
let idx = self.objects.len();
|
let idx = self.objects.len();
|
||||||
// No free-list reuse in this PR: append and keep indices stable.
|
// No free-list reuse in this PR: append and keep indices stable.
|
||||||
self.objects.push(Some(obj));
|
self.objects.push(Some(obj));
|
||||||
@ -69,7 +77,13 @@ impl Heap {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn allocate_array(&mut self, elements: Vec<Value>) -> HeapRef {
|
pub fn allocate_array(&mut self, elements: Vec<Value>) -> HeapRef {
|
||||||
let header = ObjectHeader::new(ObjectKind::Array, elements.len() as u32);
|
let header = ObjectHeader::new(ObjectKind::Array, elements.len() as u32);
|
||||||
let obj = StoredObject { header, payload: Vec::new(), array_elems: Some(elements), closure_env: None, coroutine: None };
|
let obj = StoredObject {
|
||||||
|
header,
|
||||||
|
payload: Vec::new(),
|
||||||
|
array_elems: Some(elements),
|
||||||
|
closure_env: None,
|
||||||
|
coroutine: None,
|
||||||
|
};
|
||||||
let idx = self.objects.len();
|
let idx = self.objects.len();
|
||||||
// No free-list reuse in this PR: append and keep indices stable.
|
// No free-list reuse in this PR: append and keep indices stable.
|
||||||
self.objects.push(Some(obj));
|
self.objects.push(Some(obj));
|
||||||
@ -125,7 +139,9 @@ impl Heap {
|
|||||||
/// Returns true if this handle refers to an allocated object.
|
/// Returns true if this handle refers to an allocated object.
|
||||||
pub fn is_valid(&self, r: HeapRef) -> bool {
|
pub fn is_valid(&self, r: HeapRef) -> bool {
|
||||||
let idx = r.0 as usize;
|
let idx = r.0 as usize;
|
||||||
if idx >= self.objects.len() { return false; }
|
if idx >= self.objects.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
self.objects[idx].is_some()
|
self.objects[idx].is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,18 +163,12 @@ impl Heap {
|
|||||||
|
|
||||||
/// Get immutable access to an object's header by handle.
|
/// Get immutable access to an object's header by handle.
|
||||||
pub fn header(&self, r: HeapRef) -> Option<&ObjectHeader> {
|
pub fn header(&self, r: HeapRef) -> Option<&ObjectHeader> {
|
||||||
self.objects
|
self.objects.get(r.0 as usize).and_then(|slot| slot.as_ref()).map(|o| &o.header)
|
||||||
.get(r.0 as usize)
|
|
||||||
.and_then(|slot| slot.as_ref())
|
|
||||||
.map(|o| &o.header)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal: get mutable access to an object's header by handle.
|
/// Internal: get mutable access to an object's header by handle.
|
||||||
fn header_mut(&mut self, r: HeapRef) -> Option<&mut ObjectHeader> {
|
fn header_mut(&mut self, r: HeapRef) -> Option<&mut ObjectHeader> {
|
||||||
self.objects
|
self.objects.get_mut(r.0 as usize).and_then(|slot| slot.as_mut()).map(|o| &mut o.header)
|
||||||
.get_mut(r.0 as usize)
|
|
||||||
.and_then(|slot| slot.as_mut())
|
|
||||||
.map(|o| &mut o.header)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal: list inner `HeapRef` children of an object without allocating.
|
// Internal: list inner `HeapRef` children of an object without allocating.
|
||||||
@ -215,8 +225,12 @@ impl Heap {
|
|||||||
pub fn closure_fn_id(&self, r: HeapRef) -> Option<u32> {
|
pub fn closure_fn_id(&self, r: HeapRef) -> Option<u32> {
|
||||||
let idx = r.0 as usize;
|
let idx = r.0 as usize;
|
||||||
let slot = self.objects.get(idx)?.as_ref()?;
|
let slot = self.objects.get(idx)?.as_ref()?;
|
||||||
if slot.header.kind != ObjectKind::Closure { return None; }
|
if slot.header.kind != ObjectKind::Closure {
|
||||||
if slot.payload.len() < 8 { return None; }
|
return None;
|
||||||
|
}
|
||||||
|
if slot.payload.len() < 8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
debug_assert_eq!(slot.header.payload_len, 8);
|
debug_assert_eq!(slot.header.payload_len, 8);
|
||||||
let mut bytes = [0u8; 4];
|
let mut bytes = [0u8; 4];
|
||||||
bytes.copy_from_slice(&slot.payload[0..4]);
|
bytes.copy_from_slice(&slot.payload[0..4]);
|
||||||
@ -228,7 +242,9 @@ impl Heap {
|
|||||||
pub fn closure_env_slice(&self, r: HeapRef) -> Option<&[Value]> {
|
pub fn closure_env_slice(&self, r: HeapRef) -> Option<&[Value]> {
|
||||||
let idx = r.0 as usize;
|
let idx = r.0 as usize;
|
||||||
let slot = self.objects.get(idx)?.as_ref()?;
|
let slot = self.objects.get(idx)?.as_ref()?;
|
||||||
if slot.header.kind != ObjectKind::Closure { return None; }
|
if slot.header.kind != ObjectKind::Closure {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if slot.payload.len() >= 8 {
|
if slot.payload.len() >= 8 {
|
||||||
let mut nbytes = [0u8; 4];
|
let mut nbytes = [0u8; 4];
|
||||||
nbytes.copy_from_slice(&slot.payload[4..8]);
|
nbytes.copy_from_slice(&slot.payload[4..8]);
|
||||||
@ -246,17 +262,21 @@ impl Heap {
|
|||||||
let mut stack: Vec<HeapRef> = roots.into_iter().collect();
|
let mut stack: Vec<HeapRef> = roots.into_iter().collect();
|
||||||
|
|
||||||
while let Some(r) = stack.pop() {
|
while let Some(r) = stack.pop() {
|
||||||
if !self.is_valid(r) { continue; }
|
if !self.is_valid(r) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If already marked, skip.
|
// If already marked, skip.
|
||||||
let already_marked = self
|
let already_marked =
|
||||||
.header(r)
|
self.header(r).map(|h: &ObjectHeader| h.is_marked()).unwrap_or(false);
|
||||||
.map(|h: &ObjectHeader| h.is_marked())
|
if already_marked {
|
||||||
.unwrap_or(false);
|
continue;
|
||||||
if already_marked { continue; }
|
}
|
||||||
|
|
||||||
// Set mark bit.
|
// Set mark bit.
|
||||||
if let Some(h) = self.header_mut(r) { h.set_marked(true); }
|
if let Some(h) = self.header_mut(r) {
|
||||||
|
h.set_marked(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Push children by scanning payload directly (no intermediate Vec allocs).
|
// Push children by scanning payload directly (no intermediate Vec allocs).
|
||||||
let idx = r.0 as usize;
|
let idx = r.0 as usize;
|
||||||
@ -272,7 +292,9 @@ impl Heap {
|
|||||||
.header(*child)
|
.header(*child)
|
||||||
.map(|h: &ObjectHeader| h.is_marked())
|
.map(|h: &ObjectHeader| h.is_marked())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !marked { stack.push(*child); }
|
if !marked {
|
||||||
|
stack.push(*child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,7 +305,11 @@ impl Heap {
|
|||||||
nbytes.copy_from_slice(&obj.payload[4..8]);
|
nbytes.copy_from_slice(&obj.payload[4..8]);
|
||||||
let env_len = u32::from_le_bytes(nbytes) as usize;
|
let env_len = u32::from_le_bytes(nbytes) as usize;
|
||||||
if let Some(env) = obj.closure_env.as_ref() {
|
if let Some(env) = obj.closure_env.as_ref() {
|
||||||
debug_assert_eq!(env.len(), env_len, "closure env len must match encoded env_len");
|
debug_assert_eq!(
|
||||||
|
env.len(),
|
||||||
|
env_len,
|
||||||
|
"closure env len must match encoded env_len"
|
||||||
|
);
|
||||||
for val in env[..env_len].iter() {
|
for val in env[..env_len].iter() {
|
||||||
if let Value::HeapRef(child) = val
|
if let Value::HeapRef(child) = val
|
||||||
&& self.is_valid(*child)
|
&& self.is_valid(*child)
|
||||||
@ -292,7 +318,9 @@ impl Heap {
|
|||||||
.header(*child)
|
.header(*child)
|
||||||
.map(|h: &ObjectHeader| h.is_marked())
|
.map(|h: &ObjectHeader| h.is_marked())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !marked { stack.push(*child); }
|
if !marked {
|
||||||
|
stack.push(*child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,7 +335,9 @@ impl Heap {
|
|||||||
.header(*child)
|
.header(*child)
|
||||||
.map(|h: &ObjectHeader| h.is_marked())
|
.map(|h: &ObjectHeader| h.is_marked())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !marked { stack.push(*child); }
|
if !marked {
|
||||||
|
stack.push(*child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,7 +366,9 @@ impl Heap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Current number of allocated (live) objects.
|
/// Current number of allocated (live) objects.
|
||||||
pub fn len(&self) -> usize { self.objects.iter().filter(|s| s.is_some()).count() }
|
pub fn len(&self) -> usize {
|
||||||
|
self.objects.iter().filter(|s| s.is_some()).count()
|
||||||
|
}
|
||||||
|
|
||||||
/// Enumerate handles of coroutines that are currently suspended (i.e., not running):
|
/// Enumerate handles of coroutines that are currently suspended (i.e., not running):
|
||||||
/// Ready or Sleeping. These must be treated as GC roots by the runtime so their
|
/// Ready or Sleeping. These must be treated as GC roots by the runtime so their
|
||||||
@ -430,11 +462,8 @@ mod tests {
|
|||||||
// Target object B (unreferenced yet)
|
// Target object B (unreferenced yet)
|
||||||
let b = heap.allocate_object(ObjectKind::Bytes, &[9, 9, 9]);
|
let b = heap.allocate_object(ObjectKind::Bytes, &[9, 9, 9]);
|
||||||
// Array A that contains a reference to B among other primitives
|
// Array A that contains a reference to B among other primitives
|
||||||
let a = heap.allocate_array(vec![
|
let a =
|
||||||
Value::Int32(1),
|
heap.allocate_array(vec![Value::Int32(1), Value::HeapRef(b), Value::Boolean(false)]);
|
||||||
Value::HeapRef(b),
|
|
||||||
Value::Boolean(false),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Mark starting from root A
|
// Mark starting from root A
|
||||||
heap.mark_from_roots([a]);
|
heap.mark_from_roots([a]);
|
||||||
|
|||||||
@ -2,16 +2,16 @@ mod call_frame;
|
|||||||
mod local_addressing;
|
mod local_addressing;
|
||||||
// Keep the verifier internal in production builds, but expose it for integration tests
|
// Keep the verifier internal in production builds, but expose it for integration tests
|
||||||
// so the golden verifier suite can exercise it without widening the public API in releases.
|
// so the golden verifier suite can exercise it without widening the public API in releases.
|
||||||
|
mod heap;
|
||||||
|
mod object;
|
||||||
|
mod roots;
|
||||||
|
mod scheduler;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
mod verifier;
|
mod verifier;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod verifier;
|
mod verifier;
|
||||||
mod virtual_machine;
|
mod virtual_machine;
|
||||||
mod vm_init_error;
|
mod vm_init_error;
|
||||||
mod object;
|
|
||||||
mod heap;
|
|
||||||
mod roots;
|
|
||||||
mod scheduler;
|
|
||||||
|
|
||||||
pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
||||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||||
|
|||||||
@ -51,7 +51,8 @@
|
|||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub enum ObjectKind {
|
pub enum ObjectKind {
|
||||||
/// Reserved/unknown kind. Should not appear in valid allocations.
|
/// Reserved/unknown kind. Should not appear in valid allocations.
|
||||||
#[allow(dead_code)] // Kept for stable tag layout and persisted images, even if not constructed in this crate yet
|
#[allow(dead_code)]
|
||||||
|
// Kept for stable tag layout and persisted images, even if not constructed in this crate yet
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
|
|
||||||
/// UTF-8 string. `payload_len` is the number of bytes.
|
/// UTF-8 string. `payload_len` is the number of bytes.
|
||||||
@ -87,7 +88,6 @@ pub enum ObjectKind {
|
|||||||
/// contained `HeapRef`s directly.
|
/// contained `HeapRef`s directly.
|
||||||
/// - `payload_len` is 0 for this fixed-layout object.
|
/// - `payload_len` is 0 for this fixed-layout object.
|
||||||
Coroutine = 6,
|
Coroutine = 6,
|
||||||
|
|
||||||
// Future kinds must be appended here to keep tag numbers stable.
|
// Future kinds must be appended here to keep tag numbers stable.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,10 +120,16 @@ impl ObjectHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the GC mark bit is set.
|
/// Returns true if the GC mark bit is set.
|
||||||
pub fn is_marked(&self) -> bool { (self.flags & object_flags::MARKED) != 0 }
|
pub fn is_marked(&self) -> bool {
|
||||||
|
(self.flags & object_flags::MARKED) != 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets or clears the GC mark bit. Note: actual GC logic lives elsewhere.
|
/// Sets or clears the GC mark bit. Note: actual GC logic lives elsewhere.
|
||||||
pub fn set_marked(&mut self, value: bool) {
|
pub fn set_marked(&mut self, value: bool) {
|
||||||
if value { self.flags |= object_flags::MARKED; } else { self.flags &= !object_flags::MARKED; }
|
if value {
|
||||||
|
self.flags |= object_flags::MARKED;
|
||||||
|
} else {
|
||||||
|
self.flags &= !object_flags::MARKED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,13 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::VirtualMachine;
|
use crate::VirtualMachine;
|
||||||
|
|
||||||
struct CollectVisitor { pub seen: Vec<HeapRef> }
|
struct CollectVisitor {
|
||||||
|
pub seen: Vec<HeapRef>,
|
||||||
|
}
|
||||||
impl RootVisitor for CollectVisitor {
|
impl RootVisitor for CollectVisitor {
|
||||||
fn visit_heap_ref(&mut self, r: HeapRef) { self.seen.push(r); }
|
fn visit_heap_ref(&mut self, r: HeapRef) {
|
||||||
|
self.seen.push(r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -28,7 +28,9 @@ struct SleepEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Scheduler {
|
impl Scheduler {
|
||||||
pub fn new() -> Self { Self::default() }
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Ready queue operations ----------
|
// ---------- Ready queue operations ----------
|
||||||
|
|
||||||
@ -43,15 +45,25 @@ impl Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn is_ready_empty(&self) -> bool { self.ready_queue.is_empty() }
|
pub fn is_ready_empty(&self) -> bool {
|
||||||
|
self.ready_queue.is_empty()
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn ready_len(&self) -> usize { self.ready_queue.len() }
|
pub fn ready_len(&self) -> usize {
|
||||||
|
self.ready_queue.len()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Current tracking (no switching here) ----------
|
// ---------- Current tracking (no switching here) ----------
|
||||||
pub fn set_current(&mut self, coro: Option<HeapRef>) { self.current = coro; }
|
pub fn set_current(&mut self, coro: Option<HeapRef>) {
|
||||||
|
self.current = coro;
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn current(&self) -> Option<HeapRef> { self.current }
|
pub fn current(&self) -> Option<HeapRef> {
|
||||||
pub fn clear_current(&mut self) { self.current = None; }
|
self.current
|
||||||
|
}
|
||||||
|
pub fn clear_current(&mut self) {
|
||||||
|
self.current = None;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Sleeping operations ----------
|
// ---------- Sleeping operations ----------
|
||||||
|
|
||||||
@ -63,9 +75,7 @@ impl Scheduler {
|
|||||||
self.next_seq = self.next_seq.wrapping_add(1);
|
self.next_seq = self.next_seq.wrapping_add(1);
|
||||||
|
|
||||||
// Binary search insertion point by wake_tick, then by seq to keep total order deterministic
|
// Binary search insertion point by wake_tick, then by seq to keep total order deterministic
|
||||||
let idx = match self
|
let idx = match self.sleeping.binary_search_by(|e| {
|
||||||
.sleeping
|
|
||||||
.binary_search_by(|e| {
|
|
||||||
if e.wake_tick == entry.wake_tick {
|
if e.wake_tick == entry.wake_tick {
|
||||||
e.seq.cmp(&entry.seq)
|
e.seq.cmp(&entry.seq)
|
||||||
} else {
|
} else {
|
||||||
@ -81,10 +91,10 @@ impl Scheduler {
|
|||||||
/// Move all sleeping coroutines with `wake_tick <= current_tick` to ready queue (FIFO by wake order).
|
/// Move all sleeping coroutines with `wake_tick <= current_tick` to ready queue (FIFO by wake order).
|
||||||
pub fn wake_ready(&mut self, current_tick: u64) {
|
pub fn wake_ready(&mut self, current_tick: u64) {
|
||||||
// Find split point where wake_tick > current_tick
|
// Find split point where wake_tick > current_tick
|
||||||
let split = self
|
let split = self.sleeping.partition_point(|e| e.wake_tick <= current_tick);
|
||||||
.sleeping
|
if split == 0 {
|
||||||
.partition_point(|e| e.wake_tick <= current_tick);
|
return;
|
||||||
if split == 0 { return; }
|
}
|
||||||
let mut ready_slice: Vec<SleepEntry> = self.sleeping.drain(0..split).collect();
|
let mut ready_slice: Vec<SleepEntry> = self.sleeping.drain(0..split).collect();
|
||||||
// Already in order by (wake_tick, seq); push in that order to preserve determinism
|
// Already in order by (wake_tick, seq); push in that order to preserve determinism
|
||||||
for e in ready_slice.drain(..) {
|
for e in ready_slice.drain(..) {
|
||||||
@ -93,14 +103,18 @@ impl Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if there are any sleeping coroutines.
|
/// Returns true if there are any sleeping coroutines.
|
||||||
pub fn has_sleeping(&self) -> bool { !self.sleeping.is_empty() }
|
pub fn has_sleeping(&self) -> bool {
|
||||||
|
!self.sleeping.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn hr(id: u32) -> HeapRef { HeapRef(id) }
|
fn hr(id: u32) -> HeapRef {
|
||||||
|
HeapRef(id)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fifo_ready_queue_is_deterministic() {
|
fn fifo_ready_queue_is_deterministic() {
|
||||||
|
|||||||
@ -8,10 +8,9 @@ fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
|||||||
match (need, imm) {
|
match (need, imm) {
|
||||||
(0, None) => {}
|
(0, None) => {}
|
||||||
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
||||||
(n, Some(bytes)) => panic!(
|
(n, Some(bytes)) => {
|
||||||
"immediate size mismatch for {:?}: expected {}, got {}",
|
panic!("immediate size mismatch for {:?}: expected {}, got {}", op, n, bytes.len())
|
||||||
op, n, bytes.len()
|
}
|
||||||
),
|
|
||||||
(n, None) => panic!("missing immediate for {:?}: need {} bytes", op, n),
|
(n, None) => panic!("missing immediate for {:?}: need {} bytes", op, n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
use prometeu_hal::vm_fault::VmFault;
|
||||||
use prometeu_vm::VirtualMachine;
|
use prometeu_vm::VirtualMachine;
|
||||||
use prometeu_vm::{HostContext, HostReturn, NativeInterface, SyscallId};
|
use prometeu_vm::{HostContext, HostReturn, NativeInterface, SyscallId};
|
||||||
use prometeu_hal::vm_fault::VmFault;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gc_collects_unreachable_closure_but_keeps_marked() {
|
fn gc_collects_unreachable_closure_but_keeps_marked() {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt};
|
use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt};
|
||||||
use prometeu_vm::{VirtualMachine, BudgetReport, LogicalFrameEndingReason};
|
|
||||||
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
||||||
|
use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scheduler_wake_and_ready_order_is_deterministic() {
|
fn scheduler_wake_and_ready_order_is_deterministic() {
|
||||||
@ -13,7 +13,9 @@ fn scheduler_wake_and_ready_order_is_deterministic() {
|
|||||||
match (need, imm) {
|
match (need, imm) {
|
||||||
(0, None) => {}
|
(0, None) => {}
|
||||||
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
||||||
(n, Some(bytes)) => panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()),
|
(n, Some(bytes)) => {
|
||||||
|
panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len())
|
||||||
|
}
|
||||||
(n, None) => panic!("missing imm for {:?}: need {} bytes", op, n),
|
(n, None) => panic!("missing imm for {:?}: need {} bytes", op, n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt};
|
use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt};
|
||||||
|
use prometeu_bytecode::Value;
|
||||||
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
||||||
use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||||
use prometeu_bytecode::Value;
|
|
||||||
|
|
||||||
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
||||||
out.extend_from_slice(&(op as u16).to_le_bytes());
|
out.extend_from_slice(&(op as u16).to_le_bytes());
|
||||||
@ -9,7 +9,9 @@ fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec<u8>) {
|
|||||||
match (need, imm) {
|
match (need, imm) {
|
||||||
(0, None) => {}
|
(0, None) => {}
|
||||||
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
(n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes),
|
||||||
(n, Some(bytes)) => panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()),
|
(n, Some(bytes)) => {
|
||||||
|
panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len())
|
||||||
|
}
|
||||||
(n, None) => panic!("missing imm for {:?}: need {} bytes", op, n),
|
(n, None) => panic!("missing imm for {:?}: need {} bytes", op, n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,9 @@ const FORBIDDEN_IDENT_TOKENS: &[&str] = &[
|
|||||||
// HIP must be fully removed from code
|
// HIP must be fully removed from code
|
||||||
"hip",
|
"hip",
|
||||||
"HIP",
|
"HIP",
|
||||||
|
|
||||||
// Legacy RC API surface (exact token only)
|
// Legacy RC API surface (exact token only)
|
||||||
"release",
|
"release",
|
||||||
"Release",
|
"Release",
|
||||||
|
|
||||||
// Legacy scope helpers (exact tokens only)
|
// Legacy scope helpers (exact tokens only)
|
||||||
"enter_scope",
|
"enter_scope",
|
||||||
"exit_scope",
|
"exit_scope",
|
||||||
@ -22,7 +20,6 @@ const FORBIDDEN_IDENT_TOKENS: &[&str] = &[
|
|||||||
"borrow_scope",
|
"borrow_scope",
|
||||||
"mutate_scope",
|
"mutate_scope",
|
||||||
"peek_scope",
|
"peek_scope",
|
||||||
|
|
||||||
// Legacy handle/gate/retain-release naming that is almost certainly RC/HIP-related
|
// Legacy handle/gate/retain-release naming that is almost certainly RC/HIP-related
|
||||||
"GateHandle",
|
"GateHandle",
|
||||||
"gate_handle",
|
"gate_handle",
|
||||||
@ -48,7 +45,8 @@ const FORBIDDEN_PATH_SEGMENTS: &[&str] = &[
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_legacy_artifacts() {
|
fn test_no_legacy_artifacts() {
|
||||||
let workspace_root = find_workspace_root().expect("Failed to locate workspace root with [workspace] Cargo.toml");
|
let workspace_root =
|
||||||
|
find_workspace_root().expect("Failed to locate workspace root with [workspace] Cargo.toml");
|
||||||
|
|
||||||
// Collect Rust files under crates/**/src/**/*.rs and any build.rs under crates/**
|
// Collect Rust files under crates/**/src/**/*.rs and any build.rs under crates/**
|
||||||
let files = collect_rust_files(&workspace_root);
|
let files = collect_rust_files(&workspace_root);
|
||||||
@ -70,14 +68,21 @@ fn test_no_legacy_artifacts() {
|
|||||||
|
|
||||||
for (tok, line, col) in toks {
|
for (tok, line, col) in toks {
|
||||||
if is_forbidden_ident(&tok) {
|
if is_forbidden_ident(&tok) {
|
||||||
violations.insert(format!("{}:{}:{} identifier '{}': legacy token",
|
violations.insert(format!(
|
||||||
rel(&workspace_root, path), line, col, tok));
|
"{}:{}:{} identifier '{}': legacy token",
|
||||||
|
rel(&workspace_root, path),
|
||||||
|
line,
|
||||||
|
col,
|
||||||
|
tok
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !violations.is_empty() {
|
if !violations.is_empty() {
|
||||||
let mut msg = String::from("Legacy artifacts detected (RC/HIP/scope helpers). Please remove or rename.\n");
|
let mut msg = String::from(
|
||||||
|
"Legacy artifacts detected (RC/HIP/scope helpers). Please remove or rename.\n",
|
||||||
|
);
|
||||||
for v in &violations {
|
for v in &violations {
|
||||||
msg.push_str(" - ");
|
msg.push_str(" - ");
|
||||||
msg.push_str(v);
|
msg.push_str(v);
|
||||||
@ -110,15 +115,18 @@ fn find_workspace_root() -> Option<PathBuf> {
|
|||||||
fn collect_rust_files(root: &Path) -> Vec<PathBuf> {
|
fn collect_rust_files(root: &Path) -> Vec<PathBuf> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let crates_dir = root.join("crates");
|
let crates_dir = root.join("crates");
|
||||||
if !crates_dir.exists() { return out; }
|
if !crates_dir.exists() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
let mut stack = vec![crates_dir];
|
let mut stack = vec![crates_dir];
|
||||||
while let Some(dir) = stack.pop() {
|
while let Some(dir) = stack.pop() {
|
||||||
// Exclude noisy/non-code directories early
|
// Exclude noisy/non-code directories early
|
||||||
let name_lc = dir.file_name().and_then(|s| s.to_str()).unwrap_or("").to_ascii_lowercase();
|
let name_lc = dir.file_name().and_then(|s| s.to_str()).unwrap_or("").to_ascii_lowercase();
|
||||||
if matches!(name_lc.as_str(),
|
if matches!(
|
||||||
"target" | "docs" | "files" | "sdcard" | "test-cartridges" | "temp")
|
name_lc.as_str(),
|
||||||
|| name_lc.starts_with("dist")
|
"target" | "docs" | "files" | "sdcard" | "test-cartridges" | "temp"
|
||||||
|
) || name_lc.starts_with("dist")
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -143,10 +151,8 @@ fn collect_rust_files(root: &Path) -> Vec<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn path_segment_violation(path: &Path) -> Option<String> {
|
fn path_segment_violation(path: &Path) -> Option<String> {
|
||||||
let mut segs: Vec<String> = path
|
let mut segs: Vec<String> =
|
||||||
.components()
|
path.components().filter_map(|c| c.as_os_str().to_str().map(|s| s.to_string())).collect();
|
||||||
.filter_map(|c| c.as_os_str().to_str().map(|s| s.to_string()))
|
|
||||||
.collect();
|
|
||||||
if let Some(fname) = path.file_stem().and_then(|s| s.to_str()) {
|
if let Some(fname) = path.file_stem().and_then(|s| s.to_str()) {
|
||||||
segs.push(fname.to_string());
|
segs.push(fname.to_string());
|
||||||
}
|
}
|
||||||
@ -154,10 +160,7 @@ fn path_segment_violation(path: &Path) -> Option<String> {
|
|||||||
let seg_lc = seg.to_ascii_lowercase();
|
let seg_lc = seg.to_ascii_lowercase();
|
||||||
for &bad in FORBIDDEN_PATH_SEGMENTS {
|
for &bad in FORBIDDEN_PATH_SEGMENTS {
|
||||||
if seg_lc == bad.to_ascii_lowercase() {
|
if seg_lc == bad.to_ascii_lowercase() {
|
||||||
return Some(format!(
|
return Some(format!("{} path-segment '{}'", path.display(), seg));
|
||||||
"{} path-segment '{}'",
|
|
||||||
path.display(), seg
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,15 +191,22 @@ fn tokenize_identifiers(text: &str) -> Vec<(String, usize, usize)> {
|
|||||||
let start_line = line;
|
let start_line = line;
|
||||||
let start_col = col;
|
let start_col = col;
|
||||||
let start = i;
|
let start = i;
|
||||||
i += 1; col += 1;
|
i += 1;
|
||||||
|
col += 1;
|
||||||
while i < bytes.len() {
|
while i < bytes.len() {
|
||||||
let c = bytes[i] as char;
|
let c = bytes[i] as char;
|
||||||
if is_ident_part(c) { i += 1; col += 1; } else { break; }
|
if is_ident_part(c) {
|
||||||
|
i += 1;
|
||||||
|
col += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let tok = &text[start..i];
|
let tok = &text[start..i];
|
||||||
out.push((tok.to_string(), start_line, start_col));
|
out.push((tok.to_string(), start_line, start_col));
|
||||||
} else {
|
} else {
|
||||||
i += 1; col += 1;
|
i += 1;
|
||||||
|
col += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
@ -221,14 +231,20 @@ fn strip_comments_and_strings(src: &str) -> String {
|
|||||||
while i < b.len() {
|
while i < b.len() {
|
||||||
let c = b[i] as char;
|
let c = b[i] as char;
|
||||||
// Preserve newlines to maintain line numbers
|
// Preserve newlines to maintain line numbers
|
||||||
if c == '\n' { out.push('\n'); i += 1; continue; }
|
if c == '\n' {
|
||||||
|
out.push('\n');
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to match line comment
|
// Try to match line comment
|
||||||
if c == '/' && i + 1 < b.len() && b[i + 1] as char == '/' {
|
if c == '/' && i + 1 < b.len() && b[i + 1] as char == '/' {
|
||||||
i += 2;
|
i += 2;
|
||||||
while i < b.len() {
|
while i < b.len() {
|
||||||
let ch = b[i] as char;
|
let ch = b[i] as char;
|
||||||
if ch == '\n' { break; }
|
if ch == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
continue; // newline is handled at top on next loop
|
continue; // newline is handled at top on next loop
|
||||||
@ -239,8 +255,13 @@ fn strip_comments_and_strings(src: &str) -> String {
|
|||||||
i += 2;
|
i += 2;
|
||||||
while i + 1 < b.len() {
|
while i + 1 < b.len() {
|
||||||
let ch = b[i] as char;
|
let ch = b[i] as char;
|
||||||
if ch == '\n' { out.push('\n'); }
|
if ch == '\n' {
|
||||||
if ch == '*' && b[i + 1] as char == '/' { i += 2; break; }
|
out.push('\n');
|
||||||
|
}
|
||||||
|
if ch == '*' && b[i + 1] as char == '/' {
|
||||||
|
i += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -251,7 +272,10 @@ fn strip_comments_and_strings(src: &str) -> String {
|
|||||||
let mut j = i + 1;
|
let mut j = i + 1;
|
||||||
let mut hashes = 0usize;
|
let mut hashes = 0usize;
|
||||||
if j < b.len() && b[j] as char == '#' {
|
if j < b.len() && b[j] as char == '#' {
|
||||||
while j < b.len() && b[j] as char == '#' && hashes < 10 { hashes += 1; j += 1; }
|
while j < b.len() && b[j] as char == '#' && hashes < 10 {
|
||||||
|
hashes += 1;
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if j < b.len() && b[j] as char == '"' {
|
if j < b.len() && b[j] as char == '"' {
|
||||||
// Found start of raw string
|
// Found start of raw string
|
||||||
@ -259,13 +283,18 @@ fn strip_comments_and_strings(src: &str) -> String {
|
|||||||
let mut end_found = false;
|
let mut end_found = false;
|
||||||
while j < b.len() {
|
while j < b.len() {
|
||||||
let ch = b[j] as char;
|
let ch = b[j] as char;
|
||||||
if ch == '\n' { out.push('\n'); j += 1; continue; }
|
if ch == '\n' {
|
||||||
|
out.push('\n');
|
||||||
|
j += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ch == '"' {
|
if ch == '"' {
|
||||||
// check for closing hashes
|
// check for closing hashes
|
||||||
let mut k = j + 1;
|
let mut k = j + 1;
|
||||||
let mut matched = 0usize;
|
let mut matched = 0usize;
|
||||||
while matched < hashes && k < b.len() && b[k] as char == '#' {
|
while matched < hashes && k < b.len() && b[k] as char == '#' {
|
||||||
matched += 1; k += 1;
|
matched += 1;
|
||||||
|
k += 1;
|
||||||
}
|
}
|
||||||
if matched == hashes {
|
if matched == hashes {
|
||||||
i = k; // consume entire raw string
|
i = k; // consume entire raw string
|
||||||
@ -278,7 +307,9 @@ fn strip_comments_and_strings(src: &str) -> String {
|
|||||||
}
|
}
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
if !end_found { i = j; } // EOF inside string
|
if !end_found {
|
||||||
|
i = j;
|
||||||
|
} // EOF inside string
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,12 +319,17 @@ fn strip_comments_and_strings(src: &str) -> String {
|
|||||||
i += 1; // skip starting quote
|
i += 1; // skip starting quote
|
||||||
while i < b.len() {
|
while i < b.len() {
|
||||||
let ch = b[i] as char;
|
let ch = b[i] as char;
|
||||||
if ch == '\n' { out.push('\n'); }
|
if ch == '\n' {
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
i += 2; // skip escaped char
|
i += 2; // skip escaped char
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ch == '"' { i += 1; break; }
|
if ch == '"' {
|
||||||
|
i += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -320,22 +356,38 @@ mod pathdiff {
|
|||||||
// pop common prefix
|
// pop common prefix
|
||||||
let mut comps_a: Vec<Component> = Vec::new();
|
let mut comps_a: Vec<Component> = Vec::new();
|
||||||
let mut comps_b: Vec<Component> = Vec::new();
|
let mut comps_b: Vec<Component> = Vec::new();
|
||||||
for c in ita { comps_a.push(c); }
|
for c in ita {
|
||||||
for c in itb { comps_b.push(c); }
|
comps_a.push(c);
|
||||||
|
}
|
||||||
|
for c in itb {
|
||||||
|
comps_b.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
let mut i = 0usize;
|
let mut i = 0usize;
|
||||||
while i < comps_a.len() && i < comps_b.len() && comps_a[i] == comps_b[i] { i += 1; }
|
while i < comps_a.len() && i < comps_b.len() && comps_a[i] == comps_b[i] {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let mut result = PathBuf::new();
|
let mut result = PathBuf::new();
|
||||||
for _ in i..comps_a.len() { result.push(".."); }
|
for _ in i..comps_a.len() {
|
||||||
for c in &comps_b[i..] { result.push(c.as_os_str()); }
|
result.push("..");
|
||||||
|
}
|
||||||
|
for c in &comps_b[i..] {
|
||||||
|
result.push(c.as_os_str());
|
||||||
|
}
|
||||||
Some(result)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Absolutize { fn absolutize(&self) -> PathBuf; }
|
trait Absolutize {
|
||||||
|
fn absolutize(&self) -> PathBuf;
|
||||||
|
}
|
||||||
impl Absolutize for Path {
|
impl Absolutize for Path {
|
||||||
fn absolutize(&self) -> PathBuf {
|
fn absolutize(&self) -> PathBuf {
|
||||||
if self.is_absolute() { self.to_path_buf() } else { std::env::current_dir().unwrap().join(self) }
|
if self.is_absolute() {
|
||||||
|
self.to_path_buf()
|
||||||
|
} else {
|
||||||
|
std::env::current_dir().unwrap().join(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use prometeu_bytecode::assembler::assemble;
|
use prometeu_bytecode::assembler::assemble;
|
||||||
use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta};
|
use prometeu_bytecode::model::{
|
||||||
|
BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta,
|
||||||
|
};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn asm(s: &str) -> Vec<u8> { assemble(s).expect("assemble") }
|
fn asm(s: &str) -> Vec<u8> {
|
||||||
|
assemble(s).expect("assemble")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate() -> Result<()> {
|
pub fn generate() -> Result<()> {
|
||||||
let mut rom: Vec<u8> = Vec::new();
|
let mut rom: Vec<u8> = Vec::new();
|
||||||
@ -12,16 +16,14 @@ pub fn generate() -> Result<()> {
|
|||||||
heavy_load(&mut rom);
|
heavy_load(&mut rom);
|
||||||
// light_load(&mut rom);
|
// light_load(&mut rom);
|
||||||
|
|
||||||
let functions = vec![
|
let functions = vec![FunctionMeta {
|
||||||
FunctionMeta {
|
|
||||||
code_offset: 0,
|
code_offset: 0,
|
||||||
code_len: rom.len() as u32,
|
code_len: rom.len() as u32,
|
||||||
param_slots: 0,
|
param_slots: 0,
|
||||||
local_slots: 2,
|
local_slots: 2,
|
||||||
return_slots: 0,
|
return_slots: 0,
|
||||||
max_stack_slots: 16,
|
max_stack_slots: 16,
|
||||||
},
|
}];
|
||||||
];
|
|
||||||
|
|
||||||
let module = BytecodeModule {
|
let module = BytecodeModule {
|
||||||
version: 0,
|
version: 0,
|
||||||
@ -119,7 +121,9 @@ fn heavy_load(mut rom: &mut Vec<u8>) {
|
|||||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||||
|
|
||||||
// x = (t * 3 + i * 40) % 320
|
// 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"));
|
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
|
// y = (i * 30 + t) % 180
|
||||||
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 30\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 180\nMOD"));
|
rom.extend(asm("GET_LOCAL 1\nPUSH_I32 30\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 180\nMOD"));
|
||||||
// string (toggle between "stress" and "frame")
|
// string (toggle between "stress" and "frame")
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
fn main() -> Result<()> { pbxgen_stress::generate() }
|
fn main() -> Result<()> {
|
||||||
|
pbxgen_stress::generate()
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,324 @@
|
|||||||
|
# PR-2 - PBX SYSC and HOSTCALL Loader Patching
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Teach the runtime to load canonical host bindings from PBX, resolve them at load time, validate ABI and capabilities, and rewrite pre-load host calls into final numeric syscalls.
|
||||||
|
|
||||||
|
This PR assumes PR-1 is already available, so cartridge capabilities are exposed to the loader as internal `CapFlags`.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Prometeu's hardware contract is:
|
||||||
|
|
||||||
|
- source- and SDK-level host APIs map to canonical identities `(module, name, version)`
|
||||||
|
- load-time resolution maps those identities to numeric syscall ids
|
||||||
|
- runtime execution is numeric-only via `SYSCALL <id>`
|
||||||
|
|
||||||
|
The runtime already has the important building blocks:
|
||||||
|
|
||||||
|
- canonical syscall registry
|
||||||
|
- load-time `resolve_program_syscalls`
|
||||||
|
- verifier support for numeric `SYSCALL <id>`
|
||||||
|
- VM dispatch by numeric id
|
||||||
|
|
||||||
|
What is missing is the PBX and loader wiring.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
In scope:
|
||||||
|
|
||||||
|
- add a mandatory PBX `SYSC` section
|
||||||
|
- extend each `SYSC` entry with ABI shape
|
||||||
|
- add pre-load opcode `HOSTCALL <sysc_index>`
|
||||||
|
- parse `SYSC` at load time
|
||||||
|
- resolve `SYSC` against the host syscall registry
|
||||||
|
- validate declared ABI against authoritative host metadata
|
||||||
|
- validate capabilities using cartridge-granted `CapFlags`
|
||||||
|
- reject unused `SYSC` entries
|
||||||
|
- reject out-of-bounds `HOSTCALL` indices
|
||||||
|
- patch all `HOSTCALL <sysc_index>` to `SYSCALL <id>`
|
||||||
|
- ensure verifier runs only on the patched image
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
|
||||||
|
- compiler emission
|
||||||
|
- stdlib SDK pack format
|
||||||
|
- final external tooling for PBX generation
|
||||||
|
- platform policy beyond current manifest-derived granted capabilities
|
||||||
|
|
||||||
|
## Architectural Contract
|
||||||
|
|
||||||
|
### PBX `SYSC`
|
||||||
|
|
||||||
|
`SYSC` is a unique, deduplicated, program-wide table of declared host bindings.
|
||||||
|
|
||||||
|
Each entry carries:
|
||||||
|
|
||||||
|
- `module`
|
||||||
|
- `name`
|
||||||
|
- `version`
|
||||||
|
- `arg_slots`
|
||||||
|
- `ret_slots`
|
||||||
|
|
||||||
|
`SYSC` is mandatory for every valid PBX.
|
||||||
|
If the program requires no host bindings, `SYSC` is present with `count = 0`.
|
||||||
|
|
||||||
|
### Pre-load callsites
|
||||||
|
|
||||||
|
The compiler-side artifact form is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HOSTCALL <u32 sysc_index>
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `sysc_index` is zero-based into the `SYSC` table
|
||||||
|
- code must not contain final `SYSCALL <id>` for unresolved host-backed SDK calls
|
||||||
|
- the final executable image given to the VM must contain no `HOSTCALL`
|
||||||
|
|
||||||
|
### Load-time responsibilities
|
||||||
|
|
||||||
|
The loader must:
|
||||||
|
|
||||||
|
1. parse `SYSC`
|
||||||
|
2. resolve each entry to `syscall_id`
|
||||||
|
3. validate `arg_slots` and `ret_slots`
|
||||||
|
4. validate capabilities
|
||||||
|
5. scan code for `HOSTCALL`
|
||||||
|
6. mark used `SYSC` indices
|
||||||
|
7. reject out-of-bounds indices
|
||||||
|
8. reject unused `SYSC` entries
|
||||||
|
9. rewrite `HOSTCALL <index>` into `SYSCALL <id>`
|
||||||
|
10. ensure no `HOSTCALL` remains
|
||||||
|
11. only then hand the image to the verifier
|
||||||
|
|
||||||
|
## PBX Format Changes
|
||||||
|
|
||||||
|
### `prometeu-bytecode`
|
||||||
|
|
||||||
|
Add a new section kind:
|
||||||
|
|
||||||
|
- `SYSC`
|
||||||
|
|
||||||
|
Recommended temporary binary layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
u32 count
|
||||||
|
repeat count times:
|
||||||
|
u16 module_len
|
||||||
|
[module_len bytes UTF-8]
|
||||||
|
u16 name_len
|
||||||
|
[name_len bytes UTF-8]
|
||||||
|
u16 version
|
||||||
|
u16 arg_slots
|
||||||
|
u16 ret_slots
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- strings are UTF-8
|
||||||
|
- duplicate canonical identities are invalid
|
||||||
|
- malformed lengths are invalid
|
||||||
|
- missing `SYSC` is invalid
|
||||||
|
|
||||||
|
### Bytecode model changes
|
||||||
|
|
||||||
|
Extend `BytecodeModule` with a syscall declaration vector.
|
||||||
|
|
||||||
|
Suggested shape:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct SyscallDecl {
|
||||||
|
pub module: String,
|
||||||
|
pub name: String,
|
||||||
|
pub version: u16,
|
||||||
|
pub arg_slots: u16,
|
||||||
|
pub ret_slots: u16,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub syscalls: Vec<SyscallDecl>
|
||||||
|
```
|
||||||
|
|
||||||
|
Serialization/deserialization must include section kind for `SYSC`.
|
||||||
|
|
||||||
|
## Opcode Changes
|
||||||
|
|
||||||
|
### Add `HOSTCALL`
|
||||||
|
|
||||||
|
Add a new opcode:
|
||||||
|
|
||||||
|
- `HOSTCALL`
|
||||||
|
|
||||||
|
Immediate:
|
||||||
|
|
||||||
|
- `u32 sysc_index`
|
||||||
|
|
||||||
|
Recommended behavior:
|
||||||
|
|
||||||
|
- valid only in pre-load artifact form
|
||||||
|
- not allowed to reach final verifier/VM execution path
|
||||||
|
|
||||||
|
Required code updates:
|
||||||
|
|
||||||
|
- opcode enum
|
||||||
|
- decoder
|
||||||
|
- opcode spec
|
||||||
|
- assembler
|
||||||
|
- disassembler
|
||||||
|
|
||||||
|
Verifier recommendation:
|
||||||
|
|
||||||
|
- the verifier does not need to support `HOSTCALL`
|
||||||
|
- it should continue to run after loader patching only
|
||||||
|
|
||||||
|
## Loader Integration
|
||||||
|
|
||||||
|
### Resolution
|
||||||
|
|
||||||
|
Use the existing canonical resolver in `prometeu-hal::syscalls`.
|
||||||
|
|
||||||
|
For each `SYSC` entry:
|
||||||
|
|
||||||
|
1. resolve `(module, name, version)`
|
||||||
|
2. obtain `SyscallResolved`
|
||||||
|
3. compare `arg_slots`
|
||||||
|
4. compare `ret_slots`
|
||||||
|
5. compare required capability against granted cartridge flags
|
||||||
|
|
||||||
|
If any check fails, load fails deterministically.
|
||||||
|
|
||||||
|
### Capability source
|
||||||
|
|
||||||
|
Capabilities come from the already-loaded cartridge manifest via PR-1.
|
||||||
|
|
||||||
|
This PR must not invent authority from PBX contents.
|
||||||
|
|
||||||
|
### Code patching
|
||||||
|
|
||||||
|
Recommended algorithm:
|
||||||
|
|
||||||
|
1. parse code buffer
|
||||||
|
2. whenever `HOSTCALL <index>` is found:
|
||||||
|
3. validate `index < syscalls.len()`
|
||||||
|
4. mark that `SYSC[index]` is used
|
||||||
|
5. rewrite opcode/immediate in place or rebuild the code buffer
|
||||||
|
6. emit final `SYSCALL <resolved_id>`
|
||||||
|
|
||||||
|
After scan:
|
||||||
|
|
||||||
|
1. every `SYSC` entry must be marked used
|
||||||
|
2. no `HOSTCALL` may remain
|
||||||
|
|
||||||
|
Either patching strategy is acceptable:
|
||||||
|
|
||||||
|
- mutate byte buffer in place
|
||||||
|
- rebuild a fresh patched buffer
|
||||||
|
|
||||||
|
The final `ProgramImage` must contain only numeric `SYSCALL <id>`.
|
||||||
|
|
||||||
|
## Verifier Contract
|
||||||
|
|
||||||
|
No verifier redesign is required.
|
||||||
|
|
||||||
|
The intended contract is:
|
||||||
|
|
||||||
|
- loader validates interface compatibility
|
||||||
|
- verifier validates final numeric program structure
|
||||||
|
|
||||||
|
This means:
|
||||||
|
|
||||||
|
- loader checks `SYSC`-declared ABI against host metadata
|
||||||
|
- verifier checks stack effects of final `SYSCALL <id>` using existing runtime metadata
|
||||||
|
|
||||||
|
This is intentional and not considered harmful duplication.
|
||||||
|
|
||||||
|
## Proposed Code Areas
|
||||||
|
|
||||||
|
### `prometeu-bytecode`
|
||||||
|
|
||||||
|
Likely files:
|
||||||
|
|
||||||
|
- `src/model.rs`
|
||||||
|
- `src/opcode.rs`
|
||||||
|
- `src/opcode_spec.rs`
|
||||||
|
- `src/decoder.rs`
|
||||||
|
- `src/assembler.rs`
|
||||||
|
- `src/disassembler.rs`
|
||||||
|
- `src/lib.rs`
|
||||||
|
|
||||||
|
### `prometeu-hal`
|
||||||
|
|
||||||
|
Likely files:
|
||||||
|
|
||||||
|
- `src/cartridge_loader.rs`
|
||||||
|
- possibly helper types or resolver glue near syscall loading logic
|
||||||
|
|
||||||
|
### `prometeu-vm`
|
||||||
|
|
||||||
|
Likely files:
|
||||||
|
|
||||||
|
- load path in `src/virtual_machine.rs`
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- patch before `Verifier::verify(...)`
|
||||||
|
|
||||||
|
## Deterministic Load Errors
|
||||||
|
|
||||||
|
Load must fail for at least:
|
||||||
|
|
||||||
|
1. missing `SYSC`
|
||||||
|
2. malformed `SYSC`
|
||||||
|
3. invalid UTF-8
|
||||||
|
4. duplicate syscall identities
|
||||||
|
5. unknown syscall identity
|
||||||
|
6. ABI mismatch between `SYSC` and host metadata
|
||||||
|
7. missing capability
|
||||||
|
8. `HOSTCALL` with out-of-bounds `sysc_index`
|
||||||
|
9. declared `SYSC` entry unused by all `HOSTCALL`s
|
||||||
|
10. `HOSTCALL` still present after patch
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- PBX parser supports mandatory `SYSC`
|
||||||
|
- `BytecodeModule` carries syscall declarations
|
||||||
|
- runtime understands `HOSTCALL`
|
||||||
|
- loader resolves `SYSC` entries before verification
|
||||||
|
- loader validates `arg_slots` and `ret_slots`
|
||||||
|
- loader validates capabilities against cartridge flags
|
||||||
|
- loader rewrites `HOSTCALL` to `SYSCALL`
|
||||||
|
- verifier runs only on patched code
|
||||||
|
- final VM execution path remains numeric-only
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Add tests covering:
|
||||||
|
|
||||||
|
1. valid PBX with empty `SYSC` and no `HOSTCALL`
|
||||||
|
2. valid PBX with one syscall and one `HOSTCALL`
|
||||||
|
3. unknown syscall identity
|
||||||
|
4. capability mismatch
|
||||||
|
5. ABI mismatch
|
||||||
|
6. missing `SYSC`
|
||||||
|
7. duplicate `SYSC` entries
|
||||||
|
8. malformed `SYSC` payload
|
||||||
|
9. `HOSTCALL` index out of bounds
|
||||||
|
10. unused `SYSC` entry
|
||||||
|
11. patched image contains only `SYSCALL`
|
||||||
|
|
||||||
|
Prefer synthetic in-memory PBX images in tests.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
After this PR:
|
||||||
|
|
||||||
|
- PBX declares canonical host bindings in `SYSC`
|
||||||
|
- pre-load code references those bindings with `HOSTCALL`
|
||||||
|
- the loader resolves and validates them during load
|
||||||
|
- the loader patches executable code to `SYSCALL <id>`
|
||||||
|
- the verifier and VM continue to operate on numeric syscalls only
|
||||||
Loading…
x
Reference in New Issue
Block a user