🤔 Add a type inference engine, along with typed literals. #4
@@ -137,7 +137,7 @@ impl<M: Module> Backend<M> {
|
|||||||
// Look up the value for the variable. Because this might be a
|
// Look up the value for the variable. Because this might be a
|
||||||
// global variable (and that requires special logic), we just turn
|
// global variable (and that requires special logic), we just turn
|
||||||
// this into an `Expression` and re-use the logic in that implementation.
|
// this into an `Expression` and re-use the logic in that implementation.
|
||||||
let (val, vtype) = Expression::Reference(ann, var).into_crane(
|
let (val, vtype) = ValueOrRef::Ref(ann, var).into_crane(
|
||||||
&mut builder,
|
&mut builder,
|
||||||
&variable_table,
|
&variable_table,
|
||||||
&pre_defined_symbols,
|
&pre_defined_symbols,
|
||||||
@@ -251,120 +251,53 @@ impl Expression {
|
|||||||
global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
|
global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
|
||||||
) -> Result<(entities::Value, ConstantType), BackendError> {
|
) -> Result<(entities::Value, ConstantType), BackendError> {
|
||||||
match self {
|
match self {
|
||||||
// Values are pretty straightforward to compile, mostly because we only
|
Expression::Atomic(x) => x.into_crane(builder, local_variables, global_variables),
|
||||||
// have one type of variable, and it's an integer type.
|
|
||||||
Expression::Value(_, val) => match val {
|
|
||||||
Value::I8(_, v) => {
|
|
||||||
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::I8))
|
|
||||||
}
|
|
||||||
Value::I16(_, v) => Ok((
|
|
||||||
builder.ins().iconst(types::I16, v as i64),
|
|
||||||
ConstantType::I16,
|
|
||||||
)),
|
|
||||||
Value::I32(_, v) => Ok((
|
|
||||||
builder.ins().iconst(types::I32, v as i64),
|
|
||||||
ConstantType::I32,
|
|
||||||
)),
|
|
||||||
Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), ConstantType::I64)),
|
|
||||||
Value::U8(_, v) => {
|
|
||||||
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::U8))
|
|
||||||
}
|
|
||||||
Value::U16(_, v) => Ok((
|
|
||||||
builder.ins().iconst(types::I16, v as i64),
|
|
||||||
ConstantType::U16,
|
|
||||||
)),
|
|
||||||
Value::U32(_, v) => Ok((
|
|
||||||
builder.ins().iconst(types::I32, v as i64),
|
|
||||||
ConstantType::U32,
|
|
||||||
)),
|
|
||||||
Value::U64(_, v) => Ok((
|
|
||||||
builder.ins().iconst(types::I64, v as i64),
|
|
||||||
ConstantType::U64,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
|
|
||||||
Expression::Reference(_, name) => {
|
|
||||||
// first we see if this is a local variable (which is nicer, from an
|
|
||||||
// optimization point of view.)
|
|
||||||
if let Some((local_var, etype)) = local_variables.get(&name) {
|
|
||||||
return Ok((builder.use_var(*local_var), *etype));
|
|
||||||
}
|
|
||||||
|
|
||||||
// then we check to see if this is a global reference, which requires us to
|
|
||||||
// first lookup where the value is stored, and then load it.
|
|
||||||
if let Some((global_var, etype)) = global_variables.get(name.as_ref()) {
|
|
||||||
let cranelift_type = ir::Type::from(*etype);
|
|
||||||
let val_ptr = builder.ins().symbol_value(cranelift_type, *global_var);
|
|
||||||
return Ok((
|
|
||||||
builder
|
|
||||||
.ins()
|
|
||||||
.load(cranelift_type, MemFlags::new(), val_ptr, 0),
|
|
||||||
*etype,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// this should never happen, because we should have made sure that there are
|
|
||||||
// no unbound variables a long time before this. but still ...
|
|
||||||
Err(BackendError::VariableLookupFailure(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
Expression::Cast(_, target_type, expr) => {
|
Expression::Cast(_, target_type, expr) => {
|
||||||
let (val, val_type) =
|
let (val, val_type) = expr.into_crane(builder, local_variables, global_variables)?;
|
||||||
expr.into_crane(builder, local_variables, global_variables)?;
|
|
||||||
|
|
||||||
match (val_type, &target_type) {
|
match (val_type, &target_type) {
|
||||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I8)) => Ok((val, val_type)),
|
(ConstantType::I8, Type::Primitive(PrimitiveType::I8)) => Ok((val, val_type)),
|
||||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I16)) => {
|
(ConstantType::I8, Type::Primitive(PrimitiveType::I16)) =>
|
||||||
Ok((builder.ins().sextend(types::I16, val), ConstantType::I16))
|
Ok((builder.ins().sextend(types::I16, val), ConstantType::I16)),
|
||||||
}
|
(ConstantType::I8, Type::Primitive(PrimitiveType::I32)) =>
|
||||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I32)) => {
|
Ok((builder.ins().sextend(types::I32, val), ConstantType::I32)),
|
||||||
Ok((builder.ins().sextend(types::I32, val), ConstantType::I32))
|
(ConstantType::I8, Type::Primitive(PrimitiveType::I64)) =>
|
||||||
}
|
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)),
|
||||||
(ConstantType::I8, Type::Primitive(PrimitiveType::I64)) => {
|
|
||||||
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64))
|
|
||||||
}
|
|
||||||
|
|
||||||
(ConstantType::I16, Type::Primitive(PrimitiveType::I16)) => Ok((val, val_type)),
|
(ConstantType::I16, Type::Primitive(PrimitiveType::I16)) => Ok((val, val_type)),
|
||||||
(ConstantType::I16, Type::Primitive(PrimitiveType::I32)) => {
|
(ConstantType::I16, Type::Primitive(PrimitiveType::I32)) =>
|
||||||
Ok((builder.ins().sextend(types::I32, val), ConstantType::I32))
|
Ok((builder.ins().sextend(types::I32, val), ConstantType::I32)),
|
||||||
}
|
(ConstantType::I16, Type::Primitive(PrimitiveType::I64)) =>
|
||||||
(ConstantType::I16, Type::Primitive(PrimitiveType::I64)) => {
|
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)),
|
||||||
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64))
|
|
||||||
}
|
|
||||||
|
|
||||||
(ConstantType::I32, Type::Primitive(PrimitiveType::I32)) => Ok((val, val_type)),
|
(ConstantType::I32, Type::Primitive(PrimitiveType::I32)) => Ok((val, val_type)),
|
||||||
(ConstantType::I32, Type::Primitive(PrimitiveType::I64)) => {
|
(ConstantType::I32, Type::Primitive(PrimitiveType::I64)) =>
|
||||||
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64))
|
Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)),
|
||||||
}
|
|
||||||
|
|
||||||
(ConstantType::I64, Type::Primitive(PrimitiveType::I64)) => Ok((val, val_type)),
|
(ConstantType::I64, Type::Primitive(PrimitiveType::I64)) => Ok((val, val_type)),
|
||||||
|
|
||||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U8)) => Ok((val, val_type)),
|
(ConstantType::U8, Type::Primitive(PrimitiveType::U8)) => Ok((val, val_type)),
|
||||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U16)) => {
|
(ConstantType::U8, Type::Primitive(PrimitiveType::U16)) =>
|
||||||
Ok((builder.ins().uextend(types::I16, val), ConstantType::U16))
|
Ok((builder.ins().uextend(types::I16, val), ConstantType::U16)),
|
||||||
}
|
(ConstantType::U8, Type::Primitive(PrimitiveType::U32)) =>
|
||||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U32)) => {
|
Ok((builder.ins().uextend(types::I32, val), ConstantType::U32)),
|
||||||
Ok((builder.ins().uextend(types::I32, val), ConstantType::U32))
|
(ConstantType::U8, Type::Primitive(PrimitiveType::U64)) =>
|
||||||
}
|
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)),
|
||||||
(ConstantType::U8, Type::Primitive(PrimitiveType::U64)) => {
|
|
||||||
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64))
|
|
||||||
}
|
|
||||||
|
|
||||||
(ConstantType::U16, Type::Primitive(PrimitiveType::U16)) => Ok((val, val_type)),
|
(ConstantType::U16, Type::Primitive(PrimitiveType::U16)) => Ok((val, val_type)),
|
||||||
(ConstantType::U16, Type::Primitive(PrimitiveType::U32)) => {
|
(ConstantType::U16, Type::Primitive(PrimitiveType::U32)) =>
|
||||||
Ok((builder.ins().uextend(types::I32, val), ConstantType::U32))
|
Ok((builder.ins().uextend(types::I32, val), ConstantType::U32)),
|
||||||
}
|
(ConstantType::U16, Type::Primitive(PrimitiveType::U64)) =>
|
||||||
(ConstantType::U16, Type::Primitive(PrimitiveType::U64)) => {
|
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)),
|
||||||
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64))
|
|
||||||
}
|
|
||||||
|
|
||||||
(ConstantType::U32, Type::Primitive(PrimitiveType::U32)) => Ok((val, val_type)),
|
(ConstantType::U32, Type::Primitive(PrimitiveType::U32)) => Ok((val, val_type)),
|
||||||
(ConstantType::U32, Type::Primitive(PrimitiveType::U64)) => {
|
(ConstantType::U32, Type::Primitive(PrimitiveType::U64)) =>
|
||||||
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64))
|
Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)),
|
||||||
}
|
|
||||||
|
|
||||||
(ConstantType::U64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)),
|
(ConstantType::U64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)),
|
||||||
|
|
||||||
|
|
||||||
_ => Err(BackendError::InvalidTypeCast {
|
_ => Err(BackendError::InvalidTypeCast {
|
||||||
from: val_type.into(),
|
from: val_type.into(),
|
||||||
to: target_type,
|
to: target_type,
|
||||||
@@ -421,6 +354,63 @@ impl ValueOrRef {
|
|||||||
local_variables: &HashMap<ArcIntern<String>, (Variable, ConstantType)>,
|
local_variables: &HashMap<ArcIntern<String>, (Variable, ConstantType)>,
|
||||||
global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
|
global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
|
||||||
) -> Result<(entities::Value, ConstantType), BackendError> {
|
) -> Result<(entities::Value, ConstantType), BackendError> {
|
||||||
Expression::from(self).into_crane(builder, local_variables, global_variables)
|
match self {
|
||||||
|
// Values are pretty straightforward to compile, mostly because we only
|
||||||
|
// have one type of variable, and it's an integer type.
|
||||||
|
ValueOrRef::Value(_, val) => match val {
|
||||||
|
Value::I8(_, v) => {
|
||||||
|
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::I8))
|
||||||
|
}
|
||||||
|
Value::I16(_, v) => Ok((
|
||||||
|
builder.ins().iconst(types::I16, v as i64),
|
||||||
|
ConstantType::I16,
|
||||||
|
)),
|
||||||
|
Value::I32(_, v) => Ok((
|
||||||
|
builder.ins().iconst(types::I32, v as i64),
|
||||||
|
ConstantType::I32,
|
||||||
|
)),
|
||||||
|
Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), ConstantType::I64)),
|
||||||
|
Value::U8(_, v) => {
|
||||||
|
Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::U8))
|
||||||
|
}
|
||||||
|
Value::U16(_, v) => Ok((
|
||||||
|
builder.ins().iconst(types::I16, v as i64),
|
||||||
|
ConstantType::U16,
|
||||||
|
)),
|
||||||
|
Value::U32(_, v) => Ok((
|
||||||
|
builder.ins().iconst(types::I32, v as i64),
|
||||||
|
ConstantType::U32,
|
||||||
|
)),
|
||||||
|
Value::U64(_, v) => Ok((
|
||||||
|
builder.ins().iconst(types::I64, v as i64),
|
||||||
|
ConstantType::U64,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
|
||||||
|
ValueOrRef::Ref(_, name) => {
|
||||||
|
// first we see if this is a local variable (which is nicer, from an
|
||||||
|
// optimization point of view.)
|
||||||
|
if let Some((local_var, etype)) = local_variables.get(&name) {
|
||||||
|
return Ok((builder.use_var(*local_var), *etype));
|
||||||
|
}
|
||||||
|
|
||||||
|
// then we check to see if this is a global reference, which requires us to
|
||||||
|
// first lookup where the value is stored, and then load it.
|
||||||
|
if let Some((global_var, etype)) = global_variables.get(name.as_ref()) {
|
||||||
|
let cranelift_type = ir::Type::from(*etype);
|
||||||
|
let val_ptr = builder.ins().symbol_value(cranelift_type, *global_var);
|
||||||
|
return Ok((
|
||||||
|
builder
|
||||||
|
.ins()
|
||||||
|
.load(cranelift_type, MemFlags::new(), val_ptr, 0),
|
||||||
|
*etype,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should never happen, because we should have made sure that there are
|
||||||
|
// no unbound variables a long time before this. but still ...
|
||||||
|
Err(BackendError::VariableLookupFailure(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,8 +117,7 @@ where
|
|||||||
/// variable reference.
|
/// variable reference.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Value(Location, Value),
|
Atomic(ValueOrRef),
|
||||||
Reference(Location, Variable),
|
|
||||||
Cast(Location, Type, ValueOrRef),
|
Cast(Location, Type, ValueOrRef),
|
||||||
Primitive(Location, Primitive, Vec<ValueOrRef>),
|
Primitive(Location, Primitive, Vec<ValueOrRef>),
|
||||||
}
|
}
|
||||||
@@ -130,8 +129,7 @@ where
|
|||||||
{
|
{
|
||||||
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
|
||||||
match self {
|
match self {
|
||||||
Expression::Value(_, val) => val.pretty(allocator),
|
Expression::Atomic(x) => x.pretty(allocator),
|
||||||
Expression::Reference(_, var) => allocator.text(var.as_ref().to_string()),
|
|
||||||
Expression::Cast(_, t, e) => allocator
|
Expression::Cast(_, t, e) => allocator
|
||||||
.text("<")
|
.text("<")
|
||||||
.append(t.pretty(allocator))
|
.append(t.pretty(allocator))
|
||||||
@@ -226,10 +224,7 @@ where
|
|||||||
|
|
||||||
impl From<ValueOrRef> for Expression {
|
impl From<ValueOrRef> for Expression {
|
||||||
fn from(value: ValueOrRef) -> Self {
|
fn from(value: ValueOrRef) -> Self {
|
||||||
match value {
|
Expression::Atomic(value)
|
||||||
ValueOrRef::Value(loc, val) => Expression::Value(loc, val),
|
|
||||||
ValueOrRef::Ref(loc, var) => Expression::Reference(loc, var),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,26 +34,10 @@ impl Program {
|
|||||||
impl Expression {
|
impl Expression {
|
||||||
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
|
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
|
||||||
match self {
|
match self {
|
||||||
Expression::Value(_, v) => match v {
|
Expression::Atomic(x) => x.eval(env),
|
||||||
super::Value::I8(_, v) => Ok(Value::I8(*v)),
|
|
||||||
super::Value::I16(_, v) => Ok(Value::I16(*v)),
|
|
||||||
super::Value::I32(_, v) => Ok(Value::I32(*v)),
|
|
||||||
super::Value::I64(_, v) => Ok(Value::I64(*v)),
|
|
||||||
super::Value::U8(_, v) => Ok(Value::U8(*v)),
|
|
||||||
super::Value::U16(_, v) => Ok(Value::U16(*v)),
|
|
||||||
super::Value::U32(_, v) => Ok(Value::U32(*v)),
|
|
||||||
super::Value::U64(_, v) => Ok(Value::U64(*v)),
|
|
||||||
},
|
|
||||||
|
|
||||||
Expression::Reference(_, n) => Ok(env.lookup(n.clone())?),
|
|
||||||
|
|
||||||
Expression::Cast(_, t, valref) => {
|
Expression::Cast(_, t, valref) => {
|
||||||
let value = match valref {
|
let value = valref.eval(env)?;
|
||||||
ValueOrRef::Ref(_, n) => env.lookup(n.clone())?,
|
|
||||||
ValueOrRef::Value(loc, val) => {
|
|
||||||
Expression::Value(loc.clone(), val.clone()).eval(env)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match t {
|
match t {
|
||||||
Type::Primitive(pt) => Ok(pt.safe_cast(&value)?),
|
Type::Primitive(pt) => Ok(pt.safe_cast(&value)?),
|
||||||
@@ -61,19 +45,7 @@ impl Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expression::Primitive(_, op, args) => {
|
Expression::Primitive(_, op, args) => {
|
||||||
let mut arg_values = Vec::with_capacity(args.len());
|
let arg_values = args.iter().map(|x| x.eval(env)).collect::<Result<Vec<Value>, EvalError>>()?;
|
||||||
|
|
||||||
// we implement primitive operations by first evaluating each of the
|
|
||||||
// arguments to the function, and then gathering up all the values
|
|
||||||
// produced.
|
|
||||||
for arg in args.iter() {
|
|
||||||
match arg {
|
|
||||||
ValueOrRef::Ref(_, n) => arg_values.push(env.lookup(n.clone())?),
|
|
||||||
ValueOrRef::Value(loc, val) => {
|
|
||||||
arg_values.push(Expression::Value(loc.clone(), val.clone()).eval(env)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// and then finally we call `calculate` to run them. trust me, it's nice
|
// and then finally we call `calculate` to run them. trust me, it's nice
|
||||||
// to not have to deal with all the nonsense hidden under `calculate`.
|
// to not have to deal with all the nonsense hidden under `calculate`.
|
||||||
@@ -88,6 +60,25 @@ impl Expression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ValueOrRef {
|
||||||
|
fn eval(&self, env: &EvalEnvironment) -> Result<Value, EvalError> {
|
||||||
|
match self {
|
||||||
|
ValueOrRef::Value(_, v) => match v {
|
||||||
|
super::Value::I8(_, v) => Ok(Value::I8(*v)),
|
||||||
|
super::Value::I16(_, v) => Ok(Value::I16(*v)),
|
||||||
|
super::Value::I32(_, v) => Ok(Value::I32(*v)),
|
||||||
|
super::Value::I64(_, v) => Ok(Value::I64(*v)),
|
||||||
|
super::Value::U8(_, v) => Ok(Value::U8(*v)),
|
||||||
|
super::Value::U16(_, v) => Ok(Value::U16(*v)),
|
||||||
|
super::Value::U32(_, v) => Ok(Value::U32(*v)),
|
||||||
|
super::Value::U64(_, v) => Ok(Value::U64(*v)),
|
||||||
|
},
|
||||||
|
|
||||||
|
ValueOrRef::Ref(_, n) => Ok(env.lookup(n.clone())?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_plus_three() {
|
fn two_plus_three() {
|
||||||
let input = crate::syntax::Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
|
let input = crate::syntax::Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
|
||||||
|
|||||||
Reference in New Issue
Block a user