🤷 The initial version of the compiler, both static and JIT.
This implements a full compiler, with both static compilation and JIT support, for the world's simplest and silliest programming language. You can do math, and print variables. That's it. On the bright side, it implements every part of the compiler, from the lexer and parser; through analysis and simplification; and into a reasonable code generator. This should be a good jumping off point for adding more advanced features. Tests, including proptests, are included to help avoid regressions.
This commit is contained in:
46
src/backend/error.rs
Normal file
46
src/backend/error.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::backend::runtime::RuntimeFunctionError;
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use cranelift_codegen::{isa::LookupError, settings::SetError, CodegenError};
|
||||
use cranelift_module::ModuleError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum BackendError {
|
||||
#[error("Cranelift module error: {0}")]
|
||||
Cranelift(#[from] ModuleError),
|
||||
#[error("Builtin function error: {0}")]
|
||||
BuiltinError(#[from] RuntimeFunctionError),
|
||||
#[error("Internal variable lookup error")]
|
||||
VariableLookupFailure,
|
||||
#[error(transparent)]
|
||||
CodegenError(#[from] CodegenError),
|
||||
#[error(transparent)]
|
||||
SetError(#[from] SetError),
|
||||
#[error(transparent)]
|
||||
LookupError(#[from] LookupError),
|
||||
}
|
||||
|
||||
impl From<BackendError> for Diagnostic<usize> {
|
||||
fn from(value: BackendError) -> Self {
|
||||
match value {
|
||||
BackendError::Cranelift(me) => {
|
||||
Diagnostic::error().with_message(format!("Internal cranelift error: {}", me))
|
||||
}
|
||||
BackendError::BuiltinError(me) => {
|
||||
Diagnostic::error().with_message(format!("Internal runtime function error: {}", me))
|
||||
}
|
||||
BackendError::VariableLookupFailure => {
|
||||
Diagnostic::error().with_message("Internal variable lookup error!")
|
||||
}
|
||||
BackendError::CodegenError(me) => {
|
||||
Diagnostic::error().with_message(format!("Internal codegen error: {}", me))
|
||||
}
|
||||
BackendError::SetError(me) => {
|
||||
Diagnostic::error().with_message(format!("Internal backend setup error: {}", me))
|
||||
}
|
||||
BackendError::LookupError(me) => {
|
||||
Diagnostic::error().with_message(format!("Internal error: {}", me))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
174
src/backend/into_crane.rs
Normal file
174
src/backend/into_crane.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::ir::{Expression, Primitive, Program, Statement, Value, ValueOrRef};
|
||||
use cranelift_codegen::entity::EntityRef;
|
||||
use cranelift_codegen::ir::{
|
||||
entities, types, Function, GlobalValue, InstBuilder, MemFlags, Signature, UserFuncName,
|
||||
};
|
||||
use cranelift_codegen::isa::CallConv;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
|
||||
use cranelift_module::{FuncId, Linkage, Module, ModuleError};
|
||||
use internment::ArcIntern;
|
||||
|
||||
use crate::backend::error::BackendError;
|
||||
use crate::backend::Backend;
|
||||
|
||||
type StringTable = HashMap<ArcIntern<String>, GlobalValue>;
|
||||
|
||||
impl<M: Module> Backend<M> {
|
||||
pub fn compile_function(
|
||||
&mut self,
|
||||
function_name: &str,
|
||||
mut program: Program,
|
||||
) -> Result<FuncId, BackendError> {
|
||||
let basic_signature = Signature {
|
||||
params: vec![],
|
||||
returns: vec![],
|
||||
call_conv: CallConv::SystemV,
|
||||
};
|
||||
|
||||
let func_id =
|
||||
self.module
|
||||
.declare_function(function_name, Linkage::Export, &basic_signature)?;
|
||||
let mut ctx = Context::new();
|
||||
ctx.func =
|
||||
Function::with_name_signature(UserFuncName::user(0, func_id.as_u32()), basic_signature);
|
||||
|
||||
let string_table = self.build_string_table(&mut ctx.func, &program)?;
|
||||
let mut variable_table = HashMap::new();
|
||||
let mut next_var_num = 1;
|
||||
let print_func_ref = self.runtime_functions.include_runtime_function(
|
||||
"print",
|
||||
&mut self.module,
|
||||
&mut ctx.func,
|
||||
)?;
|
||||
let pre_defined_symbols: HashMap<String, GlobalValue> = self
|
||||
.defined_symbols
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let local_data = self.module.declare_data_in_func(*v, &mut ctx.func);
|
||||
(k.clone(), local_data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut fctx = FunctionBuilderContext::new();
|
||||
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fctx);
|
||||
let main_block = builder.create_block();
|
||||
builder.switch_to_block(main_block);
|
||||
|
||||
for stmt in program.statements.drain(..) {
|
||||
match stmt {
|
||||
Statement::Print(ann, var) => {
|
||||
let local_name_ref = string_table.get(&var).unwrap();
|
||||
let name_ptr = builder.ins().symbol_value(types::I64, *local_name_ref);
|
||||
let val = ValueOrRef::Ref(ann, var).into_cranelift(
|
||||
&mut builder,
|
||||
&variable_table,
|
||||
&pre_defined_symbols,
|
||||
)?;
|
||||
builder.ins().call(print_func_ref, &[name_ptr, val]);
|
||||
}
|
||||
|
||||
Statement::Binding(_, var_name, value) => {
|
||||
let val = match value {
|
||||
Expression::Value(_, Value::Number(_, v)) => {
|
||||
builder.ins().iconst(types::I64, v)
|
||||
}
|
||||
|
||||
Expression::Reference(_, name) => {
|
||||
let value_var_num = variable_table.get(&name).unwrap();
|
||||
builder.use_var(Variable::new(*value_var_num))
|
||||
}
|
||||
|
||||
Expression::Primitive(_, prim, mut vals) => {
|
||||
let right = vals.pop().unwrap().into_cranelift(
|
||||
&mut builder,
|
||||
&variable_table,
|
||||
&pre_defined_symbols,
|
||||
)?;
|
||||
let left = vals.pop().unwrap().into_cranelift(
|
||||
&mut builder,
|
||||
&variable_table,
|
||||
&pre_defined_symbols,
|
||||
)?;
|
||||
|
||||
match prim {
|
||||
Primitive::Plus => builder.ins().iadd(left, right),
|
||||
Primitive::Minus => builder.ins().isub(left, right),
|
||||
Primitive::Times => builder.ins().imul(left, right),
|
||||
Primitive::Divide => builder.ins().sdiv(left, right),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(global_id) = pre_defined_symbols.get(var_name.as_str()) {
|
||||
let val_ptr = builder.ins().symbol_value(types::I64, *global_id);
|
||||
builder.ins().store(MemFlags::new(), val, val_ptr, 0);
|
||||
} else {
|
||||
let var = Variable::new(next_var_num);
|
||||
variable_table.insert(var_name, next_var_num);
|
||||
next_var_num += 1;
|
||||
builder.declare_var(var, types::I64);
|
||||
builder.def_var(var, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.ins().return_(&[]);
|
||||
builder.seal_block(main_block);
|
||||
builder.finalize();
|
||||
|
||||
let _ = self.module.define_function(func_id, &mut ctx)?;
|
||||
|
||||
Ok(func_id)
|
||||
}
|
||||
|
||||
fn build_string_table(
|
||||
&mut self,
|
||||
func: &mut Function,
|
||||
program: &Program,
|
||||
) -> Result<StringTable, BackendError> {
|
||||
let mut string_table = HashMap::new();
|
||||
|
||||
for interned_value in program.strings().drain() {
|
||||
let global_id = match self.defined_strings.get(interned_value.as_str()) {
|
||||
Some(x) => *x,
|
||||
None => self.define_string(interned_value.as_str())?,
|
||||
};
|
||||
let local_data = self.module.declare_data_in_func(global_id, func);
|
||||
string_table.insert(interned_value, local_data);
|
||||
}
|
||||
|
||||
Ok(string_table)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueOrRef {
|
||||
fn into_cranelift(
|
||||
self,
|
||||
builder: &mut FunctionBuilder,
|
||||
local_variables: &HashMap<ArcIntern<String>, usize>,
|
||||
global_variables: &HashMap<String, GlobalValue>,
|
||||
) -> Result<entities::Value, ModuleError> {
|
||||
match self {
|
||||
ValueOrRef::Value(_, value) => match value {
|
||||
Value::Number(_base, numval) => Ok(builder.ins().iconst(types::I64, numval)),
|
||||
},
|
||||
|
||||
ValueOrRef::Ref(_, name) => {
|
||||
if let Some(local_num) = local_variables.get(&name) {
|
||||
return Ok(builder.use_var(Variable::new(*local_num)));
|
||||
}
|
||||
|
||||
if let Some(global_id) = global_variables.get(name.as_str()) {
|
||||
let val_ptr = builder.ins().symbol_value(types::I64, *global_id);
|
||||
return Ok(builder.ins().load(types::I64, MemFlags::new(), val_ptr, 0));
|
||||
}
|
||||
|
||||
Err(ModuleError::Undeclared(name.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/backend/object.rs
Normal file
8
src/backend/object.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
struct BackendObject {
|
||||
}
|
||||
|
||||
impl BackendObject {
|
||||
pub fn new() -> Result<Self, ()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
69
src/backend/runtime.rs
Normal file
69
src/backend/runtime.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use cranelift_codegen::ir::{types, AbiParam, FuncRef, Function, Signature};
|
||||
use cranelift_codegen::isa::CallConv;
|
||||
use cranelift_jit::JITBuilder;
|
||||
use cranelift_module::{FuncId, Linkage, Module, ModuleResult};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use target_lexicon::Triple;
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct RuntimeFunctions {
|
||||
builtin_functions: HashMap<String, FuncId>,
|
||||
_referenced_functions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RuntimeFunctionError {
|
||||
#[error("Could not find runtime function named '{0}'")]
|
||||
CannotFindRuntimeFunction(String),
|
||||
}
|
||||
|
||||
extern "C" fn runtime_print(name: *const i8, value: u64) {
|
||||
let cstr = unsafe { CStr::from_ptr(name) };
|
||||
let reconstituted = cstr.to_string_lossy();
|
||||
println!("{} = {}", reconstituted, value);
|
||||
}
|
||||
|
||||
impl RuntimeFunctions {
|
||||
pub fn new<M: Module>(platform: &Triple, module: &mut M) -> ModuleResult<RuntimeFunctions> {
|
||||
let mut builtin_functions = HashMap::new();
|
||||
let _referenced_functions = Vec::new();
|
||||
|
||||
let string_param = AbiParam::new(types::I64);
|
||||
let int64_param = AbiParam::new(types::I64);
|
||||
|
||||
let print_id = module.declare_function(
|
||||
"print",
|
||||
Linkage::Import,
|
||||
&Signature {
|
||||
params: vec![string_param, int64_param],
|
||||
returns: vec![],
|
||||
call_conv: CallConv::triple_default(platform),
|
||||
},
|
||||
)?;
|
||||
builtin_functions.insert("print".to_string(), print_id);
|
||||
|
||||
Ok(RuntimeFunctions {
|
||||
builtin_functions,
|
||||
_referenced_functions,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn include_runtime_function<M: Module>(
|
||||
&self,
|
||||
name: &str,
|
||||
module: &mut M,
|
||||
func: &mut Function,
|
||||
) -> Result<FuncRef, RuntimeFunctionError> {
|
||||
match self.builtin_functions.get(name) {
|
||||
None => Err(RuntimeFunctionError::CannotFindRuntimeFunction(
|
||||
name.to_string(),
|
||||
)),
|
||||
Some(func_id) => Ok(module.declare_func_in_func(*func_id, func)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_jit_implementations(builder: &mut JITBuilder) {
|
||||
builder.symbol("print", runtime_print as *const u8);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user