got some basics working!
This commit is contained in:
@@ -43,6 +43,11 @@ pub enum BackendError {
|
|||||||
InvalidTypeCast { from: PrimitiveType, to: Type },
|
InvalidTypeCast { from: PrimitiveType, to: Type },
|
||||||
#[error("Unknown string constant '{0}")]
|
#[error("Unknown string constant '{0}")]
|
||||||
UnknownString(ArcIntern<String>),
|
UnknownString(ArcIntern<String>),
|
||||||
|
#[error("Compiler doesn't currently support function arguments")]
|
||||||
|
NoFunctionArguments {
|
||||||
|
function_name: String,
|
||||||
|
argument_name: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BackendError> for Diagnostic<usize> {
|
impl From<BackendError> for Diagnostic<usize> {
|
||||||
@@ -73,6 +78,13 @@ impl From<BackendError> for Diagnostic<usize> {
|
|||||||
),
|
),
|
||||||
BackendError::UnknownString(str) => Diagnostic::error()
|
BackendError::UnknownString(str) => Diagnostic::error()
|
||||||
.with_message(format!("Unknown string found trying to compile: '{}'", str)),
|
.with_message(format!("Unknown string found trying to compile: '{}'", str)),
|
||||||
|
BackendError::NoFunctionArguments {
|
||||||
|
function_name,
|
||||||
|
argument_name,
|
||||||
|
} => Diagnostic::error().with_message(format!(
|
||||||
|
"Function {} takes a function argument ({}), which is not supported",
|
||||||
|
function_name, argument_name
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +140,17 @@ impl PartialEq for BackendError {
|
|||||||
BackendError::UnknownString(b) => a == b,
|
BackendError::UnknownString(b) => a == b,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
BackendError::NoFunctionArguments {
|
||||||
|
function_name: f1,
|
||||||
|
argument_name: a1,
|
||||||
|
} => match other {
|
||||||
|
BackendError::NoFunctionArguments {
|
||||||
|
function_name: f2,
|
||||||
|
argument_name: a2,
|
||||||
|
} => f1 == f2 && a1 == a2,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,15 +206,15 @@ proptest::proptest! {
|
|||||||
#[test]
|
#[test]
|
||||||
fn jit_backend(program in Program::arbitrary()) {
|
fn jit_backend(program in Program::arbitrary()) {
|
||||||
use crate::eval::PrimOpError;
|
use crate::eval::PrimOpError;
|
||||||
// use pretty::{DocAllocator, Pretty};
|
use pretty::{DocAllocator, Pretty};
|
||||||
// let allocator = pretty::BoxAllocator;
|
let allocator = pretty::BoxAllocator;
|
||||||
// allocator
|
allocator
|
||||||
// .text("---------------")
|
.text("---------------")
|
||||||
// .append(allocator.hardline())
|
.append(allocator.hardline())
|
||||||
// .append(program.pretty(&allocator))
|
.append(program.pretty(&allocator))
|
||||||
// .1
|
.1
|
||||||
// .render_colored(70, pretty::termcolor::StandardStream::stdout(pretty::termcolor::ColorChoice::Auto))
|
.render_colored(70, pretty::termcolor::StandardStream::stdout(pretty::termcolor::ColorChoice::Auto))
|
||||||
// .expect("rendering works");
|
.expect("rendering works");
|
||||||
|
|
||||||
|
|
||||||
let basic_result = program.eval().map(|(_,x)| x);
|
let basic_result = program.eval().map(|(_,x)| x);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use crate::backend::Backend;
|
|||||||
enum ReferenceBuilder {
|
enum ReferenceBuilder {
|
||||||
Global(ConstantType, GlobalValue),
|
Global(ConstantType, GlobalValue),
|
||||||
Local(ConstantType, cranelift_frontend::Variable),
|
Local(ConstantType, cranelift_frontend::Variable),
|
||||||
|
Argument(ConstantType, entities::Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReferenceBuilder {
|
impl ReferenceBuilder {
|
||||||
@@ -36,6 +37,8 @@ impl ReferenceBuilder {
|
|||||||
let value = builder.use_var(*var);
|
let value = builder.use_var(*var);
|
||||||
(value, *ty)
|
(value, *ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReferenceBuilder::Argument(ty, val) => (*val, *ty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +175,21 @@ impl<M: Module> Backend<M> {
|
|||||||
// flow, we might add more blocks after this one. But, for now, we only have
|
// flow, we might add more blocks after this one. But, for now, we only have
|
||||||
// the one block.
|
// the one block.
|
||||||
let main_block = builder.create_block();
|
let main_block = builder.create_block();
|
||||||
|
// add the block parameters, which should be the function parameters
|
||||||
|
for (name, ty) in arguments.iter() {
|
||||||
|
let constant_type = ty
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| BackendError::NoFunctionArguments {
|
||||||
|
function_name: function_name.to_string(),
|
||||||
|
argument_name: name.to_string(),
|
||||||
|
})?;
|
||||||
|
let value = builder.append_block_param(main_block, ir::Type::from(constant_type));
|
||||||
|
variables.insert(
|
||||||
|
name.clone(),
|
||||||
|
ReferenceBuilder::Argument(constant_type, value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
builder.switch_to_block(main_block);
|
builder.switch_to_block(main_block);
|
||||||
|
|
||||||
let (value, _) = self.compile_expression(body, &mut variables, &mut builder)?;
|
let (value, _) = self.compile_expression(body, &mut variables, &mut builder)?;
|
||||||
@@ -388,14 +406,23 @@ impl<M: Module> Backend<M> {
|
|||||||
match valref {
|
match valref {
|
||||||
ValueOrRef::Value(_, _, val) => match val {
|
ValueOrRef::Value(_, _, val) => match val {
|
||||||
Value::I8(_, v) => {
|
Value::I8(_, v) => {
|
||||||
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::I8))
|
// Cranelift does a funny thing where it checks you aren't using bits in the I64
|
||||||
|
// we provide above the size of the type we provide. So, in this case, we can only
|
||||||
|
// set the low 8 bits of the i64. This restriction creates a bit of a problem when
|
||||||
|
// casting direction from i8 to i64, because Rust will (helpfully) sign-extend the
|
||||||
|
// negative number for us. Which sets the high bits, which makes Cranelift unhappy.
|
||||||
|
// So first we cast the i8 as u8, to get rid of the whole concept of sign extension,
|
||||||
|
// and *then* we cast to i64.
|
||||||
|
Ok((builder.ins().iconst(types::I8, v as u8 as i64), ConstantType::I8))
|
||||||
}
|
}
|
||||||
Value::I16(_, v) => Ok((
|
Value::I16(_, v) => Ok((
|
||||||
builder.ins().iconst(types::I16, v as i64),
|
// see above note for the "... as ... as"
|
||||||
|
builder.ins().iconst(types::I16, v as u16 as i64),
|
||||||
ConstantType::I16,
|
ConstantType::I16,
|
||||||
)),
|
)),
|
||||||
Value::I32(_, v) => Ok((
|
Value::I32(_, v) => Ok((
|
||||||
builder.ins().iconst(types::I32, v as i64),
|
// see above note for the "... as ... as"
|
||||||
|
builder.ins().iconst(types::I32, v as u32 as i64),
|
||||||
ConstantType::I32,
|
ConstantType::I32,
|
||||||
)),
|
)),
|
||||||
Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), ConstantType::I64)),
|
Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), ConstantType::I64)),
|
||||||
|
|||||||
@@ -33,19 +33,37 @@ fn print_result<E>(result: (Value<E>, String)) {
|
|||||||
println!("RESULT: {}", result.0);
|
println!("RESULT: {}", result.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
fn jit(ir: ngr::ir::Program<ngr::ir::Type>) -> Result<fn(), ngr::backend::BackendError> {
|
||||||
|
let mut backend = Backend::jit(None)?;
|
||||||
|
let function_id = backend.compile_program("gogogo", ir)?;
|
||||||
|
backend.module.finalize_definitions()?;
|
||||||
|
let compiled_bytes = backend.bytes(function_id);
|
||||||
|
Ok(unsafe { std::mem::transmute::<_, fn() -> ()>(compiled_bytes) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
let cli = CommandLineArguments::parse();
|
let cli = CommandLineArguments::parse();
|
||||||
let mut file_database = SimpleFiles::new();
|
let mut file_database = SimpleFiles::new();
|
||||||
let mut console = StandardStream::stdout(pretty::termcolor::ColorChoice::Auto);
|
let mut console = StandardStream::stdout(pretty::termcolor::ColorChoice::Auto);
|
||||||
let console_options = codespan_reporting::term::Config::default();
|
let console_options = codespan_reporting::term::Config::default();
|
||||||
let syntax = syntax::Program::parse_file(&mut file_database, cli.file.as_ref())?;
|
let syntax = syntax::Program::parse_file(&mut file_database, cli.file.as_ref());
|
||||||
let mut emit = |x| {
|
let mut emit = |x| {
|
||||||
let _ = codespan_reporting::term::emit(&mut console, &console_options, &file_database, &x);
|
let _ = codespan_reporting::term::emit(&mut console, &console_options, &file_database, &x);
|
||||||
};
|
};
|
||||||
|
let syntax = match syntax {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
emit((&e).into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if cli.interpreter == Interpreter::Syntax {
|
if cli.interpreter == Interpreter::Syntax {
|
||||||
print_result(syntax.eval()?);
|
match syntax.eval() {
|
||||||
return Ok(());
|
Err(e) => println!("Evaluation error: {}", e),
|
||||||
|
Ok(v) => print_result(v),
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ir = match syntax.type_infer() {
|
let ir = match syntax.type_infer() {
|
||||||
@@ -62,21 +80,20 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
for error in errors {
|
for error in errors {
|
||||||
emit(error.into());
|
emit(error.into());
|
||||||
}
|
}
|
||||||
return Err(anyhow::anyhow!("failed to infer program types"));
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if cli.interpreter == Interpreter::IR {
|
if cli.interpreter == Interpreter::IR {
|
||||||
print_result(ir.eval()?);
|
match ir.eval() {
|
||||||
return Ok(());
|
Err(e) => println!("Evaluation error: {}", e),
|
||||||
|
Ok(v) => print_result(v),
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut backend = Backend::jit(None)?;
|
match jit(ir) {
|
||||||
let function_id = backend.compile_program("gogogo", ir)?;
|
Err(e) => emit(e.into()),
|
||||||
backend.module.finalize_definitions()?;
|
Ok(compiled_function) => compiled_function(),
|
||||||
let compiled_bytes = backend.bytes(function_id);
|
}
|
||||||
let compiled_function = unsafe { std::mem::transmute::<_, fn() -> ()>(compiled_bytes) };
|
|
||||||
compiled_function();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ impl<IR: Clone> Value<IR> {
|
|||||||
left.clone(),
|
left.clone(),
|
||||||
right.clone(),
|
right.clone(),
|
||||||
)),
|
)),
|
||||||
}
|
},
|
||||||
Value::Closure(_, _, _, _) | Value::Void => {
|
Value::Closure(_, _, _, _) | Value::Void => {
|
||||||
Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone()))
|
Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,22 @@ impl From<ConstantType> for PrimitiveType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PrimitiveType> for ConstantType {
|
||||||
|
fn from(value: PrimitiveType) -> Self {
|
||||||
|
match value {
|
||||||
|
PrimitiveType::Void => ConstantType::Void,
|
||||||
|
PrimitiveType::I8 => ConstantType::I8,
|
||||||
|
PrimitiveType::I16 => ConstantType::I16,
|
||||||
|
PrimitiveType::I32 => ConstantType::I32,
|
||||||
|
PrimitiveType::I64 => ConstantType::I64,
|
||||||
|
PrimitiveType::U8 => ConstantType::U8,
|
||||||
|
PrimitiveType::U16 => ConstantType::U16,
|
||||||
|
PrimitiveType::U32 => ConstantType::U32,
|
||||||
|
PrimitiveType::U64 => ConstantType::U64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
||||||
pub enum UnknownPrimType {
|
pub enum UnknownPrimType {
|
||||||
#[error("Could not convert '{0}' into a primitive type")]
|
#[error("Could not convert '{0}' into a primitive type")]
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ impl<IR1, IR2> PartialEq<Value<IR2>> for Value<IR1> {
|
|||||||
Value::U64(y) => x == y,
|
Value::U64(y) => x == y,
|
||||||
Value::Number(y) => x == y,
|
Value::Number(y) => x == y,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
},
|
||||||
Value::Closure(_, _, _, _) => false,
|
Value::Closure(_, _, _, _) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,11 +226,11 @@ impl ValueTree for ProgramTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn simplify(&mut self) -> bool {
|
fn simplify(&mut self) -> bool {
|
||||||
unimplemented!()
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complicate(&mut self) -> bool {
|
fn complicate(&mut self) -> bool {
|
||||||
unimplemented!()
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -466,6 +466,17 @@ impl From<PrimitiveType> for Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> TryInto<ConstantType> for &'a Type {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<ConstantType, Self::Error> {
|
||||||
|
match self {
|
||||||
|
Type::Primitive(pt) => Ok((*pt).into()),
|
||||||
|
Type::Function(_, _) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum TypeOrVar {
|
pub enum TypeOrVar {
|
||||||
Primitive(PrimitiveType),
|
Primitive(PrimitiveType),
|
||||||
|
|||||||
Reference in New Issue
Block a user