feat: add foreign function support
This commit is contained in:
@@ -49,9 +49,9 @@ A Rust-flavored, C-targeting language - built pipeline-first.
|
|||||||
|
|
||||||
To successfully write a simple ray tracer, we need continuous math, data structures, and I/O. The following path establishes these prerequisites:
|
To successfully write a simple ray tracer, we need continuous math, data structures, and I/O. The following path establishes these prerequisites:
|
||||||
|
|
||||||
- [ ] **Floating-Point Support:** Add `f32`/`f64` types, decimal literals, and Cranelift lowering for `fadd`, `fmul`, etc.
|
- [x] **Floating-Point Support:** Add `f32`/`f64` types, decimal literals, and Cranelift lowering for `fadd`, `fmul`, etc.
|
||||||
- [ ] **FFI & Interop:** Implement `extern fn` declarations to bind C standard library functions (like `putchar` or `printf`) for `.ppm` image output.
|
- [x] **FFI & Interop:** Implement `extern fn` declarations to bind C standard library functions (like `putchar` or `printf`) for `.ppm` image output.
|
||||||
- [ ] **Type Casting:** Add the `as` operator to convert floating-point color bounds `[0.0, 1.0]` into integer byte formats `[0, 255]`.
|
- [x] **Type Casting:** Add the `as` operator to convert floating-point color bounds `[0.0, 1.0]` into integer byte formats `[0, 255]`.
|
||||||
- [ ] **Pointers:** Add pointer types (`*T`), address-of (`&`), and dereference (`*`) operators.
|
- [ ] **Pointers:** Add pointer types (`*T`), address-of (`&`), and dereference (`*`) operators.
|
||||||
- [ ] **Structs:** Add `struct` definitions, initializers, and field access (`ray.origin.x`) to represent 3D vectors, rays, and spheres.
|
- [ ] **Structs:** Add `struct` definitions, initializers, and field access (`ray.origin.x`) to represent 3D vectors, rays, and spheres.
|
||||||
- [ ] **Arrays:** Add fixed-size arrays (`[T; N]`) or heap allocations for the scene and framebuffers.
|
- [ ] **Arrays:** Add fixed-size arrays (`[T; N]`) or heap allocations for the scene and framebuffers.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ def run_test(test_file: Path, compiler_bin: Path):
|
|||||||
expected_code = 0
|
expected_code = 0
|
||||||
code_lines = []
|
code_lines = []
|
||||||
harness_lines = None
|
harness_lines = None
|
||||||
|
expected_output_lines = None
|
||||||
current_section = None
|
current_section = None
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@@ -20,6 +21,8 @@ def run_test(test_file: Path, compiler_bin: Path):
|
|||||||
current_section = trimmed[1:-1]
|
current_section = trimmed[1:-1]
|
||||||
if current_section == "harness":
|
if current_section == "harness":
|
||||||
harness_lines = []
|
harness_lines = []
|
||||||
|
elif current_section == "expected_output":
|
||||||
|
expected_output_lines = []
|
||||||
elif current_section:
|
elif current_section:
|
||||||
if current_section == "expected_return_code":
|
if current_section == "expected_return_code":
|
||||||
if trimmed:
|
if trimmed:
|
||||||
@@ -28,6 +31,8 @@ def run_test(test_file: Path, compiler_bin: Path):
|
|||||||
code_lines.append(line)
|
code_lines.append(line)
|
||||||
elif current_section == "harness":
|
elif current_section == "harness":
|
||||||
harness_lines.append(line)
|
harness_lines.append(line)
|
||||||
|
elif current_section == "expected_output":
|
||||||
|
expected_output_lines.append(line)
|
||||||
|
|
||||||
code = "".join(code_lines)
|
code = "".join(code_lines)
|
||||||
harness = "".join(harness_lines) if harness_lines is not None else None
|
harness = "".join(harness_lines) if harness_lines is not None else None
|
||||||
@@ -75,6 +80,13 @@ def run_test(test_file: Path, compiler_bin: Path):
|
|||||||
if exec_res.returncode != expected_code:
|
if exec_res.returncode != expected_code:
|
||||||
raise Exception(f"Expected return code {expected_code}, but got {exec_res.returncode}")
|
raise Exception(f"Expected return code {expected_code}, but got {exec_res.returncode}")
|
||||||
|
|
||||||
|
# 4. Check the standard output if expected_output is provided
|
||||||
|
if expected_output_lines is not None:
|
||||||
|
expected_out = "".join(expected_output_lines).strip()
|
||||||
|
actual_out = exec_res.stdout.strip()
|
||||||
|
if expected_out != actual_out:
|
||||||
|
raise Exception(f"Expected output:\n{expected_out}\n\nActual output:\n{actual_out}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
GREEN = '\033[92m'
|
GREEN = '\033[92m'
|
||||||
RED = '\033[91m'
|
RED = '\033[91m'
|
||||||
|
|||||||
+85
-25
@@ -22,6 +22,7 @@ pub struct CraneliftBackend {
|
|||||||
ctx: Context,
|
ctx: Context,
|
||||||
builder_context: FunctionBuilderContext,
|
builder_context: FunctionBuilderContext,
|
||||||
module: ObjectModule,
|
module: ObjectModule,
|
||||||
|
func_ids: HashMap<String, cranelift_module::FuncId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CraneliftBackend {
|
impl CraneliftBackend {
|
||||||
@@ -46,6 +47,7 @@ impl CraneliftBackend {
|
|||||||
ctx: Context::new(),
|
ctx: Context::new(),
|
||||||
builder_context: FunctionBuilderContext::new(),
|
builder_context: FunctionBuilderContext::new(),
|
||||||
module: ObjectModule::new(builder),
|
module: ObjectModule::new(builder),
|
||||||
|
func_ids: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +57,40 @@ impl CraneliftBackend {
|
|||||||
pub fn compile_module(mut self, module: &MirModule) -> (String, Vec<u8>) {
|
pub fn compile_module(mut self, module: &MirModule) -> (String, Vec<u8>) {
|
||||||
let mut ir_output = String::new();
|
let mut ir_output = String::new();
|
||||||
|
|
||||||
|
for ext_func in &module.extern_functions {
|
||||||
|
let mut sig = self.module.make_signature();
|
||||||
|
for param_ty in &ext_func.params {
|
||||||
|
sig.params.push(AbiParam::new(Self::lower_type(param_ty)));
|
||||||
|
}
|
||||||
|
if ext_func.return_type != Ty::Unit {
|
||||||
|
sig.returns
|
||||||
|
.push(AbiParam::new(Self::lower_type(&ext_func.return_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let func_id = self
|
||||||
|
.module
|
||||||
|
.declare_function(&ext_func.name, Linkage::Import, &sig)
|
||||||
|
.unwrap();
|
||||||
|
self.func_ids.insert(ext_func.name.clone(), func_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for func in &module.functions {
|
||||||
|
let mut sig = self.module.make_signature();
|
||||||
|
for param_id in &func.params {
|
||||||
|
let param_ty = &func.locals[param_id.0].ty;
|
||||||
|
sig.params.push(AbiParam::new(Self::lower_type(param_ty)));
|
||||||
|
}
|
||||||
|
if func.return_type != Ty::Unit {
|
||||||
|
sig.returns
|
||||||
|
.push(AbiParam::new(Self::lower_type(&func.return_type)));
|
||||||
|
}
|
||||||
|
let func_id = self
|
||||||
|
.module
|
||||||
|
.declare_function(&func.name, Linkage::Export, &sig)
|
||||||
|
.unwrap();
|
||||||
|
self.func_ids.insert(func.name.clone(), func_id);
|
||||||
|
}
|
||||||
|
|
||||||
for func in &module.functions {
|
for func in &module.functions {
|
||||||
self.compile_function(func);
|
self.compile_function(func);
|
||||||
|
|
||||||
@@ -67,12 +103,8 @@ impl CraneliftBackend {
|
|||||||
ir_output.push_str(&format!("; Function: {}\n{}", func.name, self.ctx.func));
|
ir_output.push_str(&format!("; Function: {}\n{}", func.name, self.ctx.func));
|
||||||
ir_output.push('\n');
|
ir_output.push('\n');
|
||||||
|
|
||||||
let func_id = self
|
let id = self.func_ids[&func.name];
|
||||||
.module
|
self.module.define_function(id, &mut self.ctx).unwrap();
|
||||||
.declare_function(&func.name, Linkage::Export, &self.ctx.func.signature)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.module.define_function(func_id, &mut self.ctx).unwrap();
|
|
||||||
self.module.clear_context(&mut self.ctx);
|
self.module.clear_context(&mut self.ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +147,8 @@ impl CraneliftBackend {
|
|||||||
var_map,
|
var_map,
|
||||||
block_map,
|
block_map,
|
||||||
locals: &func.locals,
|
locals: &func.locals,
|
||||||
|
module: &mut self.module,
|
||||||
|
func_ids: &self.func_ids,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(first_block) = func.blocks.first() {
|
if let Some(first_block) = func.blocks.first() {
|
||||||
@@ -173,6 +207,8 @@ struct FunctionTranslator<'a> {
|
|||||||
var_map: HashMap<LocalId, Variable>,
|
var_map: HashMap<LocalId, Variable>,
|
||||||
block_map: HashMap<BlockId, ir::Block>,
|
block_map: HashMap<BlockId, ir::Block>,
|
||||||
locals: &'a [LocalDecl],
|
locals: &'a [LocalDecl],
|
||||||
|
module: &'a mut ObjectModule,
|
||||||
|
func_ids: &'a HashMap<String, cranelift_module::FuncId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FunctionTranslator<'a> {
|
impl<'a> FunctionTranslator<'a> {
|
||||||
@@ -180,8 +216,13 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
match &stmt.kind {
|
match &stmt.kind {
|
||||||
StatementKind::Assign(local_id, rvalue) => {
|
StatementKind::Assign(local_id, rvalue) => {
|
||||||
let val = self.translate_rvalue(rvalue);
|
let val = self.translate_rvalue(rvalue);
|
||||||
|
if let Some(v) = val {
|
||||||
let var = self.var_map[local_id];
|
let var = self.var_map[local_id];
|
||||||
self.builder.def_var(var, val);
|
self.builder.def_var(var, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatementKind::SideEffect(rvalue) => {
|
||||||
|
self.translate_rvalue(rvalue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,9 +291,9 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_rvalue(&mut self, rvalue: &Rvalue) -> ir::Value {
|
fn translate_rvalue(&mut self, rvalue: &Rvalue) -> Option<ir::Value> {
|
||||||
match rvalue {
|
match rvalue {
|
||||||
Rvalue::Use(op) => self.translate_operand(op),
|
Rvalue::Use(op) => Some(self.translate_operand(op)),
|
||||||
Rvalue::UnaryOp(op, inner) => {
|
Rvalue::UnaryOp(op, inner) => {
|
||||||
let inner_val = self.translate_operand(inner);
|
let inner_val = self.translate_operand(inner);
|
||||||
let ty = self.get_operand_type(inner);
|
let ty = self.get_operand_type(inner);
|
||||||
@@ -260,17 +301,19 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
match op {
|
match op {
|
||||||
UnaryOp::Neg => {
|
UnaryOp::Neg => {
|
||||||
if ty.is_float() {
|
if ty.is_float() {
|
||||||
self.builder.ins().fneg(inner_val)
|
Some(self.builder.ins().fneg(inner_val))
|
||||||
} else {
|
} else {
|
||||||
self.builder.ins().ineg(inner_val)
|
Some(self.builder.ins().ineg(inner_val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnaryOp::Not => {
|
UnaryOp::Not => {
|
||||||
let cl_ty = CraneliftBackend::lower_type(&ty);
|
let cl_ty = CraneliftBackend::lower_type(&ty);
|
||||||
let zero = self.builder.ins().iconst(cl_ty, 0);
|
let zero = self.builder.ins().iconst(cl_ty, 0);
|
||||||
|
Some(
|
||||||
self.builder
|
self.builder
|
||||||
.ins()
|
.ins()
|
||||||
.icmp(ir::condcodes::IntCC::Equal, inner_val, zero)
|
.icmp(ir::condcodes::IntCC::Equal, inner_val, zero),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +325,7 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
let is_signed = matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64);
|
let is_signed = matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64);
|
||||||
let is_float = ty.is_float();
|
let is_float = ty.is_float();
|
||||||
|
|
||||||
match op {
|
Some(match op {
|
||||||
BinaryOp::Add => {
|
BinaryOp::Add => {
|
||||||
if is_float {
|
if is_float {
|
||||||
self.builder.ins().fadd(lhs_val, rhs_val)
|
self.builder.ins().fadd(lhs_val, rhs_val)
|
||||||
@@ -412,7 +455,7 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
self.builder.ins().icmp(cc, lhs_val, rhs_val)
|
self.builder.ins().icmp(cc, lhs_val, rhs_val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
Rvalue::Cast(to_ty, inner) => {
|
Rvalue::Cast(to_ty, inner) => {
|
||||||
let inner_val = self.translate_operand(inner);
|
let inner_val = self.translate_operand(inner);
|
||||||
@@ -420,7 +463,7 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
let cl_to_ty = CraneliftBackend::lower_type(to_ty);
|
let cl_to_ty = CraneliftBackend::lower_type(to_ty);
|
||||||
|
|
||||||
if from_ty == *to_ty {
|
if from_ty == *to_ty {
|
||||||
inner_val
|
Some(inner_val)
|
||||||
} else {
|
} else {
|
||||||
match (from_ty.is_float(), to_ty.is_float()) {
|
match (from_ty.is_float(), to_ty.is_float()) {
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
@@ -430,43 +473,60 @@ impl<'a> FunctionTranslator<'a> {
|
|||||||
|
|
||||||
if to_width > from_width {
|
if to_width > from_width {
|
||||||
if from_ty.is_signed() {
|
if from_ty.is_signed() {
|
||||||
self.builder.ins().sextend(cl_to_ty, inner_val)
|
Some(self.builder.ins().sextend(cl_to_ty, inner_val))
|
||||||
} else {
|
} else {
|
||||||
self.builder.ins().uextend(cl_to_ty, inner_val)
|
Some(self.builder.ins().uextend(cl_to_ty, inner_val))
|
||||||
}
|
}
|
||||||
} else if to_width < from_width {
|
} else if to_width < from_width {
|
||||||
self.builder.ins().ireduce(cl_to_ty, inner_val)
|
Some(self.builder.ins().ireduce(cl_to_ty, inner_val))
|
||||||
} else {
|
} else {
|
||||||
inner_val // e.g. bitcasting between same-sized int and uint
|
Some(inner_val) // e.g. bitcasting between same-sized int and uint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
// Float <-> Float
|
// Float <-> Float
|
||||||
if to_ty.bit_width() > from_ty.bit_width() {
|
if to_ty.bit_width() > from_ty.bit_width() {
|
||||||
self.builder.ins().fpromote(cl_to_ty, inner_val)
|
Some(self.builder.ins().fpromote(cl_to_ty, inner_val))
|
||||||
} else {
|
} else {
|
||||||
self.builder.ins().fdemote(cl_to_ty, inner_val)
|
Some(self.builder.ins().fdemote(cl_to_ty, inner_val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(false, true) => {
|
(false, true) => {
|
||||||
// Integer -> Float
|
// Integer -> Float
|
||||||
if from_ty.is_signed() {
|
if from_ty.is_signed() {
|
||||||
self.builder.ins().fcvt_from_sint(cl_to_ty, inner_val)
|
Some(self.builder.ins().fcvt_from_sint(cl_to_ty, inner_val))
|
||||||
} else {
|
} else {
|
||||||
self.builder.ins().fcvt_from_uint(cl_to_ty, inner_val)
|
Some(self.builder.ins().fcvt_from_uint(cl_to_ty, inner_val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
// Float -> Integer
|
// Float -> Integer
|
||||||
if to_ty.is_signed() {
|
if to_ty.is_signed() {
|
||||||
self.builder.ins().fcvt_to_sint_sat(cl_to_ty, inner_val)
|
Some(self.builder.ins().fcvt_to_sint_sat(cl_to_ty, inner_val))
|
||||||
} else {
|
} else {
|
||||||
self.builder.ins().fcvt_to_uint_sat(cl_to_ty, inner_val)
|
Some(self.builder.ins().fcvt_to_uint_sat(cl_to_ty, inner_val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Rvalue::Call(name, args, ret_ty) => {
|
||||||
|
let func_id = self.func_ids[name];
|
||||||
|
let local_callee = self.module.declare_func_in_func(func_id, self.builder.func);
|
||||||
|
|
||||||
|
let mut arg_vals = Vec::new();
|
||||||
|
for arg in args {
|
||||||
|
arg_vals.push(self.translate_operand(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
let call_inst = self.builder.ins().call(local_callee, &arg_vals);
|
||||||
|
|
||||||
|
if *ret_ty == Ty::Unit {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.builder.inst_results(call_inst)[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ pub enum DeclKind<P: Phase = Untyped> {
|
|||||||
return_type: P::ReturnType,
|
return_type: P::ReturnType,
|
||||||
body: Stmt<P>,
|
body: Stmt<P>,
|
||||||
},
|
},
|
||||||
|
ForeignFunction {
|
||||||
|
name: String,
|
||||||
|
name_span: Span,
|
||||||
|
params: Vec<P::ParamType>,
|
||||||
|
return_type: P::ReturnType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -161,6 +167,10 @@ pub enum ExprKind<P: Phase = Untyped> {
|
|||||||
expr: Box<Expr<P>>,
|
expr: Box<Expr<P>>,
|
||||||
ty: P::CastType,
|
ty: P::CastType,
|
||||||
},
|
},
|
||||||
|
Call {
|
||||||
|
callee: Box<Expr<P>>,
|
||||||
|
args: Vec<Expr<P>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ impl<'src> Lexer<'src> {
|
|||||||
|
|
||||||
match &self.source[start..self.cursor] {
|
match &self.source[start..self.cursor] {
|
||||||
"fn" => TokenKind::Fn,
|
"fn" => TokenKind::Fn,
|
||||||
|
"foreign" => TokenKind::Foreign,
|
||||||
"if" => TokenKind::If,
|
"if" => TokenKind::If,
|
||||||
"as" => TokenKind::As,
|
"as" => TokenKind::As,
|
||||||
"else" => TokenKind::Else,
|
"else" => TokenKind::Else,
|
||||||
@@ -249,7 +250,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn identifiers() {
|
fn identifiers() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tokenize("HELLO _hello _0@ fn if else return let while break continue as"),
|
tokenize("HELLO _hello _0@ fn if else return let while break continue as foreign"),
|
||||||
vec![
|
vec![
|
||||||
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
||||||
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
||||||
@@ -264,6 +265,7 @@ mod test {
|
|||||||
Token::new(TokenKind::Break, "break", Span::new(45, 50)),
|
Token::new(TokenKind::Break, "break", Span::new(45, 50)),
|
||||||
Token::new(TokenKind::Continue, "continue", Span::new(51, 59)),
|
Token::new(TokenKind::Continue, "continue", Span::new(51, 59)),
|
||||||
Token::new(TokenKind::As, "as", Span::new(60, 62)),
|
Token::new(TokenKind::As, "as", Span::new(60, 62)),
|
||||||
|
Token::new(TokenKind::Foreign, "foreign", Span::new(63, 70)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ impl<'src> Parser<'src> {
|
|||||||
|
|
||||||
match peek_token.kind {
|
match peek_token.kind {
|
||||||
TokenKind::Fn => self.parse_function_decl(),
|
TokenKind::Fn => self.parse_function_decl(),
|
||||||
|
TokenKind::Foreign => self.parse_foreign_function_decl(),
|
||||||
|
|
||||||
_ => Err(ParseError::new(
|
_ => Err(ParseError::new(
|
||||||
format!(
|
format!(
|
||||||
@@ -185,6 +186,45 @@ impl<'src> Parser<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a foreign function declaration.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// foreign_function_decl = "foreign" "fn" IDENTIFIER "(" function_params ")" [ "->" type ] ";" ;
|
||||||
|
/// ```
|
||||||
|
fn parse_foreign_function_decl(&mut self) -> ParseResult<Decl> {
|
||||||
|
let foreign_token = self.expect(TokenKind::Foreign)?;
|
||||||
|
self.expect(TokenKind::Fn)?;
|
||||||
|
|
||||||
|
let (name, name_span) = {
|
||||||
|
let ident_token = self.expect(TokenKind::Identifier)?;
|
||||||
|
(ident_token.text.to_string(), ident_token.span)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.expect(TokenKind::LParen)?;
|
||||||
|
let params = self.parse_function_params()?;
|
||||||
|
self.expect(TokenKind::RParen)?;
|
||||||
|
|
||||||
|
let return_type = if self.is_peek(TokenKind::Arrow) {
|
||||||
|
self.advance();
|
||||||
|
Some(self.parse_type()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let semi_token = self.expect(TokenKind::Semicolon)?;
|
||||||
|
let span = foreign_token.span.join(semi_token.span);
|
||||||
|
|
||||||
|
Ok(Decl {
|
||||||
|
kind: DeclKind::ForeignFunction {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
params,
|
||||||
|
return_type,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses the function parameter list.
|
/// Parses the function parameter list.
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
@@ -581,6 +621,38 @@ impl<'src> Parser<'src> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if peek_token.kind == TokenKind::LParen {
|
||||||
|
let left_bp = 30; // Function calls have very high precedence
|
||||||
|
if left_bp < min_bp {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.advance(); // consume `(`
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
if !self.is_peek(TokenKind::RParen) {
|
||||||
|
loop {
|
||||||
|
args.push(self.parse_expr()?);
|
||||||
|
if !self.is_peek(TokenKind::Comma) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.advance(); // consume `,`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rparen_token = self.expect(TokenKind::RParen)?;
|
||||||
|
let span = lhs.span.join(rparen_token.span);
|
||||||
|
|
||||||
|
lhs = Expr {
|
||||||
|
kind: ExprKind::Call {
|
||||||
|
callee: Box::new(lhs),
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
ty: (),
|
||||||
|
span,
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
|
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
|
||||||
break; // Not an infix operator
|
break; // Not an infix operator
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -251,6 +251,12 @@ impl Sema {
|
|||||||
params,
|
params,
|
||||||
return_type,
|
return_type,
|
||||||
..
|
..
|
||||||
|
}
|
||||||
|
| DeclKind::ForeignFunction {
|
||||||
|
name,
|
||||||
|
params,
|
||||||
|
return_type,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let param_tys: Vec<Ty> = params.iter().map(|p| Ty::from(&p.ty.kind)).collect();
|
let param_tys: Vec<Ty> = params.iter().map(|p| Ty::from(&p.ty.kind)).collect();
|
||||||
let ret_ty = return_type
|
let ret_ty = return_type
|
||||||
@@ -321,6 +327,31 @@ impl Sema {
|
|||||||
span: decl.span,
|
span: decl.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DeclKind::ForeignFunction {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
params,
|
||||||
|
return_type,
|
||||||
|
} => {
|
||||||
|
let typed_params = params
|
||||||
|
.iter()
|
||||||
|
.map(|p| (p.name.clone(), Ty::from(&p.ty.kind)))
|
||||||
|
.collect();
|
||||||
|
let expected_ret_ty = return_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| Ty::from(&t.kind))
|
||||||
|
.unwrap_or(Ty::Unit);
|
||||||
|
|
||||||
|
TypedDecl {
|
||||||
|
kind: TypedDeclKind::ForeignFunction {
|
||||||
|
name: name.clone(),
|
||||||
|
name_span: *name_span,
|
||||||
|
params: typed_params,
|
||||||
|
return_type: expected_ret_ty,
|
||||||
|
},
|
||||||
|
span: decl.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,6 +694,33 @@ impl Sema {
|
|||||||
span: expr.span,
|
span: expr.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ExprKind::Call { callee, args } => {
|
||||||
|
let typed_callee = self.analyze_expr(callee);
|
||||||
|
let mut typed_args = Vec::new();
|
||||||
|
let mut arg_tys = Vec::new();
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
let typed_arg = self.analyze_expr(arg);
|
||||||
|
arg_tys.push(typed_arg.ty.clone());
|
||||||
|
typed_args.push(typed_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret_ty = self.new_var();
|
||||||
|
let expected_callee_ty = Ty::Function(arg_tys, Box::new(ret_ty.clone()));
|
||||||
|
|
||||||
|
if let Err(e) = self.unify(&typed_callee.ty, &expected_callee_ty) {
|
||||||
|
self.errors.push(SemanticError::new(e, callee.span));
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedExpr {
|
||||||
|
kind: TypedExprKind::Call {
|
||||||
|
callee: Box::new(typed_callee),
|
||||||
|
args: typed_args,
|
||||||
|
},
|
||||||
|
ty: ret_ty,
|
||||||
|
span: expr.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,6 +748,20 @@ impl Sema {
|
|||||||
body: self.apply_subst_stmt(body),
|
body: self.apply_subst_stmt(body),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TypedDeclKind::ForeignFunction {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
params,
|
||||||
|
return_type,
|
||||||
|
} => TypedDeclKind::ForeignFunction {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
params: params
|
||||||
|
.into_iter()
|
||||||
|
.map(|(n, ty)| (n, self.apply_subst(&ty)))
|
||||||
|
.collect(),
|
||||||
|
return_type: self.apply_subst(&return_type),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypedDecl { kind, span }
|
TypedDecl { kind, span }
|
||||||
@@ -775,6 +847,10 @@ impl Sema {
|
|||||||
expr: Box::new(self.apply_subst_expr(*expr)),
|
expr: Box::new(self.apply_subst_expr(*expr)),
|
||||||
ty: self.apply_subst(&ty),
|
ty: self.apply_subst(&ty),
|
||||||
},
|
},
|
||||||
|
TypedExprKind::Call { callee, args } => TypedExprKind::Call {
|
||||||
|
callee: Box::new(self.apply_subst_expr(*callee)),
|
||||||
|
args: args.into_iter().map(|a| self.apply_subst_expr(a)).collect(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypedExpr { kind, ty, span }
|
TypedExpr { kind, ty, span }
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ pub enum TokenKind {
|
|||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
Fn,
|
Fn,
|
||||||
|
Foreign,
|
||||||
If,
|
If,
|
||||||
As,
|
As,
|
||||||
Else,
|
Else,
|
||||||
@@ -121,6 +122,7 @@ impl Display for TokenKind {
|
|||||||
TokenKind::BooleanLit => "a boolean",
|
TokenKind::BooleanLit => "a boolean",
|
||||||
TokenKind::FloatLit => "a float",
|
TokenKind::FloatLit => "a float",
|
||||||
TokenKind::Fn => "`fn`",
|
TokenKind::Fn => "`fn`",
|
||||||
|
TokenKind::Foreign => "`foreign`",
|
||||||
TokenKind::As => "`as`",
|
TokenKind::As => "`as`",
|
||||||
TokenKind::If => "`if`",
|
TokenKind::If => "`if`",
|
||||||
TokenKind::Else => "`else`",
|
TokenKind::Else => "`else`",
|
||||||
|
|||||||
+45
-1
@@ -10,6 +10,7 @@ pub struct MirBuilder;
|
|||||||
impl MirBuilder {
|
impl MirBuilder {
|
||||||
/// Builds a `MirModule` from a `TypedModule`.
|
/// Builds a `MirModule` from a `TypedModule`.
|
||||||
pub fn build(module: &TypedModule) -> MirModule {
|
pub fn build(module: &TypedModule) -> MirModule {
|
||||||
|
let mut extern_functions = Vec::new();
|
||||||
let mut functions = Vec::new();
|
let mut functions = Vec::new();
|
||||||
|
|
||||||
for decl in &module.decls {
|
for decl in &module.decls {
|
||||||
@@ -53,10 +54,25 @@ impl MirBuilder {
|
|||||||
|
|
||||||
functions.push(builder.finish());
|
functions.push(builder.finish());
|
||||||
}
|
}
|
||||||
|
TypedDeclKind::ForeignFunction {
|
||||||
|
name,
|
||||||
|
params,
|
||||||
|
return_type,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
extern_functions.push(MirExternFunction {
|
||||||
|
name: name.clone(),
|
||||||
|
params: params.iter().map(|(_, ty)| ty.clone()).collect(),
|
||||||
|
return_type: return_type.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MirModule { functions }
|
MirModule {
|
||||||
|
extern_functions,
|
||||||
|
functions,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,6 +415,34 @@ impl FuncBuilder {
|
|||||||
|
|
||||||
Operand::Copy(temp)
|
Operand::Copy(temp)
|
||||||
}
|
}
|
||||||
|
TypedExprKind::Call { callee, args } => {
|
||||||
|
let callee_name = match &callee.kind {
|
||||||
|
TypedExprKind::Identifier { name } => name.clone(),
|
||||||
|
_ => unimplemented!("indirect function calls are not yet supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arg_ops = Vec::new();
|
||||||
|
for arg in args {
|
||||||
|
arg_ops.push(self.lower_expr(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
let rval = Rvalue::Call(callee_name, arg_ops, expr.ty.clone());
|
||||||
|
|
||||||
|
if expr.ty == Ty::Unit {
|
||||||
|
self.emit_stmt(Statement {
|
||||||
|
kind: StatementKind::SideEffect(rval),
|
||||||
|
span: expr.span,
|
||||||
|
});
|
||||||
|
Operand::Constant(ConstantValue::Boolean(false)) // Dummy value for Unit assignments
|
||||||
|
} else {
|
||||||
|
let temp = self.new_temp(expr.ty.clone());
|
||||||
|
self.emit_stmt(Statement {
|
||||||
|
kind: StatementKind::Assign(temp, rval),
|
||||||
|
span: expr.span,
|
||||||
|
});
|
||||||
|
Operand::Copy(temp)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
|
extern_functions: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let warnings = eliminate_dead_code(&mut module);
|
let warnings = eliminate_dead_code(&mut module);
|
||||||
@@ -180,6 +181,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
|
extern_functions: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let warnings = eliminate_dead_code(&mut module);
|
let warnings = eliminate_dead_code(&mut module);
|
||||||
|
|||||||
+17
-3
@@ -19,8 +19,8 @@ fn optimize_function(func: &mut MirFunction) {
|
|||||||
let mut known_constants = HashMap::new();
|
let mut known_constants = HashMap::new();
|
||||||
|
|
||||||
for stmt in &mut block.statements {
|
for stmt in &mut block.statements {
|
||||||
let StatementKind::Assign(local, rvalue) = &mut stmt.kind;
|
match &mut stmt.kind {
|
||||||
|
StatementKind::Assign(local, rvalue) => {
|
||||||
// Propagate any known constants downwards into the rvalue
|
// Propagate any known constants downwards into the rvalue
|
||||||
propagate_rvalue(rvalue, &known_constants);
|
propagate_rvalue(rvalue, &known_constants);
|
||||||
|
|
||||||
@@ -34,6 +34,11 @@ fn optimize_function(func: &mut MirFunction) {
|
|||||||
known_constants.remove(local);
|
known_constants.remove(local);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StatementKind::SideEffect(rvalue) => {
|
||||||
|
propagate_rvalue(rvalue, &known_constants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Propagate constants into the terminator
|
// Propagate constants into the terminator
|
||||||
match &mut block.terminator.kind {
|
match &mut block.terminator.kind {
|
||||||
@@ -76,6 +81,11 @@ fn propagate_rvalue(rvalue: &mut Rvalue, known_constants: &HashMap<LocalId, Cons
|
|||||||
propagate_operand(rhs, known_constants);
|
propagate_operand(rhs, known_constants);
|
||||||
}
|
}
|
||||||
Rvalue::Cast(_, op) => propagate_operand(op, known_constants),
|
Rvalue::Cast(_, op) => propagate_operand(op, known_constants),
|
||||||
|
Rvalue::Call(_, args, _) => {
|
||||||
|
for arg in args {
|
||||||
|
propagate_operand(arg, known_constants);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +305,7 @@ mod test {
|
|||||||
|
|
||||||
let mut module = MirModule {
|
let mut module = MirModule {
|
||||||
functions: vec![func],
|
functions: vec![func],
|
||||||
|
extern_functions: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
fold_constants(&mut module);
|
fold_constants(&mut module);
|
||||||
@@ -302,7 +313,9 @@ mod test {
|
|||||||
let block = &module.functions[0].blocks[0];
|
let block = &module.functions[0].blocks[0];
|
||||||
|
|
||||||
// The third statement (LocalId(2) = ...) should be folded to 15
|
// The third statement (LocalId(2) = ...) should be folded to 15
|
||||||
let StatementKind::Assign(_, rvalue) = &block.statements[2].kind;
|
let StatementKind::Assign(_, rvalue) = &block.statements[2].kind else {
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
rvalue,
|
rvalue,
|
||||||
Rvalue::Use(Operand::Constant(ConstantValue::Integer(15, Ty::I32)))
|
Rvalue::Use(Operand::Constant(ConstantValue::Integer(15, Ty::I32)))
|
||||||
@@ -340,6 +353,7 @@ mod test {
|
|||||||
|
|
||||||
let mut module = MirModule {
|
let mut module = MirModule {
|
||||||
functions: vec![func],
|
functions: vec![func],
|
||||||
|
extern_functions: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
fold_constants(&mut module);
|
fold_constants(&mut module);
|
||||||
|
|||||||
@@ -12,9 +12,17 @@ pub struct LocalId(pub usize);
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MirModule {
|
pub struct MirModule {
|
||||||
|
pub extern_functions: Vec<MirExternFunction>,
|
||||||
pub functions: Vec<MirFunction>,
|
pub functions: Vec<MirFunction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MirExternFunction {
|
||||||
|
pub name: String,
|
||||||
|
pub params: Vec<Ty>,
|
||||||
|
pub return_type: Ty,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MirFunction {
|
pub struct MirFunction {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -52,6 +60,8 @@ pub struct Statement {
|
|||||||
pub enum StatementKind {
|
pub enum StatementKind {
|
||||||
/// Assigns the result of an Rvalue to a local variable or temporary.
|
/// Assigns the result of an Rvalue to a local variable or temporary.
|
||||||
Assign(LocalId, Rvalue),
|
Assign(LocalId, Rvalue),
|
||||||
|
/// Executes an Rvalue strictly for its side effects (e.g. FFI calling `Unit` functions)
|
||||||
|
SideEffect(Rvalue),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Operations that produce a value.
|
/// Operations that produce a value.
|
||||||
@@ -61,6 +71,7 @@ pub enum Rvalue {
|
|||||||
UnaryOp(UnaryOp, Operand),
|
UnaryOp(UnaryOp, Operand),
|
||||||
BinaryOp(BinaryOp, Operand, Operand),
|
BinaryOp(BinaryOp, Operand, Operand),
|
||||||
Cast(Ty, Operand),
|
Cast(Ty, Operand),
|
||||||
|
Call(String, Vec<Operand>, Ty),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An atomic value used as inputs to instructions.
|
/// An atomic value used as inputs to instructions.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[code]
|
||||||
|
foreign fn putchar(c: i32) -> i32;
|
||||||
|
|
||||||
|
fn main() -> i32 {
|
||||||
|
putchar(72); // 'H'
|
||||||
|
putchar(105); // 'i'
|
||||||
|
putchar(33); // '!'
|
||||||
|
putchar(10); // '\n'
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[expected_return_code]
|
||||||
|
0
|
||||||
|
|
||||||
|
[expected_output]
|
||||||
|
Hi!
|
||||||
Reference in New Issue
Block a user