pr 22
This commit is contained in:
parent
603c8e7862
commit
dc3a0268f1
@ -13,8 +13,11 @@ pub struct Lowerer<'a> {
|
||||
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,
|
||||
}
|
||||
|
||||
@ -30,8 +33,11 @@ impl<'a> Lowerer<'a> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
@ -44,6 +50,17 @@ impl<'a> Lowerer<'a> {
|
||||
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 {
|
||||
@ -142,11 +159,46 @@ impl<'a> Lowerer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_alloc(&mut self, _n: &AllocNode) {
|
||||
// Allocation: Now requires explicit TypeId and slots.
|
||||
// v0 approximation: TypeId(0), slots: 1.
|
||||
// Proper derivation will be added in PR-22.
|
||||
self.emit(Instr::Alloc { ty: TypeId(0), slots: 1 });
|
||||
fn lower_alloc(&mut self, n: &AllocNode) {
|
||||
let (ty_id, slots) = self.get_type_id_and_slots(&n.ty);
|
||||
self.emit(Instr::Alloc { ty: ty_id, slots });
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -363,7 +415,7 @@ impl<'a> Lowerer<'a> {
|
||||
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 {
|
||||
Node::TypeName(n) => match n.name.as_str() {
|
||||
"int" => Type::Int,
|
||||
@ -373,6 +425,30 @@ impl<'a> Lowerer<'a> {
|
||||
"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,
|
||||
}
|
||||
}
|
||||
@ -709,4 +785,105 @@ mod tests {
|
||||
// Should be a regular call (which might fail later or be a dummy)
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,7 +328,7 @@ impl Parser {
|
||||
fn parse_type_ref(&mut self) -> Result<Node, DiagnosticBundle> {
|
||||
let id_tok = self.peek().clone();
|
||||
let name = self.expect_identifier()?;
|
||||
if self.peek().kind == TokenKind::Lt {
|
||||
let mut node = if self.peek().kind == TokenKind::Lt {
|
||||
self.advance(); // <
|
||||
let mut args = Vec::new();
|
||||
loop {
|
||||
@ -340,17 +340,56 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
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),
|
||||
base: name,
|
||||
args,
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
Ok(Node::TypeName(TypeNameNode {
|
||||
Node::TypeName(TypeNameNode {
|
||||
span: id_tok.span,
|
||||
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> {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Type {
|
||||
@ -13,8 +14,38 @@ pub enum Type {
|
||||
Service(String),
|
||||
Contract(String),
|
||||
ErrorType(String),
|
||||
Array(Box<Type>, u32),
|
||||
Function {
|
||||
params: Vec<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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::Bool => ir_vm::Type::Bool,
|
||||
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::Result(ok, _) => lower_type(ok), // Approximation
|
||||
ir_core::Type::Struct(_) => ir_vm::Type::Object,
|
||||
ir_core::Type::Service(_) => ir_vm::Type::Object,
|
||||
ir_core::Type::Contract(_) => ir_vm::Type::Object,
|
||||
ir_core::Type::ErrorType(_) => ir_vm::Type::Object,
|
||||
ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))),
|
||||
ir_core::Type::Result(ok, _) => lower_type(ok),
|
||||
ir_core::Type::Struct(_)
|
||||
| ir_core::Type::Service(_)
|
||||
| ir_core::Type::Contract(_)
|
||||
| ir_core::Type::ErrorType(_) => ir_vm::Type::Object,
|
||||
ir_core::Type::Function { .. } => ir_vm::Type::Function,
|
||||
ir_core::Type::Array(inner, _) => ir_vm::Type::Array(Box::new(lower_type(inner))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
### Goal
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user