This commit is contained in:
Nilton Constantino 2026-01-29 17:23:16 +00:00
parent 603c8e7862
commit dc3a0268f1
No known key found for this signature in database
5 changed files with 265 additions and 43 deletions

View File

@ -13,8 +13,11 @@ pub struct Lowerer<'a> {
current_block: Option<Block>, current_block: Option<Block>,
next_block_id: u32, next_block_id: u32,
next_func_id: u32, next_func_id: u32,
next_type_id: u32,
local_vars: Vec<HashMap<String, u32>>, local_vars: Vec<HashMap<String, u32>>,
function_ids: HashMap<String, FunctionId>, function_ids: HashMap<String, FunctionId>,
type_ids: HashMap<String, TypeId>,
struct_slots: HashMap<String, u32>,
contract_registry: ContractRegistry, contract_registry: ContractRegistry,
} }
@ -30,8 +33,11 @@ impl<'a> Lowerer<'a> {
current_block: None, current_block: None,
next_block_id: 0, next_block_id: 0,
next_func_id: 1, next_func_id: 1,
next_type_id: 1,
local_vars: Vec::new(), local_vars: Vec::new(),
function_ids: HashMap::new(), function_ids: HashMap::new(),
type_ids: HashMap::new(),
struct_slots: HashMap::new(),
contract_registry: ContractRegistry::new(), contract_registry: ContractRegistry::new(),
} }
} }
@ -44,6 +50,17 @@ impl<'a> Lowerer<'a> {
self.next_func_id += 1; self.next_func_id += 1;
self.function_ids.insert(n.name.clone(), id); 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 { let mut module = Module {
@ -142,11 +159,46 @@ impl<'a> Lowerer<'a> {
} }
} }
fn lower_alloc(&mut self, _n: &AllocNode) { fn lower_alloc(&mut self, n: &AllocNode) {
// Allocation: Now requires explicit TypeId and slots. let (ty_id, slots) = self.get_type_id_and_slots(&n.ty);
// v0 approximation: TypeId(0), slots: 1. self.emit(Instr::Alloc { ty: ty_id, slots });
// Proper derivation will be added in PR-22. }
self.emit(Instr::Alloc { ty: TypeId(0), slots: 1 });
fn get_type_id_and_slots(&mut self, node: &Node) -> (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);
(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);
(id, size)
}
_ => (TypeId(0), 1),
}
}
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) { fn lower_peek(&mut self, n: &PeekNode) {
@ -363,7 +415,7 @@ impl<'a> Lowerer<'a> {
self.start_block_with_id(merge_id); self.start_block_with_id(merge_id);
} }
fn lower_type_node(&self, node: &Node) -> Type { fn lower_type_node(&mut self, node: &Node) -> Type {
match node { match node {
Node::TypeName(n) => match n.name.as_str() { Node::TypeName(n) => match n.name.as_str() {
"int" => Type::Int, "int" => Type::Int,
@ -373,6 +425,30 @@ impl<'a> Lowerer<'a> {
"void" => Type::Void, "void" => Type::Void,
_ => Type::Struct(n.name.clone()), _ => 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, _ => Type::Void,
} }
} }
@ -709,4 +785,105 @@ mod tests {
// Should be a regular call (which might fail later or be a dummy) // Should be a regular call (which might fail later or be a dummy)
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _)))); assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _))));
} }
#[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");
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");
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");
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");
}
} }

View File

