From e6ae0ab2775ba6ae7b70d2dbc8cc284f2366aaed Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Sat, 17 Jun 2023 15:57:36 -0700 Subject: [PATCH] Get rid of Expression::Value and Expression:Reference, just folding in ValueOrRef directly. --- src/backend/into_crane.rs | 182 ++++++++++++++++++-------------------- src/ir/ast.rs | 11 +-- src/ir/eval.rs | 53 +++++------ 3 files changed, 111 insertions(+), 135 deletions(-) diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index d9e8701..fa038dc 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -137,7 +137,7 @@ impl Backend { // Look up the value for the variable. Because this might be a // global variable (and that requires special logic), we just turn // 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, &variable_table, &pre_defined_symbols, @@ -251,120 +251,53 @@ impl Expression { global_variables: &HashMap, ) -> Result<(entities::Value, ConstantType), BackendError> { match self { - // Values are pretty straightforward to compile, mostly because we only - // 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::Atomic(x) => x.into_crane(builder, local_variables, global_variables), Expression::Cast(_, target_type, expr) => { - let (val, val_type) = - expr.into_crane(builder, local_variables, global_variables)?; + let (val, val_type) = expr.into_crane(builder, local_variables, global_variables)?; match (val_type, &target_type) { (ConstantType::I8, Type::Primitive(PrimitiveType::I8)) => Ok((val, val_type)), - (ConstantType::I8, Type::Primitive(PrimitiveType::I16)) => { - Ok((builder.ins().sextend(types::I16, val), ConstantType::I16)) - } - (ConstantType::I8, Type::Primitive(PrimitiveType::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::I16)) => + Ok((builder.ins().sextend(types::I16, val), ConstantType::I16)), + (ConstantType::I8, Type::Primitive(PrimitiveType::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::I16, Type::Primitive(PrimitiveType::I16)) => Ok((val, val_type)), - (ConstantType::I16, Type::Primitive(PrimitiveType::I32)) => { - Ok((builder.ins().sextend(types::I32, val), ConstantType::I32)) - } - (ConstantType::I16, Type::Primitive(PrimitiveType::I64)) => { - Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)) - } + (ConstantType::I16, Type::Primitive(PrimitiveType::I32)) => + Ok((builder.ins().sextend(types::I32, val), ConstantType::I32)), + (ConstantType::I16, Type::Primitive(PrimitiveType::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::I64)) => { - Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)) - } + (ConstantType::I32, Type::Primitive(PrimitiveType::I64)) => + Ok((builder.ins().sextend(types::I64, val), ConstantType::I64)), (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::U16)) => { - Ok((builder.ins().uextend(types::I16, val), ConstantType::U16)) - } - (ConstantType::U8, Type::Primitive(PrimitiveType::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::U16)) => + Ok((builder.ins().uextend(types::I16, val), ConstantType::U16)), + (ConstantType::U8, Type::Primitive(PrimitiveType::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::U16, Type::Primitive(PrimitiveType::U16)) => Ok((val, val_type)), - (ConstantType::U16, Type::Primitive(PrimitiveType::U32)) => { - Ok((builder.ins().uextend(types::I32, val), ConstantType::U32)) - } - (ConstantType::U16, Type::Primitive(PrimitiveType::U64)) => { - Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)) - } + (ConstantType::U16, Type::Primitive(PrimitiveType::U32)) => + Ok((builder.ins().uextend(types::I32, val), ConstantType::U32)), + (ConstantType::U16, Type::Primitive(PrimitiveType::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::U64)) => { - Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)) - } + (ConstantType::U32, Type::Primitive(PrimitiveType::U64)) => + Ok((builder.ins().uextend(types::I64, val), ConstantType::U64)), (ConstantType::U64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)), + _ => Err(BackendError::InvalidTypeCast { from: val_type.into(), to: target_type, @@ -421,6 +354,63 @@ impl ValueOrRef { local_variables: &HashMap, (Variable, ConstantType)>, global_variables: &HashMap, ) -> 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)) + } + } } } diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 4ce9ab2..8cf69be 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -117,8 +117,7 @@ where /// variable reference. #[derive(Debug)] pub enum Expression { - Value(Location, Value), - Reference(Location, Variable), + Atomic(ValueOrRef), Cast(Location, Type, ValueOrRef), Primitive(Location, Primitive, Vec), } @@ -130,8 +129,7 @@ where { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { match self { - Expression::Value(_, val) => val.pretty(allocator), - Expression::Reference(_, var) => allocator.text(var.as_ref().to_string()), + Expression::Atomic(x) => x.pretty(allocator), Expression::Cast(_, t, e) => allocator .text("<") .append(t.pretty(allocator)) @@ -226,10 +224,7 @@ where impl From for Expression { fn from(value: ValueOrRef) -> Self { - match value { - ValueOrRef::Value(loc, val) => Expression::Value(loc, val), - ValueOrRef::Ref(loc, var) => Expression::Reference(loc, var), - } + Expression::Atomic(value) } } diff --git a/src/ir/eval.rs b/src/ir/eval.rs index 8d46368..420c939 100644 --- a/src/ir/eval.rs +++ b/src/ir/eval.rs @@ -34,26 +34,10 @@ impl Program { impl Expression { fn eval(&self, env: &EvalEnvironment) -> Result { match self { - Expression::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)), - }, - - Expression::Reference(_, n) => Ok(env.lookup(n.clone())?), + Expression::Atomic(x) => x.eval(env), Expression::Cast(_, t, valref) => { - let value = match valref { - ValueOrRef::Ref(_, n) => env.lookup(n.clone())?, - ValueOrRef::Value(loc, val) => { - Expression::Value(loc.clone(), val.clone()).eval(env)? - } - }; + let value = valref.eval(env)?; match t { Type::Primitive(pt) => Ok(pt.safe_cast(&value)?), @@ -61,19 +45,7 @@ impl Expression { } Expression::Primitive(_, op, args) => { - let mut arg_values = Vec::with_capacity(args.len()); - - // 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)?) - } - } - } + let arg_values = args.iter().map(|x| x.eval(env)).collect::, EvalError>>()?; // 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`. @@ -88,6 +60,25 @@ impl Expression { } } +impl ValueOrRef { + fn eval(&self, env: &EvalEnvironment) -> Result { + 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] fn two_plus_three() { let input = crate::syntax::Program::parse(0, "x = 2 + 3; print x;").expect("parse works");