This commit is contained in:
Nilton Constantino 2026-01-29 17:56:12 +00:00
parent d216918c79
commit f56353ce9b
No known key found for this signature in database
7 changed files with 239 additions and 71 deletions

View File

@ -1,7 +1,14 @@
use std::collections::HashMap;
use crate::frontends::pbs::types::PbsType;
pub struct ContractMethod {
pub id: u32,
pub params: Vec<PbsType>,
pub return_type: PbsType,
}
pub struct ContractRegistry {
mappings: HashMap<String, HashMap<String, u32>>,
mappings: HashMap<String, HashMap<String, ContractMethod>>,
}
impl ContractRegistry {
@ -10,81 +17,233 @@ impl ContractRegistry {
// GFX mappings
let mut gfx = HashMap::new();
gfx.insert("clear".to_string(), 0x1001);
gfx.insert("fillRect".to_string(), 0x1002);
gfx.insert("drawLine".to_string(), 0x1003);
gfx.insert("drawCircle".to_string(), 0x1004);
gfx.insert("drawDisc".to_string(), 0x1005);
gfx.insert("drawSquare".to_string(), 0x1006);
gfx.insert("setSprite".to_string(), 0x1007);
gfx.insert("drawText".to_string(), 0x1008);
gfx.insert("clear".to_string(), ContractMethod {
id: 0x1001,
params: vec![PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("fillRect".to_string(), ContractMethod {
id: 0x1002,
params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("drawLine".to_string(), ContractMethod {
id: 0x1003,
params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("drawCircle".to_string(), ContractMethod {
id: 0x1004,
params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("drawDisc".to_string(), ContractMethod {
id: 0x1005,
params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("drawSquare".to_string(), ContractMethod {
id: 0x1006,
params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("setSprite".to_string(), ContractMethod {
id: 0x1007,
params: vec![PbsType::Int, PbsType::Int, PbsType::Int],
return_type: PbsType::Void,
});
gfx.insert("drawText".to_string(), ContractMethod {
id: 0x1008,
params: vec![PbsType::Int, PbsType::Int, PbsType::String, PbsType::Int],
return_type: PbsType::Void,
});
mappings.insert("Gfx".to_string(), gfx);
// Input mappings
let mut input = HashMap::new();
input.insert("getPad".to_string(), 0x2001);
input.insert("getPadPressed".to_string(), 0x2002);
input.insert("getPadReleased".to_string(), 0x2003);
input.insert("getPadHold".to_string(), 0x2004);
input.insert("getPad".to_string(), ContractMethod {
id: 0x2001,
params: vec![PbsType::Int],
return_type: PbsType::Int,
});
input.insert("getPadPressed".to_string(), ContractMethod {
id: 0x2002,
params: vec![PbsType::Int],
return_type: PbsType::Int,
});
input.insert("getPadReleased".to_string(), ContractMethod {
id: 0x2003,
params: vec![PbsType::Int],
return_type: PbsType::Int,
});
input.insert("getPadHold".to_string(), ContractMethod {
id: 0x2004,
params: vec![PbsType::Int],
return_type: PbsType::Int,
});
mappings.insert("Input".to_string(), input);
// Touch mappings
let mut touch = HashMap::new();
touch.insert("getX".to_string(), 0x2101);
touch.insert("getY".to_string(), 0x2102);
touch.insert("isDown".to_string(), 0x2103);
touch.insert("isPressed".to_string(), 0x2104);
touch.insert("isReleased".to_string(), 0x2105);
touch.insert("getHold".to_string(), 0x2106);
touch.insert("getX".to_string(), ContractMethod {
id: 0x2101,
params: vec![],
return_type: PbsType::Int,
});
touch.insert("getY".to_string(), ContractMethod {
id: 0x2102,
params: vec![],
return_type: PbsType::Int,
});
touch.insert("isDown".to_string(), ContractMethod {
id: 0x2103,
params: vec![],
return_type: PbsType::Bool,
});
touch.insert("isPressed".to_string(), ContractMethod {
id: 0x2104,
params: vec![],
return_type: PbsType::Bool,
});
touch.insert("isReleased".to_string(), ContractMethod {
id: 0x2105,
params: vec![],
return_type: PbsType::Bool,
});
touch.insert("getHold".to_string(), ContractMethod {
id: 0x2106,
params: vec![],
return_type: PbsType::Int,
});
mappings.insert("Touch".to_string(), touch);
// Audio mappings
let mut audio = HashMap::new();
audio.insert("playSample".to_string(), 0x3001);
audio.insert("play".to_string(), 0x3002);
audio.insert("playSample".to_string(), ContractMethod {
id: 0x3001,
params: vec![PbsType::Int],
return_type: PbsType::Void,
});
audio.insert("play".to_string(), ContractMethod {
id: 0x3002,
params: vec![PbsType::Int],
return_type: PbsType::Void,
});
mappings.insert("Audio".to_string(), audio);
// FS mappings
let mut fs = HashMap::new();
fs.insert("open".to_string(), 0x4001);
fs.insert("read".to_string(), 0x4002);
fs.insert("write".to_string(), 0x4003);
fs.insert("close".to_string(), 0x4004);
fs.insert("listDir".to_string(), 0x4005);
fs.insert("exists".to_string(), 0x4006);
fs.insert("delete".to_string(), 0x4007);
fs.insert("open".to_string(), ContractMethod {
id: 0x4001,
params: vec![PbsType::String, PbsType::String],
return_type: PbsType::Int,
});
fs.insert("read".to_string(), ContractMethod {
id: 0x4002,
params: vec![PbsType::Int],
return_type: PbsType::String,
});
fs.insert("write".to_string(), ContractMethod {
id: 0x4003,
params: vec![PbsType::Int, PbsType::String],
return_type: PbsType::Int,
});
fs.insert("close".to_string(), ContractMethod {
id: 0x4004,
params: vec![PbsType::Int],
return_type: PbsType::Void,
});
fs.insert("listDir".to_string(), ContractMethod {
id: 0x4005,
params: vec![PbsType::String],
return_type: PbsType::String,
});
fs.insert("exists".to_string(), ContractMethod {
id: 0x4006,
params: vec![PbsType::String],
return_type: PbsType::Bool,
});
fs.insert("delete".to_string(), ContractMethod {
id: 0x4007,
params: vec![PbsType::String],
return_type: PbsType::Bool,
});
mappings.insert("Fs".to_string(), fs);
// Log mappings
let mut log = HashMap::new();
log.insert("write".to_string(), 0x5001);
log.insert("writeTag".to_string(), 0x5002);
log.insert("write".to_string(), ContractMethod {
id: 0x5001,
params: vec![PbsType::Int, PbsType::String],
return_type: PbsType::Void,
});
log.insert("writeTag".to_string(), ContractMethod {
id: 0x5002,
params: vec![PbsType::Int, PbsType::Int, PbsType::String],
return_type: PbsType::Void,
});
mappings.insert("Log".to_string(), log);
// System mappings
let mut system = HashMap::new();
system.insert("hasCart".to_string(), 0x0001);
system.insert("runCart".to_string(), 0x0002);
system.insert("hasCart".to_string(), ContractMethod {
id: 0x0001,
params: vec![],
return_type: PbsType::Bool,
});
system.insert("runCart".to_string(), ContractMethod {
id: 0x0002,
params: vec![],
return_type: PbsType::Void,
});
mappings.insert("System".to_string(), system);
// Asset mappings
let mut asset = HashMap::new();
asset.insert("load".to_string(), 0x6001);
asset.insert("status".to_string(), 0x6002);
asset.insert("commit".to_string(), 0x6003);
asset.insert("cancel".to_string(), 0x6004);
asset.insert("load".to_string(), ContractMethod {
id: 0x6001,
params: vec![PbsType::String],
return_type: PbsType::Int,
});
asset.insert("status".to_string(), ContractMethod {
id: 0x6002,
params: vec![PbsType::Int],
return_type: PbsType::Int,
});
asset.insert("commit".to_string(), ContractMethod {
id: 0x6003,
params: vec![PbsType::Int],
return_type: PbsType::Void,
});
asset.insert("cancel".to_string(), ContractMethod {
id: 0x6004,
params: vec![PbsType::Int],
return_type: PbsType::Void,
});
mappings.insert("Asset".to_string(), asset);
// Bank mappings
let mut bank = HashMap::new();
bank.insert("info".to_string(), 0x6101);
bank.insert("slotInfo".to_string(), 0x6102);
bank.insert("info".to_string(), ContractMethod {
id: 0x6101,
params: vec![PbsType::Int],
return_type: PbsType::String,
});
bank.insert("slotInfo".to_string(), ContractMethod {
id: 0x6102,
params: vec![PbsType::Int, PbsType::Int],
return_type: PbsType::String,
});
mappings.insert("Bank".to_string(), bank);
Self { mappings }
}
pub fn resolve(&self, contract: &str, method: &str) -> Option<u32> {
self.mappings.get(contract).and_then(|m| m.get(method)).copied()
self.mappings.get(contract).and_then(|m| m.get(method)).map(|m| m.id)
}
pub fn get_method(&self, contract: &str, method: &str) -> Option<&ContractMethod> {
self.mappings.get(contract).and_then(|m| m.get(method))
}
}

View File

@ -414,7 +414,7 @@ impl<'a> Lowerer<'a> {
if is_host_contract && !is_shadowed {
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
self.emit(Instr::Syscall(syscall_id));
self.emit(Instr::HostCall(syscall_id));
return Ok(());
} else {
self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span);
@ -771,7 +771,7 @@ mod tests {
declare contract Log host {}
fn main() {
Gfx.clear(0);
Log.write(\"Hello\");
Log.write(2, \"Hello\");
}
";
let mut parser = Parser::new(code, 0);
@ -788,9 +788,9 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
// Gfx.clear -> 0x1001
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x1001))));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x1001))));
// Log.write -> 0x5001
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x5001))));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x5001))));
}
#[test]

