pr 2.3
This commit is contained in:
parent
96062bf1d0
commit
df9b248c98
@ -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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user