This commit is contained in:
bQUARKz 2026-03-02 14:35:44 +00:00
parent 96062bf1d0
commit df9b248c98
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8

View File

@ -237,6 +237,44 @@ pub enum LoadError {
}, },
} }
/// Load-time error for PBX-declared syscall resolution.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeclaredLoadError {
/// The `(module, name, version)` triple is not known by the host.
UnknownSyscall {
module: String,
name: String,
version: u16,
},
/// The cartridge lacks required capabilities for the syscall.
MissingCapability {
required: CapFlags,
provided: CapFlags,
module: String,
name: String,
version: u16,
},
/// The PBX-declared ABI does not match the authoritative host metadata.
AbiMismatch {
module: String,
name: String,
version: u16,
declared_arg_slots: u16,
declared_ret_slots: u16,
expected_arg_slots: u16,
expected_ret_slots: u16,
},
}
fn resolve_syscall_impl(module: &str, name: &str, version: u16) -> Option<SyscallResolved> {
for entry in SYSCALL_TABLE {
if entry.meta.module == module && entry.meta.name == name && entry.meta.version == version {
return Some(SyscallResolved { id: entry.meta.id, meta: entry.meta });
}
}
None
}
/// 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.
@ -245,12 +283,7 @@ pub fn resolve_syscall(
name: &'static str, name: &'static str,
version: u16, version: u16,
) -> Option<SyscallResolved> { ) -> Option<SyscallResolved> {
for entry in SYSCALL_TABLE { resolve_syscall_impl(module, name, version)
if entry.meta.module == module && entry.meta.name == name && entry.meta.version == version {
return Some(SyscallResolved { id: entry.meta.id, meta: entry.meta });
}
}
None
} }
/// Resolve all declared program syscalls and enforce capability gating. /// Resolve all declared program syscalls and enforce capability gating.
@ -288,6 +321,58 @@ pub fn resolve_program_syscalls(
Ok(out) Ok(out)
} }
/// Resolve and validate syscall declarations carried by a PBX module.
///
/// This is the loader-facing path for `BytecodeModule.syscalls`:
/// - canonical identity must exist in the host table
/// - capability requirements must be satisfied by granted cart flags
/// - declared ABI must match authoritative host metadata
pub fn resolve_declared_program_syscalls(
declared: &[prometeu_bytecode::SyscallDecl],
caps: CapFlags,
) -> Result<Vec<SyscallResolved>, DeclaredLoadError> {
let mut out = Vec::with_capacity(declared.len());
for decl in declared {
let Some(res) = resolve_syscall_impl(&decl.module, &decl.name, decl.version) else {
return Err(DeclaredLoadError::UnknownSyscall {
module: decl.module.clone(),
name: decl.name.clone(),
version: decl.version,
});
};
let missing = res.meta.caps & !caps;
if missing != 0 {
return Err(DeclaredLoadError::MissingCapability {
required: res.meta.caps,
provided: caps,
module: decl.module.clone(),
name: decl.name.clone(),
version: decl.version,
});
}
let expected_arg_slots = u16::from(res.meta.arg_slots);
let expected_ret_slots = res.meta.ret_slots;
if decl.arg_slots != expected_arg_slots || decl.ret_slots != expected_ret_slots {
return Err(DeclaredLoadError::AbiMismatch {
module: decl.module.clone(),
name: decl.name.clone(),
version: decl.version,
declared_arg_slots: decl.arg_slots,
declared_ret_slots: decl.ret_slots,
expected_arg_slots,
expected_ret_slots,
});
}
out.push(res);
}
Ok(out)
}
/// Canonical registry of all syscalls and their metadata. /// Canonical registry of all syscalls and their metadata.
/// ///
/// IMPORTANT: This table is the single authoritative source for the slot-based /// IMPORTANT: This table is the single authoritative source for the slot-based
@ -1394,4 +1479,89 @@ mod tests {
assert_eq!(ok.len(), 1); assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001); assert_eq!(ok[0].id, 0x1001);
} }
#[test]
fn declared_resolver_returns_expected_id_for_known_identity() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let ok =
resolve_declared_program_syscalls(&declared, caps::GFX).expect("must resolve with ABI");
assert_eq!(ok.len(), 1);
assert_eq!(ok[0].id, 0x1001);
}
#[test]
fn declared_resolver_rejects_unknown_identity() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
name: "nonexistent".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::GFX).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::UnknownSyscall {
module: "gfx".into(),
name: "nonexistent".into(),
version: 1,
}
);
}
#[test]
fn declared_resolver_rejects_missing_capability() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
name: "clear".into(),
version: 1,
arg_slots: 1,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::NONE).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::MissingCapability {
required: caps::GFX,
provided: caps::NONE,
module: "gfx".into(),
name: "clear".into(),
version: 1,
}
);
}
#[test]
fn declared_resolver_rejects_abi_mismatch() {
let declared = [prometeu_bytecode::SyscallDecl {
module: "gfx".into(),
name: "draw_line".into(),
version: 1,
arg_slots: 4,
ret_slots: 0,
}];
let err = resolve_declared_program_syscalls(&declared, caps::GFX).unwrap_err();
assert_eq!(
err,
DeclaredLoadError::AbiMismatch {
module: "gfx".into(),
name: "draw_line".into(),
version: 1,
declared_arg_slots: 4,
declared_ret_slots: 0,
expected_arg_slots: 5,
expected_ret_slots: 0,
}
);
}
} }