View File

@ -148,7 +148,12 @@ impl<'a> TypeChecker<'a> {
if let Some(sym) = self.module_symbols.type_symbols.get(&id.name) {
if sym.kind == SymbolKind::Contract && sym.is_host {
// Check if the method exists in registry
if self.contract_registry.resolve(&id.name, &n.member).is_none() {
if let Some(method) = self.contract_registry.get_method(&id.name, &n.member) {
return PbsType::Function {
params: method.params.clone(),
return_type: Box::new(method.return_type.clone()),
};
} else {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
code: Some("E_RESOLVE_UNDEFINED".to_string()),
@ -733,6 +738,32 @@ mod tests {
assert!(res.is_ok());
}
#[test]
fn test_host_method_arity_mismatch() {
let code = "
declare contract Gfx host {}
fn main() {
Gfx.clear(0, 1);
}
";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_host_method_type_mismatch() {
let code = "
declare contract Gfx host {}
fn main() {
Gfx.clear(\"red\");
}
";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_void_return_ok() {
let code = "fn main() { return; }";

View File

@ -9,7 +9,7 @@ pub enum Instr {
/// Placeholder for function calls.
Call(FunctionId, u32),
/// Host calls (syscalls).
Syscall(u32),
HostCall(u32),
/// Variable access.
GetLocal(u32),
SetLocal(u32),

View File

@ -4,7 +4,7 @@ use crate::ir_vm::module::Module;
pub fn validate_module(_module: &Module) -> Result<(), DiagnosticBundle> {
// TODO: Implement common IR validations:
// - Type checking rules
// - Syscall signatures
// - HostCall signatures
// - VM invariants
Ok(())
}

View File

@ -52,7 +52,7 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function>
func_id: *func_id,
arg_count: *arg_count
},
ir_core::Instr::Syscall(id) => ir_vm::InstrKind::Syscall(*id),
ir_core::Instr::HostCall(id) => ir_vm::InstrKind::Syscall(*id),
ir_core::Instr::GetLocal(slot) => ir_vm::InstrKind::GetLocal(*slot),
ir_core::Instr::SetLocal(slot) => ir_vm::InstrKind::SetLocal(*slot),
ir_core::Instr::Pop => ir_vm::InstrKind::Pop,
@ -160,7 +160,7 @@ mod tests {
Block {
id: 1,
instrs: vec![
Instr::Syscall(42),
Instr::HostCall(42),
],
terminator: Terminator::Return,
},
@ -202,7 +202,7 @@ mod tests {
}
match &func.body[5].kind {
InstrKind::Syscall(id) => assert_eq!(*id, 42),
_ => panic!("Expected Syscall 42"),
_ => panic!("Expected HostCall 42"),
}
match &func.body[6].kind {
InstrKind::Ret => (),

View File

@ -1,25 +1,3 @@
# PR-24 — Validate Contract Calls in Frontend (Arity + Types)
### Goal
Move contract validation to compile time.
### Required Changes
* During PBS type checking:
* Validate argument count against contract signature
* Validate argument types
* Lower only validated calls to `HostCall`
### Tests
* Wrong arity → `E_TYPE_MISMATCH`
* Correct call lowers to Core IR `HostCall`
---
# PR-25 — Core IR Invariants Test Suite
### Goal