1059 lines
38 KiB
Rust
1059 lines
38 KiB
Rust
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
|
use crate::frontends::pbs::ast::*;
|
|
use crate::frontends::pbs::symbols::*;
|
|
use crate::frontends::pbs::contracts::ContractRegistry;
|
|
use crate::ir_core;
|
|
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
|
|
use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type};
|
|
use std::collections::HashMap;
|
|
|
|
pub struct Lowerer<'a> {
|
|
module_symbols: &'a ModuleSymbols,
|
|
program: Program,
|
|
current_function: Option<Function>,
|
|
current_block: Option<Block>,
|
|
next_block_id: u32,
|
|
next_func_id: u32,
|
|
next_type_id: u32,
|
|
local_vars: Vec<HashMap<String, u32>>,
|
|
function_ids: HashMap<String, FunctionId>,
|
|
type_ids: HashMap<String, TypeId>,
|
|
struct_slots: HashMap<String, u32>,
|
|
contract_registry: ContractRegistry,
|
|
diagnostics: Vec<Diagnostic>,
|
|
}
|
|
|
|
impl<'a> Lowerer<'a> {
|
|
pub fn new(module_symbols: &'a ModuleSymbols) -> Self {
|
|
let mut field_offsets = HashMap::new();
|
|
field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation
|
|
|
|
Self {
|
|
module_symbols,
|
|
program: Program {
|
|
const_pool: ir_core::ConstPool::new(),
|
|
modules: Vec::new(),
|
|
field_offsets,
|
|
field_types: HashMap::new(),
|
|
},
|
|
current_function: None,
|
|
current_block: None,
|
|
next_block_id: 0,
|
|
next_func_id: 1,
|
|
next_type_id: 1,
|
|
local_vars: Vec::new(),
|
|
function_ids: HashMap::new(),
|
|
type_ids: HashMap::new(),
|
|
struct_slots: HashMap::new(),
|
|
contract_registry: ContractRegistry::new(),
|
|
diagnostics: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn error(&mut self, code: &str, message: String, span: crate::common::spans::Span) {
|
|
self.diagnostics.push(Diagnostic {
|
|
level: DiagnosticLevel::Error,
|
|
code: Some(code.to_string()),
|
|
message,
|
|
span: Some(span),
|
|
});
|
|
}
|
|
|
|
pub fn lower_file(mut self, file: &FileNode, module_name: &str) -> Result<Program, DiagnosticBundle> {
|
|
// Pre-scan for function declarations to assign IDs
|
|
for decl in &file.decls {
|
|
if let Node::FnDecl(n) = decl {
|
|
let id = FunctionId(self.next_func_id);
|
|
self.next_func_id += 1;
|
|
self.function_ids.insert(n.name.clone(), id);
|
|
}
|
|
if let Node::TypeDecl(n) = decl {
|
|
let id = TypeId(self.next_type_id);
|
|
self.next_type_id += 1;
|
|
self.type_ids.insert(n.name.clone(), id);
|
|
|
|
if n.type_kind == "struct" {
|
|
if let Node::TypeBody(body) = &*n.body {
|
|
self.struct_slots.insert(n.name.clone(), body.members.len() as u32);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut module = Module {
|
|
name: module_name.to_string(),
|
|
functions: Vec::new(),
|
|
};
|
|
|
|
for decl in &file.decls {
|
|
match decl {
|
|
Node::FnDecl(fn_decl) => {
|
|
let func = self.lower_function(fn_decl).map_err(|_| DiagnosticBundle {
|
|
diagnostics: self.diagnostics.clone(),
|
|
})?;
|
|
module.functions.push(func);
|
|
}
|
|
_ => {} // Other declarations not handled for now
|
|
}
|
|
}
|
|
|
|
self.program.modules.push(module);
|
|
Ok(self.program)
|
|
}
|
|
|
|
fn lower_function(&mut self, n: &FnDeclNode) -> Result<Function, ()> {
|
|
let func_id = *self.function_ids.get(&n.name).unwrap();
|
|
self.next_block_id = 0;
|
|
self.local_vars = vec![HashMap::new()];
|
|
|
|
let mut params = Vec::new();
|
|
let mut local_types = HashMap::new();
|
|
for (i, param) in n.params.iter().enumerate() {
|
|
let ty = self.lower_type_node(¶m.ty);
|
|
params.push(Param {
|
|
name: param.name.clone(),
|
|
ty: ty.clone(),
|
|
});
|
|
self.local_vars[0].insert(param.name.clone(), i as u32);
|
|
local_types.insert(i as u32, ty);
|
|
}
|
|
|
|
let ret_ty = if let Some(ret) = &n.ret {
|
|
self.lower_type_node(ret)
|
|
} else {
|
|
Type::Void
|
|
};
|
|
|
|
let func = Function {
|
|
id: func_id,
|
|
name: n.name.clone(),
|
|
params,
|
|
return_type: ret_ty,
|
|
blocks: Vec::new(),
|
|
local_types,
|
|
};
|
|
|
|
self.current_function = Some(func);
|
|
self.start_block();
|
|
self.lower_node(&n.body)?;
|
|
|
|
// Ensure every function ends with a return if not already terminated
|
|
if let Some(mut block) = self.current_block.take() {
|
|
if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) {
|
|
block.terminator = Terminator::Return;
|
|
}
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
|
|
Ok(self.current_function.take().unwrap())
|
|
}
|
|
|
|
fn lower_node(&mut self, node: &Node) -> Result<(), ()> {
|
|
match node {
|
|
Node::Block(n) => self.lower_block(n),
|
|
Node::LetStmt(n) => self.lower_let_stmt(n),
|
|
Node::ExprStmt(n) => self.lower_node(&n.expr),
|
|
Node::ReturnStmt(n) => self.lower_return_stmt(n),
|
|
Node::IntLit(n) => {
|
|
let id = self.program.const_pool.add_int(n.value);
|
|
self.emit(Instr::PushConst(id));
|
|
Ok(())
|
|
}
|
|
Node::FloatLit(n) => {
|
|
let id = self.program.const_pool.add_float(n.value);
|
|
self.emit(Instr::PushConst(id));
|
|
Ok(())
|
|
}
|
|
Node::StringLit(n) => {
|
|
let id = self.program.const_pool.add_string(n.value.clone());
|
|
self.emit(Instr::PushConst(id));
|
|
Ok(())
|
|
}
|
|
Node::BoundedLit(n) => {
|
|
let id = self.program.const_pool.add_int(n.value as i64);
|
|
self.emit(Instr::PushConst(id));
|
|
Ok(())
|
|
}
|
|
Node::Ident(n) => self.lower_ident(n),
|
|
Node::MemberAccess(n) => self.lower_member_access(n),
|
|
Node::Call(n) => self.lower_call(n),
|
|
Node::Binary(n) => self.lower_binary(n),
|
|
Node::Unary(n) => self.lower_unary(n),
|
|
Node::IfExpr(n) => self.lower_if_expr(n),
|
|
Node::Alloc(n) => self.lower_alloc(n),
|
|
Node::Mutate(n) => self.lower_mutate(n),
|
|
Node::Borrow(n) => self.lower_borrow(n),
|
|
Node::Peek(n) => self.lower_peek(n),
|
|
_ => {
|
|
// For unhandled nodes, we can either ignore or error.
|
|
// Given the PR, maybe we should error on things we don't support yet in lowering.
|
|
self.error("E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", node), node.span());
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_alloc(&mut self, n: &AllocNode) -> Result<(), ()> {
|
|
let (ty_id, slots) = self.get_type_id_and_slots(&n.ty)?;
|
|
self.emit(Instr::Alloc { ty: ty_id, slots });
|
|
Ok(())
|
|
}
|
|
|
|
fn get_type_id_and_slots(&mut self, node: &Node) -> Result<(TypeId, u32), ()> {
|
|
match node {
|
|
Node::TypeName(n) => {
|
|
let slots = self.struct_slots.get(&n.name).cloned().unwrap_or(1);
|
|
let id = self.get_or_create_type_id(&n.name);
|
|
Ok((id, slots))
|
|
}
|
|
Node::TypeApp(ta) if ta.base == "array" => {
|
|
let size = if ta.args.len() > 1 {
|
|
if let Node::IntLit(il) = &ta.args[1] {
|
|
il.value as u32
|
|
} else {
|
|
1
|
|
}
|
|
} else {
|
|
1
|
|
};
|
|
let elem_ty = self.lower_type_node(&ta.args[0]);
|
|
let name = format!("array<{}>[{}]", elem_ty, size);
|
|
let id = self.get_or_create_type_id(&name);
|
|
Ok((id, size))
|
|
}
|
|
_ => {
|
|
self.error("E_RESOLVE_UNDEFINED", format!("Unknown type in allocation: {:?}", node), node.span());
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_or_create_type_id(&mut self, name: &str) -> TypeId {
|
|
if let Some(id) = self.type_ids.get(name) {
|
|
*id
|
|
} else {
|
|
let id = TypeId(self.next_type_id);
|
|
self.next_type_id += 1;
|
|
self.type_ids.insert(name.to_string(), id);
|
|
id
|
|
}
|
|
}
|
|
|
|
fn lower_peek(&mut self, n: &PeekNode) -> Result<(), ()> {
|
|
// 1. Evaluate target (gate)
|
|
self.lower_node(&n.target)?;
|
|
|
|
// 2. Preserve gate identity
|
|
let gate_slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
|
self.emit(Instr::SetLocal(gate_slot));
|
|
|
|
// 3. Begin Operation
|
|
self.emit(Instr::BeginPeek { gate: ValueId(gate_slot) });
|
|
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
|
|
|
|
// 4. Bind view to local
|
|
self.local_vars.push(HashMap::new());
|
|
let view_slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), view_slot);
|
|
self.emit(Instr::SetLocal(view_slot));
|
|
|
|
// 5. Body
|
|
self.lower_node(&n.body)?;
|
|
|
|
// 6. End Operation
|
|
self.emit(Instr::EndPeek);
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_borrow(&mut self, n: &BorrowNode) -> Result<(), ()> {
|
|
// 1. Evaluate target (gate)
|
|
self.lower_node(&n.target)?;
|
|
|
|
// 2. Preserve gate identity
|
|
let gate_slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
|
self.emit(Instr::SetLocal(gate_slot));
|
|
|
|
// 3. Begin Operation
|
|
self.emit(Instr::BeginBorrow { gate: ValueId(gate_slot) });
|
|
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
|
|
|
|
// 4. Bind view to local
|
|
self.local_vars.push(HashMap::new());
|
|
let view_slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), view_slot);
|
|
self.emit(Instr::SetLocal(view_slot));
|
|
|
|
// 5. Body
|
|
self.lower_node(&n.body)?;
|
|
|
|
// 6. End Operation
|
|
self.emit(Instr::EndBorrow);
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_mutate(&mut self, n: &MutateNode) -> Result<(), ()> {
|
|
// 1. Evaluate target (gate)
|
|
self.lower_node(&n.target)?;
|
|
|
|
// 2. Preserve gate identity
|
|
let gate_slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
|
self.emit(Instr::SetLocal(gate_slot));
|
|
|
|
// 3. Begin Operation
|
|
self.emit(Instr::BeginMutate { gate: ValueId(gate_slot) });
|
|
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
|
|
|
|
// 4. Bind view to local
|
|
self.local_vars.push(HashMap::new());
|
|
let view_slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), view_slot);
|
|
self.emit(Instr::SetLocal(view_slot));
|
|
|
|
// 5. Body
|
|
self.lower_node(&n.body)?;
|
|
|
|
// 6. End Operation
|
|
self.emit(Instr::EndMutate);
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_block(&mut self, n: &BlockNode) -> Result<(), ()> {
|
|
self.local_vars.push(HashMap::new());
|
|
for stmt in &n.stmts {
|
|
self.lower_node(stmt)?;
|
|
}
|
|
if let Some(tail) = &n.tail {
|
|
self.lower_node(tail)?;
|
|
}
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_let_stmt(&mut self, n: &LetStmtNode) -> Result<(), ()> {
|
|
self.lower_node(&n.init)?;
|
|
let slot = self.get_next_local_slot();
|
|
self.local_vars.last_mut().unwrap().insert(n.name.clone(), slot);
|
|
self.emit(Instr::SetLocal(slot));
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_return_stmt(&mut self, n: &ReturnStmtNode) -> Result<(), ()> {
|
|
if let Some(expr) = &n.expr {
|
|
self.lower_node(expr)?;
|
|
}
|
|
self.terminate(Terminator::Return);
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_ident(&mut self, n: &IdentNode) -> Result<(), ()> {
|
|
if let Some(slot) = self.lookup_local(&n.name) {
|
|
self.emit(Instr::GetLocal(slot));
|
|
Ok(())
|
|
} else {
|
|
// Check for special identifiers
|
|
match n.name.as_str() {
|
|
"true" => {
|
|
let id = self.program.const_pool.add_int(1);
|
|
self.emit(Instr::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
"false" => {
|
|
let id = self.program.const_pool.add_int(0);
|
|
self.emit(Instr::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
"none" => {
|
|
// For now, treat none as 0. This should be refined when optional is fully implemented.
|
|
let id = self.program.const_pool.add_int(0);
|
|
self.emit(Instr::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// Check if it's a function (for first-class functions if supported)
|
|
if let Some(_id) = self.function_ids.get(&n.name) {
|
|
// Push function reference? Not in v0.
|
|
self.error("E_LOWER_UNSUPPORTED", format!("First-class function reference '{}' not supported", n.name), n.span);
|
|
Err(())
|
|
} else {
|
|
self.error("E_RESOLVE_UNDEFINED", format!("Undefined identifier '{}'", n.name), n.span);
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_member_access(&mut self, n: &MemberAccessNode) -> Result<(), ()> {
|
|
if let Node::Ident(id) = &*n.object {
|
|
if id.name == "Color" {
|
|
let val = match n.member.as_str() {
|
|
"BLACK" => 0x0000,
|
|
"WHITE" => 0xFFFF,
|
|
"RED" => 0xF800,
|
|
"GREEN" => 0x07E0,
|
|
"BLUE" => 0x001F,
|
|
"MAGENTA" => 0xF81F,
|
|
"TRANSPARENT" => 0x0000,
|
|
"COLOR_KEY" => 0x0000,
|
|
_ => {
|
|
self.error("E_RESOLVE_UNDEFINED", format!("Undefined Color constant '{}'", n.member), n.span);
|
|
return Err(());
|
|
}
|
|
};
|
|
let id = self.program.const_pool.add_int(val);
|
|
self.emit(Instr::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// For instance members (e.g., p.any), we'll just ignore for now in v0
|
|
// to pass typecheck tests if they are being lowered.
|
|
// In a real implementation we would need type information.
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_call(&mut self, n: &CallNode) -> Result<(), ()> {
|
|
for arg in &n.args {
|
|
self.lower_node(arg)?;
|
|
}
|
|
match &*n.callee {
|
|
Node::Ident(id_node) => {
|
|
if let Some(func_id) = self.function_ids.get(&id_node.name) {
|
|
self.emit(Instr::Call(*func_id, n.args.len() as u32));
|
|
Ok(())
|
|
} else {
|
|
// Check for special built-in functions
|
|
match id_node.name.as_str() {
|
|
"some" | "ok" | "err" => {
|
|
// For now, these are effectively nops in terms of IR emission,
|
|
// as they just wrap the already pushed arguments.
|
|
// In a real implementation, they might push a tag.
|
|
return Ok(());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
self.error("E_RESOLVE_UNDEFINED", format!("Undefined function '{}'", id_node.name), id_node.span);
|
|
Err(())
|
|
}
|
|
}
|
|
Node::MemberAccess(ma) => {
|
|
if let Node::Ident(obj_id) = &*ma.object {
|
|
// Check if it's a host contract according to symbol table
|
|
let is_host_contract = self.module_symbols.type_symbols.get(&obj_id.name)
|
|
.map(|sym| sym.kind == SymbolKind::Contract && sym.is_host)
|
|
.unwrap_or(false);
|
|
|
|
// Ensure it's not shadowed by a local variable
|
|
let is_shadowed = self.lookup_local(&obj_id.name).is_some();
|
|
|
|
if is_host_contract && !is_shadowed {
|
|
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
|
|
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);
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regular member call (method) or fallback
|
|
// In v0 we don't handle this yet.
|
|
self.error("E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), ma.span);
|
|
Err(())
|
|
}
|
|
_ => {
|
|
self.error("E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), n.callee.span());
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_binary(&mut self, n: &BinaryNode) -> Result<(), ()> {
|
|
self.lower_node(&n.left)?;
|
|
self.lower_node(&n.right)?;
|
|
match n.op.as_str() {
|
|
"+" => self.emit(Instr::Add),
|
|
"-" => self.emit(Instr::Sub),
|
|
"*" => self.emit(Instr::Mul),
|
|
"/" => self.emit(Instr::Div),
|
|
"==" => self.emit(Instr::Eq),
|
|
"!=" => self.emit(Instr::Neq),
|
|
"<" => self.emit(Instr::Lt),
|
|
"<=" => self.emit(Instr::Lte),
|
|
">" => self.emit(Instr::Gt),
|
|
">=" => self.emit(Instr::Gte),
|
|
"&&" => self.emit(Instr::And),
|
|
"||" => self.emit(Instr::Or),
|
|
_ => {
|
|
self.error("E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), n.span);
|
|
return Err(());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_unary(&mut self, n: &UnaryNode) -> Result<(), ()> {
|
|
self.lower_node(&n.expr)?;
|
|
match n.op.as_str() {
|
|
"-" => self.emit(Instr::Neg),
|
|
"!" => self.emit(Instr::Not),
|
|
_ => {
|
|
self.error("E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), n.span);
|
|
return Err(());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_if_expr(&mut self, n: &IfExprNode) -> Result<(), ()> {
|
|
let then_id = self.reserve_block_id();
|
|
let else_id = self.reserve_block_id();
|
|
let merge_id = self.reserve_block_id();
|
|
|
|
self.lower_node(&n.cond)?;
|
|
self.terminate(Terminator::JumpIfFalse {
|
|
target: else_id,
|
|
else_target: then_id,
|
|
});
|
|
|
|
// Then block
|
|
self.start_block_with_id(then_id);
|
|
self.lower_node(&n.then_block)?;
|
|
self.terminate(Terminator::Jump(merge_id));
|
|
|
|
// Else block
|
|
self.start_block_with_id(else_id);
|
|
if let Some(else_block) = &n.else_block {
|
|
self.lower_node(else_block)?;
|
|
}
|
|
self.terminate(Terminator::Jump(merge_id));
|
|
|
|
// Merge block
|
|
self.start_block_with_id(merge_id);
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_type_node(&mut self, node: &Node) -> Type {
|
|
match node {
|
|
Node::TypeName(n) => match n.name.as_str() {
|
|
"int" => Type::Int,
|
|
"float" => Type::Float,
|
|
"bool" => Type::Bool,
|
|
"string" => Type::String,
|
|
"void" => Type::Void,
|
|
_ => Type::Struct(n.name.clone()),
|
|
},
|
|
Node::TypeApp(ta) => {
|
|
if ta.base == "array" {
|
|
let elem_ty = self.lower_type_node(&ta.args[0]);
|
|
let size = if ta.args.len() > 1 {
|
|
if let Node::IntLit(il) = &ta.args[1] {
|
|
il.value as u32
|
|
} else {
|
|
0
|
|
}
|
|
} else {
|
|
0
|
|
};
|
|
Type::Array(Box::new(elem_ty), size)
|
|
} else if ta.base == "optional" {
|
|
Type::Optional(Box::new(self.lower_type_node(&ta.args[0])))
|
|
} else if ta.base == "result" {
|
|
Type::Result(
|
|
Box::new(self.lower_type_node(&ta.args[0])),
|
|
Box::new(self.lower_type_node(&ta.args[1]))
|
|
)
|
|
} else {
|
|
Type::Struct(format!("{}<{}>", ta.base, ta.args.len()))
|
|
}
|
|
}
|
|
_ => Type::Void,
|
|
}
|
|
}
|
|
|
|
fn start_block(&mut self) {
|
|
let id = self.reserve_block_id();
|
|
self.start_block_with_id(id);
|
|
}
|
|
|
|
fn start_block_with_id(&mut self, id: u32) {
|
|
if let Some(block) = self.current_block.take() {
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
self.current_block = Some(Block {
|
|
id,
|
|
instrs: Vec::new(),
|
|
terminator: Terminator::Return, // Default, will be overwritten
|
|
});
|
|
}
|
|
|
|
fn reserve_block_id(&mut self) -> u32 {
|
|
let id = self.next_block_id;
|
|
self.next_block_id += 1;
|
|
id
|
|
}
|
|
|
|
fn emit(&mut self, instr: Instr) {
|
|
if let Some(block) = &mut self.current_block {
|
|
block.instrs.push(instr);
|
|
}
|
|
}
|
|
|
|
fn terminate(&mut self, terminator: Terminator) {
|
|
if let Some(mut block) = self.current_block.take() {
|
|
block.terminator = terminator;
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_next_local_slot(&self) -> u32 {
|
|
self.local_vars.iter().map(|s| s.len() as u32).sum()
|
|
}
|
|
|
|
fn lookup_local(&self, name: &str) -> Option<u32> {
|
|
for scope in self.local_vars.iter().rev() {
|
|
if let Some(slot) = scope.get(name) {
|
|
return Some(*slot);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::frontends::pbs::parser::Parser;
|
|
use crate::frontends::pbs::collector::SymbolCollector;
|
|
use crate::frontends::pbs::symbols::ModuleSymbols;
|
|
use crate::ir_core;
|
|
|
|
#[test]
|
|
fn test_basic_lowering() {
|
|
let code = "
|
|
fn add(a: int, b: int): int {
|
|
return a + b;
|
|
}
|
|
fn main() {
|
|
let x = add(10, 20);
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
// Verify program structure
|
|
assert_eq!(program.modules.len(), 1);
|
|
let module = &program.modules[0];
|
|
assert_eq!(module.functions.len(), 2);
|
|
|
|
let add_func = module.functions.iter().find(|f| f.name == "add").unwrap();
|
|
assert_eq!(add_func.params.len(), 2);
|
|
assert_eq!(add_func.return_type, ir_core::Type::Int);
|
|
|
|
// Verify blocks
|
|
assert!(add_func.blocks.len() >= 1);
|
|
let first_block = &add_func.blocks[0];
|
|
// Check for Add instruction
|
|
assert!(first_block.instrs.iter().any(|i| matches!(i, ir_core::Instr::Add)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_control_flow_lowering() {
|
|
let code = "
|
|
fn max(a: int, b: int): int {
|
|
if (a > b) {
|
|
return a;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let max_func = &program.modules[0].functions[0];
|
|
// Should have multiple blocks for if-else
|
|
assert!(max_func.blocks.len() >= 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_lowering() {
|
|
let code = "
|
|
fn test_hip() {
|
|
let g = alloc int;
|
|
mutate g as x {
|
|
let y = x + 1;
|
|
}
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::BeginMutate { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::EndMutate)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_lowering_golden() {
|
|
let code = "
|
|
fn test_hip() {
|
|
let g = alloc int;
|
|
mutate g as x {
|
|
let y = x + 1;
|
|
}
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).unwrap();
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let json = serde_json::to_string_pretty(&program).unwrap();
|
|
|
|
// Assertions for PR-20 HIP Semantics:
|
|
// 1. Gate is preserved in a local (SetLocal(1) after GetLocal(0))
|
|
// 2. BeginMutate uses that local (BeginMutate { gate: 1 })
|
|
// 3. EndMutate exists
|
|
// 4. No ReadGate/WriteGate (they were removed from Instr)
|
|
|
|
assert!(json.contains("\"SetLocal\": 1"), "Gate should be stored in a local");
|
|
assert!(json.contains("\"BeginMutate\""), "Should have BeginMutate");
|
|
assert!(json.contains("\"gate\": 1"), "BeginMutate should use the gate local");
|
|
assert!(json.contains("\"EndMutate\""), "Should have EndMutate");
|
|
assert!(!json.contains("ReadGate"), "ReadGate should be gone");
|
|
assert!(!json.contains("WriteGate"), "WriteGate should be gone");
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_semantics_distinction() {
|
|
let code = "
|
|
fn test_hip(g: int) {
|
|
peek g as p {
|
|
let x = p;
|
|
}
|
|
borrow g as b {
|
|
let y = b;
|
|
}
|
|
mutate g as m {
|
|
let z = m;
|
|
}
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
// Assert distinct Core IR instruction sequences
|
|
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginPeek { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i, Instr::EndPeek)));
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginBorrow { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i, Instr::EndBorrow)));
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginMutate { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i, Instr::EndMutate)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_host_contract_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
declare contract Log host {}
|
|
fn main() {
|
|
Gfx.clear(0);
|
|
Log.write(2, \"Hello\");
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
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::HostCall(0x1001))));
|
|
// Log.write -> 0x5001
|
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x5001))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_contract_call_without_host_lowering() {
|
|
let code = "
|
|
declare contract Gfx {}
|
|
fn main() {
|
|
Gfx.clear(0);
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let result = lowerer.lower_file(&ast, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string())));
|
|
}
|
|
|
|
#[test]
|
|
fn test_shadowed_contract_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
fn main() {
|
|
let Gfx = 10;
|
|
Gfx.clear(0);
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let result = lowerer.lower_file(&ast, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string())));
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_contract_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
fn main() {
|
|
Gfx.invalidMethod(0);
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let result = lowerer.lower_file(&ast, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
|
}
|
|
|
|
#[test]
|
|
fn test_alloc_struct_slots() {
|
|
let code = "
|
|
declare struct Vec3 {
|
|
x: int,
|
|
y: int,
|
|
z: int
|
|
}
|
|
fn main() {
|
|
let v = alloc Vec3;
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
let alloc = instrs.iter().find_map(|i| {
|
|
if let Instr::Alloc { ty, slots } = i {
|
|
Some((ty, slots))
|
|
} else {
|
|
None
|
|
}
|
|
}).expect("Should have Alloc instruction");
|
|
|
|
assert_eq!(*alloc.1, 3, "Vec3 should have 3 slots");
|
|
assert!(alloc.0.0 > 0, "Should have a valid TypeId");
|
|
}
|
|
|
|
#[test]
|
|
fn test_alloc_array_slots() {
|
|
let code = "
|
|
fn main() {
|
|
let a = alloc array<int>[10b];
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
let alloc = instrs.iter().find_map(|i| {
|
|
if let Instr::Alloc { ty, slots } = i {
|
|
Some((ty, slots))
|
|
} else {
|
|
None
|
|
}
|
|
}).expect("Should have Alloc instruction");
|
|
|
|
assert_eq!(*alloc.1, 10, "array<int>[10b] should have 10 slots");
|
|
assert!(alloc.0.0 > 0, "Should have a valid TypeId");
|
|
}
|
|
|
|
#[test]
|
|
fn test_alloc_primitive_slots() {
|
|
let code = "
|
|
fn main() {
|
|
let x = alloc int;
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
let alloc = instrs.iter().find_map(|i| {
|
|
if let Instr::Alloc { ty, slots } = i {
|
|
Some((ty, slots))
|
|
} else {
|
|
None
|
|
}
|
|
}).expect("Should have Alloc instruction");
|
|
|
|
assert_eq!(*alloc.1, 1, "Primitive int should have 1 slot");
|
|
assert!(alloc.0.0 > 0, "Should have a valid TypeId");
|
|
}
|
|
|
|
#[test]
|
|
fn test_missing_function_error() {
|
|
let code = "
|
|
fn main() {
|
|
missing_func();
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let result = lowerer.lower_file(&ast, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
|
assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined function 'missing_func'")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_unresolved_ident_error() {
|
|
let code = "
|
|
fn main() {
|
|
let x = undefined_var;
|
|
}
|
|
";
|
|
let mut parser = Parser::new(code, 0);
|
|
let ast = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new();
|
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let lowerer = Lowerer::new(&module_symbols);
|
|
let result = lowerer.lower_file(&ast, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
|
|
assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'")));
|
|
}
|
|
}
|