feat: add support for arrays

This commit is contained in:
2026-04-23 00:19:11 +02:00
parent ec2aa771fa
commit 0a74262cee
11 changed files with 512 additions and 11 deletions
+5
View File
@@ -0,0 +1,5 @@
#include <stdio.h>
void print_number(int number) {
printf("%d", number);
}
+204
View File
@@ -0,0 +1,204 @@
foreign fn putchar(c: i32) -> i32;
foreign fn print_number(n: i32);
foreign fn sqrtf(num: f32) -> f32;
struct Vec3 {
x: f32,
y: f32,
z: f32
}
struct Ray {
origin: Vec3,
dir: Vec3
}
struct Sphere {
center: Vec3,
radius: f32,
color: Vec3
}
struct Hit {
hit: bool,
t: f32,
normal: Vec3,
color: Vec3
}
fn vec3_add(a: Vec3, b: Vec3) -> Vec3 {
return Vec3 { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
}
fn vec3_sub(a: Vec3, b: Vec3) -> Vec3 {
return Vec3 { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
}
fn vec3_mul(a: Vec3, t: f32) -> Vec3 {
return Vec3 { x: a.x * t, y: a.y * t, z: a.z * t };
}
fn vec3_dot(a: Vec3, b: Vec3) -> f32 {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
fn vec3_normalize(v: Vec3) -> Vec3 {
let len = sqrtf(vec3_dot(v, v));
if len == 0.0 {
return Vec3 { x: 0.0, y: 0.0, z: 0.0 };
}
return vec3_mul(v, 1.0 / len);
}
fn intersect_sphere(ray: *Ray, sphere: *Sphere, t_min: f32, t_max: f32) -> Hit {
let oc = vec3_sub((*ray).origin, (*sphere).center);
let a = vec3_dot((*ray).dir, (*ray).dir);
let half_b = vec3_dot(oc, (*ray).dir);
let c = vec3_dot(oc, oc) - (*sphere).radius * (*sphere).radius;
let discriminant = half_b * half_b - a * c;
let miss = Hit {
hit: false,
t: 0.0,
normal: Vec3 { x: 0.0, y: 0.0, z: 0.0 },
color: Vec3 { x: 0.0, y: 0.0, z: 0.0 }
};
if discriminant < 0.0 {
return miss;
}
let sqrtd = sqrtf(discriminant);
let root = (-half_b - sqrtd) / a;
if root < t_min {
root = (-half_b + sqrtd) / a;
if root < t_min {
return miss;
}
}
if root > t_max {
return miss;
}
let hit_point = vec3_add((*ray).origin, vec3_mul((*ray).dir, root));
let normal = vec3_mul(vec3_sub(hit_point, (*sphere).center), 1.0 / (*sphere).radius);
return Hit {
hit: true,
t: root,
normal: normal,
color: (*sphere).color
};
}
fn ray_color(ray: *Ray, spheres: *[Sphere; 2]) -> Vec3 {
let closest_so_far = 1000.0;
let hit_anything = false;
let hit_color = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
let hit_normal = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
let i = 0;
while i < 2 {
// Pointer arithmetic to array structs via the &[] index syntax
let hit = intersect_sphere(ray, &(*spheres)[i], 0.001, closest_so_far);
if hit.hit {
hit_anything = true;
closest_so_far = hit.t;
hit_color = hit.color;
hit_normal = hit.normal;
}
i = i + 1;
}
if hit_anything {
// We remap the surface normal from [-1..1] to the [0..1] color space
let mapped_normal = vec3_mul(vec3_add(hit_normal, Vec3 { x: 1.0, y: 1.0, z: 1.0 }), 0.5);
// Multiply the resulting surface map shading by the intrinsic color of the object
return Vec3 {
x: hit_color.x * mapped_normal.x,
y: hit_color.y * mapped_normal.y,
z: hit_color.z * mapped_normal.z
};
}
// Beautiful skybox blue to white linear gradient
let unit_dir = vec3_normalize((*ray).dir);
let t = 0.5 * (unit_dir.y + 1.0);
return vec3_add(
vec3_mul(Vec3 { x: 1.0, y: 1.0, z: 1.0 }, 1.0 - t),
vec3_mul(Vec3 { x: 0.5, y: 0.7, z: 1.0 }, t)
);
}
fn main() -> i32 {
let image_width = 1024;
let image_height = 1024;
let spheres: [Sphere; 2] = [
Sphere {
center: Vec3 { x: 0.0, y: 0.0, z: -1.0 },
radius: 0.5,
color: Vec3 { x: 1.0, y: 0.2, z: 0.2 } // Red sphere
},
Sphere {
center: Vec3 { x: 0.0, y: -100.5, z: -1.0 },
radius: 100.0,
color: Vec3 { x: 0.2, y: 0.8, z: 0.2 } // Green ground
}
];
// Print standard PPM Image File Header
putchar(80); putchar(51); putchar(10); // "P3\n"
print_number(image_width);
putchar(32); // " "
print_number(image_height);
putchar(10); // "\n"
print_number(255);
putchar(10); // "255\n"
let j = image_height - 1;
while j >= 0 {
let i = 0;
while i < image_width {
let u = (i as f32) / (image_width as f32 - 1.0);
let v = (j as f32) / (image_height as f32 - 1.0);
// Set up a classic look-at viewport layout
let lower_left_corner = Vec3 { x: -2.0, y: -2.0, z: -1.0 };
let horizontal = Vec3 { x: 4.0, y: 0.0, z: 0.0 };
let vertical = Vec3 { x: 0.0, y: 4.0, z: 0.0 };
let origin = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
let dir = vec3_sub(
vec3_add(lower_left_corner, vec3_add(vec3_mul(horizontal, u), vec3_mul(vertical, v))),
origin
);
let ray = Ray { origin: origin, dir: dir };
let col = ray_color(&ray, &spheres);
// Precedence parser respects parenthesis grouping over multiplication
let ir = (255.999 * col.x) as i32;
let ig = (255.999 * col.y) as i32;
let ib = (255.999 * col.z) as i32;
print_number(ir);
putchar(32);
print_number(ig);
putchar(32);
print_number(ib);
putchar(10);
i = i + 1;
}
j = j - 1;
}
return 0;
}
+36 -8
View File
@@ -143,7 +143,7 @@ impl CraneliftBackend {
let mut var_map = HashMap::new();
let mut stack_slot_map = HashMap::new();
for local in &func.locals {
if local.address_taken || matches!(local.ty, Ty::Struct(_)) {
if local.address_taken || matches!(local.ty, Ty::Struct(_) | Ty::Array(_, _)) {
let bytes = Self::type_size(&local.ty, structs);
let slot = builder.create_sized_stack_slot(ir::StackSlotData::new(
ir::StackSlotKind::ExplicitSlot,
@@ -184,7 +184,7 @@ impl CraneliftBackend {
for (j, param_id) in func.params.iter().enumerate() {
let val = trans.builder.block_params(cl_block)[j];
if matches!(func.locals[param_id.0].ty, Ty::Struct(_)) {
if matches!(func.locals[param_id.0].ty, Ty::Struct(_) | Ty::Array(_, _)) {
let slot = trans.stack_slot_map[param_id];
let dest_addr = trans.builder.ins().stack_addr(types::I64, slot, 0);
let size =
@@ -223,7 +223,7 @@ impl CraneliftBackend {
Ty::F32 => types::F32,
Ty::F64 => types::F64,
Ty::Bool => types::I8, // Booleans are represented as 8-bit integers
Ty::Pointer(_) | Ty::Struct(_) => types::I64, // Structs are passed by reference implicitly
Ty::Pointer(_) | Ty::Struct(_) | Ty::Array(_, _) => types::I64, // Aggregates and pointers are passed by reference
_ => unimplemented!("Unsupported type for Cranelift lowering: {:?}", ty),
}
}
@@ -247,6 +247,10 @@ impl CraneliftBackend {
}
size
}
Ty::Array(inner, len) => {
let elem_size = Self::type_size(inner, structs);
elem_size * (*len as u32)
}
Ty::Unit => 0,
Ty::Var(_) | Ty::Function(_, _) => unimplemented!(),
}
@@ -293,11 +297,11 @@ impl<'a> FunctionTranslator<'a> {
match &stmt.kind {
StatementKind::Assign(local_id, rvalue) => {
if let Some(&slot) = self.stack_slot_map.get(local_id) {
if let Ty::Struct(name) = &self.locals[local_id.0].ty {
if matches!(self.locals[local_id.0].ty, Ty::Struct(_) | Ty::Array(_, _)) {
let dest_addr = self.builder.ins().stack_addr(types::I64, slot, 0);
if let Some(src_addr) = self.translate_rvalue(rvalue) {
let size = CraneliftBackend::type_size(
&Ty::Struct(name.clone()),
&self.locals[local_id.0].ty,
self.structs,
);
self.emit_memcpy(dest_addr, src_addr, size);
@@ -318,7 +322,7 @@ impl<'a> FunctionTranslator<'a> {
StatementKind::Store { ptr, val } => {
let ptr_val = self.translate_operand(ptr);
let rval_ty = self.get_rvalue_type(val);
if matches!(rval_ty, Ty::Struct(_)) {
if matches!(rval_ty, Ty::Struct(_) | Ty::Array(_, _)) {
if let Some(src_addr) = self.translate_rvalue(val) {
let size = CraneliftBackend::type_size(&rval_ty, self.structs);
self.emit_memcpy(ptr_val, src_addr, size);
@@ -379,7 +383,7 @@ impl<'a> FunctionTranslator<'a> {
fn translate_operand(&mut self, op: &Operand) -> ir::Value {
match op {
Operand::Copy(local_id) => {
if matches!(self.locals[local_id.0].ty, Ty::Struct(_)) {
if matches!(self.locals[local_id.0].ty, Ty::Struct(_) | Ty::Array(_, _)) {
let slot = self.stack_slot_map[local_id];
self.builder.ins().stack_addr(types::I64, slot, 0)
} else if let Some(&slot) = self.stack_slot_map.get(local_id) {
@@ -656,7 +660,7 @@ impl<'a> FunctionTranslator<'a> {
Ty::Pointer(inner) => *inner,
_ => unreachable!(),
};
if matches!(inner_ty, Ty::Struct(_)) {
if matches!(inner_ty, Ty::Struct(_) | Ty::Array(_, _)) {
Some(ptr_val)
} else {
let cl_ty = CraneliftBackend::lower_type(&inner_ty);
@@ -676,6 +680,29 @@ impl<'a> FunctionTranslator<'a> {
let offset = CraneliftBackend::field_offset(struct_name, field_name, self.structs);
Some(self.builder.ins().iadd_imm(base, offset as i64))
}
Rvalue::GetElementPtr {
base_ptr,
index,
element_ty,
} => {
let base = self.translate_operand(base_ptr);
let idx = self.translate_operand(index);
let idx_ty = self.get_operand_type(index);
let idx_64 = if idx_ty.bit_width() < 64 {
if idx_ty.is_signed() {
self.builder.ins().sextend(types::I64, idx)
} else {
self.builder.ins().uextend(types::I64, idx)
}
} else {
idx
};
let size = CraneliftBackend::type_size(element_ty, self.structs);
let offset = self.builder.ins().imul_imm(idx_64, size as i64);
Some(self.builder.ins().iadd(base, offset))
}
}
}
@@ -718,6 +745,7 @@ impl<'a> FunctionTranslator<'a> {
.clone();
Ty::Pointer(Box::new(ty))
}
Rvalue::GetElementPtr { element_ty, .. } => Ty::Pointer(Box::new(element_ty.clone())),
}
}
+8
View File
@@ -105,6 +105,7 @@ pub enum TypeKind {
Bool,
Pointer(Box<Type>),
Struct(String),
Array(Box<Type>, usize),
}
#[derive(Debug, PartialEq)]
@@ -195,6 +196,13 @@ pub enum ExprKind<P: Phase = Untyped> {
field: String,
field_span: Span,
},
Array {
elements: Vec<Expr<P>>,
},
Index {
array: Box<Expr<P>>,
index: Box<Expr<P>>,
},
}
#[derive(Debug, PartialEq)]
+62 -1
View File
@@ -377,6 +377,19 @@ impl<'src> Parser<'src> {
span,
});
}
TokenKind::LBracket => {
let lbracket = self.advance().unwrap();
let inner = self.parse_type()?;
self.expect(TokenKind::Semicolon)?;
let len_token = self.expect(TokenKind::IntegerLit)?;
let len = len_token.text.parse().unwrap();
let rbracket = self.expect(TokenKind::RBracket)?;
let span = lbracket.span.join(rbracket.span);
return Ok(Type {
kind: TypeKind::Array(Box::new(inner), len),
span,
});
}
_ => {
return Err(ParseError::new(
@@ -642,7 +655,9 @@ impl<'src> Parser<'src> {
let mut lhs = self.parse_leading_expr(allow_struct)?;
loop {
let peek_token = self.peek_no_eof()?;
let Some(peek_token) = self.peek() else {
break;
};
if peek_token.kind == TokenKind::Assign {
let left_bp = 2;
@@ -743,6 +758,28 @@ impl<'src> Parser<'src> {
continue;
}
if peek_token.kind == TokenKind::LBracket {
let left_bp = 30; // Array indexing has high precedence
if left_bp < min_bp {
break;
}
self.advance(); // consume `[`
let index = self.parse_expr()?;
let rbracket_token = self.expect(TokenKind::RBracket)?;
let span = lhs.span.join(rbracket_token.span);
lhs = Expr {
kind: ExprKind::Index {
array: Box::new(lhs),
index: Box::new(index),
},
ty: (),
span,
};
continue;
}
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
break; // Not an infix operator
};
@@ -881,6 +918,30 @@ impl<'src> Parser<'src> {
})
}
TokenKind::LBracket => {
let lbracket = self.advance().unwrap();
let mut elements = Vec::new();
if !self.is_peek(TokenKind::RBracket) {
loop {
elements.push(self.parse_expr()?);
if !self.is_peek(TokenKind::Comma) {
break;
}
self.advance(); // consume `,`
}
}
let rbracket = self.expect(TokenKind::RBracket)?;
let span = lbracket.span.join(rbracket.span);
Ok(Expr {
kind: ExprKind::Array { elements },
ty: (),
span,
})
}
kind if let Some((op, r_bp)) = self.prefix_operator(kind) => {
let op_token = self.advance().unwrap();
let rhs = self.parse_expr_bp(r_bp, allow_struct)?;
+81
View File
@@ -43,6 +43,7 @@ pub enum Ty {
Function(Vec<Ty>, Box<Ty>),
Pointer(Box<Ty>),
Struct(String),
Array(Box<Ty>, usize),
}
impl Ty {
@@ -110,6 +111,7 @@ impl From<&TypeKind> for Ty {
TypeKind::Bool => Ty::Bool,
TypeKind::Pointer(inner) => Ty::Pointer(Box::new(Ty::from(&inner.kind))),
TypeKind::Struct(name) => Ty::Struct(name.clone()),
TypeKind::Array(inner, len) => Ty::Array(Box::new(Ty::from(&inner.kind)), *len),
}
}
}
@@ -123,6 +125,7 @@ impl TypedExpr {
op: UnaryOp::Deref, ..
} => true,
TypedExprKind::FieldAccess { expr, .. } => expr.is_lvalue(),
TypedExprKind::Index { .. } => true,
_ => false,
}
}
@@ -213,6 +216,7 @@ impl Sema {
}
Ty::Pointer(inner) => Ty::Pointer(Box::new(self.apply_subst(inner))),
Ty::Array(inner, len) => Ty::Array(Box::new(self.apply_subst(inner)), *len),
t => t.clone(),
}
@@ -228,6 +232,7 @@ impl Sema {
params.iter().any(|p| self.occurs_check(v, p)) || self.occurs_check(v, &ret)
}
Ty::Pointer(inner) => self.occurs_check(v, &inner),
Ty::Array(inner, _) => self.occurs_check(v, &inner),
_ => false,
}
}
@@ -264,6 +269,7 @@ impl Sema {
self.unify(&r1, &r2)
}
(Ty::Pointer(p1), Ty::Pointer(p2)) => self.unify(&p1, &p2),
(Ty::Array(a1, l1), Ty::Array(a2, l2)) if l1 == l2 => self.unify(&a1, &a2),
(a, b) => Err(format!("type mismatch: expected {:?}, found {:?}", a, b)),
}
}
@@ -895,6 +901,71 @@ impl Sema {
span: expr.span,
}
}
ExprKind::Array { elements } => {
let mut typed_elements = Vec::new();
let element_ty = self.new_var();
for el in elements {
let typed_el = self.analyze_expr(el);
if let Err(e) = self.unify(&element_ty, &typed_el.ty) {
self.errors.push(SemanticError::new(e, el.span));
}
typed_elements.push(typed_el);
}
let len = elements.len();
TypedExpr {
kind: TypedExprKind::Array {
elements: typed_elements,
},
ty: Ty::Array(Box::new(element_ty), len),
span: expr.span,
}
}
ExprKind::Index { array, index } => {
let typed_array = self.analyze_expr(array);
let typed_index = self.analyze_expr(index);
// Enforce that the array index evaluates to an integer
let default_index_ty = self.new_var();
self.deferred_int_literals
.push((index.span, default_index_ty.clone()));
if let Err(e) = self.unify(&typed_index.ty, &default_index_ty) {
self.errors.push(SemanticError::new(e, index.span));
}
let result_ty = self.new_var();
let array_ty_resolved = self.apply_subst(&typed_array.ty);
match array_ty_resolved {
Ty::Array(inner_ty, _) => {
if let Err(e) = self.unify(&result_ty, &inner_ty) {
self.errors.push(SemanticError::new(e, expr.span));
}
}
Ty::Pointer(inner_ty) => {
if let Err(e) = self.unify(&result_ty, &inner_ty) {
self.errors.push(SemanticError::new(e, expr.span));
}
}
Ty::Var(_) => self.errors.push(SemanticError::new(
"type of expression must be known to index",
array.span,
)),
_ => self.errors.push(SemanticError::new(
"cannot index into a non-array, non-pointer type",
array.span,
)),
}
TypedExpr {
kind: TypedExprKind::Index {
array: Box::new(typed_array),
index: Box::new(typed_index),
},
ty: result_ty,
span: expr.span,
}
}
ExprKind::FieldAccess {
expr: inner_expr,
field,
@@ -1108,6 +1179,16 @@ impl Sema {
field,
field_span,
},
TypedExprKind::Array { elements } => TypedExprKind::Array {
elements: elements
.into_iter()
.map(|e| self.apply_subst_expr(e))
.collect(),
},
TypedExprKind::Index { array, index } => TypedExprKind::Index {
array: Box::new(self.apply_subst_expr(*array)),
index: Box::new(self.apply_subst_expr(*index)),
},
};
TypedExpr { kind, ty, span }
+74 -2
View File
@@ -36,7 +36,7 @@ impl MirBuilder {
return_type,
body,
} => {
let is_sret = matches!(return_type, Ty::Struct(_));
let is_sret = matches!(return_type, Ty::Struct(_) | Ty::Array(_, _));
let mir_return_type = if is_sret {
Ty::Unit
} else {
@@ -494,7 +494,7 @@ impl FuncBuilder {
};
let mut arg_ops = Vec::new();
let is_sret = matches!(expr.ty, Ty::Struct(_));
let is_sret = matches!(expr.ty, Ty::Struct(_) | Ty::Array(_, _));
let mut sret_temp = None;
// Implement implicit sret struct passing to the callee
@@ -589,6 +589,57 @@ impl FuncBuilder {
});
Operand::Copy(temp)
}
TypedExprKind::Array { elements } => {
let local_id = self.new_temp(expr.ty.clone());
self.locals[local_id.0].address_taken = true;
let base_ptr_temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
self.emit_stmt(Statement {
kind: StatementKind::Assign(base_ptr_temp, Rvalue::AddressOf(local_id)),
span: expr.span,
});
let element_ty = match &expr.ty {
Ty::Array(inner, _) => *inner.clone(),
_ => unreachable!(),
};
for (i, el) in elements.iter().enumerate() {
let val_op = self.lower_expr(el);
let index_op = Operand::Constant(ConstantValue::Integer(i as u64, Ty::I32));
let element_ptr_temp = self.new_temp(Ty::Pointer(Box::new(element_ty.clone())));
self.emit_stmt(Statement {
kind: StatementKind::Assign(
element_ptr_temp,
Rvalue::GetElementPtr {
base_ptr: Operand::Copy(base_ptr_temp),
index: index_op,
element_ty: element_ty.clone(),
},
),
span: el.span,
});
self.emit_stmt(Statement {
kind: StatementKind::Store {
ptr: Operand::Copy(element_ptr_temp),
val: Rvalue::Use(val_op),
},
span: el.span,
});
}
Operand::Copy(local_id)
}
TypedExprKind::Index { .. } => {
let ptr_op = self.lower_address_of(expr);
let temp = self.new_temp(expr.ty.clone());
self.emit_stmt(Statement {
kind: StatementKind::Assign(temp, Rvalue::ReadPointer(ptr_op)),
span: expr.span,
});
Operand::Copy(temp)
}
}
}
@@ -634,6 +685,27 @@ impl FuncBuilder {
});
Operand::Copy(temp)
}
TypedExprKind::Index { array, index } => {
let base_ptr = match &array.ty {
Ty::Array(_, _) => self.lower_address_of(array),
Ty::Pointer(_) => self.lower_expr(array),
_ => unreachable!(),
};
let index_op = self.lower_expr(index);
let temp = self.new_temp(Ty::Pointer(Box::new(expr.ty.clone())));
self.emit_stmt(Statement {
kind: StatementKind::Assign(
temp,
Rvalue::GetElementPtr {
base_ptr,
index: index_op,
element_ty: expr.ty.clone(),
},
),
span: expr.span,
});
Operand::Copy(temp)
}
_ => {
let val_op = self.lower_expr(expr);
if let Operand::Copy(id) = val_op {
+6
View File
@@ -95,6 +95,12 @@ fn propagate_rvalue(rvalue: &mut Rvalue, known_constants: &HashMap<LocalId, Cons
Rvalue::AddressOf(_) => {}
Rvalue::ReadPointer(op) => propagate_operand(op, known_constants),
Rvalue::GetFieldPtr { base_ptr, .. } => propagate_operand(base_ptr, known_constants),
Rvalue::GetElementPtr {
base_ptr, index, ..
} => {
propagate_operand(base_ptr, known_constants);
propagate_operand(index, known_constants);
}
}
}
+5
View File
@@ -86,6 +86,11 @@ pub enum Rvalue {
struct_name: String,
field_name: String,
},
GetElementPtr {
base_ptr: Operand,
index: Operand,
element_ty: Ty,
},
}
/// An atomic value used as inputs to instructions.
+16
View File
@@ -0,0 +1,16 @@
[code]
foreign fn putchar(c: i32) -> i32;
fn main() -> i32 {
let arr: [i32; 3] = [65, 66, 67];
putchar(arr[0]);
putchar(arr[1]);
putchar(arr[2]);
return 0;
}
[expected_return_code]
0
[expected_output]
ABC
+15
View File
@@ -0,0 +1,15 @@
[code]
fn swap(a: *i32, b: *i32) {
let temp = *a;
*a = *b;
*b = temp;
}
fn main() -> i32 {
let a = [10, 20];
swap(&a[0], &a[1]);
return a[0] - a[1]; // 20 - 10 = 10
}
[expected_return_code]
10