@ -328,7 +328,7 @@ impl Parser {
fn parse_type_ref(&mut self) -> Result<Node, DiagnosticBundle> { fn parse_type_ref(&mut self) -> Result<Node, DiagnosticBundle> {
let id_tok = self.peek().clone(); let id_tok = self.peek().clone();
let name = self.expect_identifier()?; let name = self.expect_identifier()?;
if self.peek().kind == TokenKind::Lt { let mut node = if self.peek().kind == TokenKind::Lt {
self.advance(); // < self.advance(); // <
let mut args = Vec::new(); let mut args = Vec::new();
loop { loop {
@ -340,17 +340,56 @@ impl Parser {
} }
} }
let end_tok = self.consume(TokenKind::Gt)?; let end_tok = self.consume(TokenKind::Gt)?;
Ok(Node::TypeApp(TypeAppNode { Node::TypeApp(TypeAppNode {
span: Span::new(self.file_id, id_tok.span.start, end_tok.span.end), span: Span::new(self.file_id, id_tok.span.start, end_tok.span.end),
base: name, base: name,
args, args,
})) })
} else { } else {
Ok(Node::TypeName(TypeNameNode { Node::TypeName(TypeNameNode {
span: id_tok.span, span: id_tok.span,
name, name,
})) })
};
if self.peek().kind == TokenKind::OpenBracket {
self.advance();
let size_tok = self.peek().clone();
let size = match size_tok.kind {
TokenKind::IntLit(v) => {
self.advance();
v as u32
} }
TokenKind::BoundedLit(v) => {
self.advance();
v
}
_ => return Err(self.error_with_code("integer or bounded literal for array size", Some("E_PARSE_EXPECTED_TOKEN"))),
};
let end_tok = self.consume(TokenKind::CloseBracket)?;
let span = Span::new(self.file_id, node.span().start, end_tok.span.end);
// If it's array<T>[N], we want to represent it cleanly.
// Currently TypeAppNode { base: name, args } was created.
// If base was "array", it already has T in args.
// We can just add N to args.
match &mut node {
Node::TypeApp(ta) if ta.base == "array" => {
ta.args.push(Node::IntLit(IntLitNode { span: size_tok.span, value: size as i64 }));
ta.span = span;
}
_ => {
// Fallback for T[N] if we want to support it, but spec says array<T>[N]
node = Node::TypeApp(TypeAppNode {
span,
base: "array".to_string(),
args: vec![node, Node::IntLit(IntLitNode { span: size_tok.span, value: size as i64 })],
});
}
}
}
Ok(node)
} }
fn parse_block(&mut self) -> Result<Node, DiagnosticBundle> { fn parse_block(&mut self) -> Result<Node, DiagnosticBundle> {

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Type { pub enum Type {
@ -13,8 +14,38 @@ pub enum Type {
Service(String), Service(String),
Contract(String), Contract(String),
ErrorType(String), ErrorType(String),
Array(Box<Type>, u32),
Function { Function {
params: Vec<Type>, params: Vec<Type>,
return_type: Box<Type>, return_type: Box<Type>,
}, },
} }
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Void => write!(f, "void"),
Type::Int => write!(f, "int"),
Type::Float => write!(f, "float"),
Type::Bool => write!(f, "bool"),
Type::String => write!(f, "string"),
Type::Optional(inner) => write!(f, "optional<{}>", inner),
Type::Result(ok, err) => write!(f, "result<{}, {}>", ok, err),
Type::Struct(name) => write!(f, "{}", name),
Type::Service(name) => write!(f, "{}", name),
Type::Contract(name) => write!(f, "{}", name),
Type::ErrorType(name) => write!(f, "{}", name),
Type::Array(inner, size) => write!(f, "array<{}>[{}]", inner, size),
Type::Function { params, return_type } => {
write!(f, "fn(")?;
for (i, param) in params.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", param)?;
}
write!(f, ") -> {}", return_type)
}
}
}
}

View File

@ -116,13 +116,14 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
ir_core::Type::Float => ir_vm::Type::Float, ir_core::Type::Float => ir_vm::Type::Float,
ir_core::Type::Bool => ir_vm::Type::Bool, ir_core::Type::Bool => ir_vm::Type::Bool,
ir_core::Type::String => ir_vm::Type::String, ir_core::Type::String => ir_vm::Type::String,
ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))), // Approximation ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))),
ir_core::Type::Result(ok, _) => lower_type(ok), // Approximation ir_core::Type::Result(ok, _) => lower_type(ok),
ir_core::Type::Struct(_) => ir_vm::Type::Object, ir_core::Type::Struct(_)
ir_core::Type::Service(_) => ir_vm::Type::Object, | ir_core::Type::Service(_)
ir_core::Type::Contract(_) => ir_vm::Type::Object, | ir_core::Type::Contract(_)
ir_core::Type::ErrorType(_) => ir_vm::Type::Object, | ir_core::Type::ErrorType(_) => ir_vm::Type::Object,
ir_core::Type::Function { .. } => ir_vm::Type::Function, ir_core::Type::Function { .. } => ir_vm::Type::Function,
ir_core::Type::Array(inner, _) => ir_vm::Type::Array(Box::new(lower_type(inner))),
} }
} }

View File

@ -1,29 +1,3 @@
# PR-22 — Make Allocation Shape Explicit in Core IR
### Goal
Stop implicit / guessed heap layouts.
### Required Changes
* Replace any shape-less `Alloc` with:
```rust
Alloc { ty: TypeId, slots: u32 }
```
Rules:
* `TypeId` comes from frontend type checking
* `slots` is derived deterministically (struct fields / array size)
### Tests
* Allocating storage struct emits correct `slots`
* Allocating array emits correct `slots`
---
# PR-23 — Eliminate Invalid Call Fallbacks # PR-23 — Eliminate Invalid Call Fallbacks
### Goal ### Goal