Skip to content

Commit

Permalink
Add try_use_var method to cranelift-frontend. (#4588)
Browse files Browse the repository at this point in the history
* Add `try_use_var` method to `cranelift-frontend`.
- Unlike `use_var`, this method does not panic if the variable has not been defined
before use

* Add `try_declare_var` and `try_def_var`.
- Also implement Error for error enums.

* Use `write!` macro.

* Add `write!` use I missed.
  • Loading branch information
teymour-aldridge authored Aug 4, 2022
1 parent 1fc11bb commit ad223c5
Showing 1 changed file with 194 additions and 31 deletions.
225 changes: 194 additions & 31 deletions cranelift/frontend/src/frontend.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! A frontend for building Cranelift IR from other languages.
use crate::ssa::{SSABuilder, SideEffects};
use crate::variable::Variable;
use core::fmt::{self, Debug};
use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::entity::{EntitySet, SecondaryMap};
use cranelift_codegen::entity::{EntityRef, EntitySet, SecondaryMap};
use cranelift_codegen::ir;
use cranelift_codegen::ir::condcodes::IntCC;
use cranelift_codegen::ir::{
Expand Down Expand Up @@ -162,6 +163,91 @@ impl<'short, 'long> InstBuilderBase<'short> for FuncInstBuilder<'short, 'long> {
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
/// An error encountered when calling [`FunctionBuilder::try_use_var`].
pub enum UseVariableError {
UsedBeforeDeclared(Variable),
}

impl fmt::Display for UseVariableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UseVariableError::UsedBeforeDeclared(variable) => {
write!(
f,
"variable {} was used before it was defined",
variable.index()
)?;
}
}
Ok(())
}
}

impl std::error::Error for UseVariableError {}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
/// An error encountered when calling [`FunctionBuilder::try_declare_var`].
pub enum DeclareVariableError {
DeclaredMultipleTimes(Variable),
}

impl std::error::Error for DeclareVariableError {}

impl fmt::Display for DeclareVariableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DeclareVariableError::DeclaredMultipleTimes(variable) => {
write!(
f,
"variable {} was declared multiple times",
variable.index()
)?;
}
}
Ok(())
}
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
/// An error encountered when defining the initial value of a variable.
pub enum DefVariableError {
/// The variable was instantiated with a value of the wrong type.
///
/// note: to obtain the type of the value, you can call
/// [`cranelift_codegen::ir::dfg::DataFlowGraph::value_type`] (using the
/// [`FunctionBuilder.func.dfg`] field)
TypeMismatch(Variable, Value),
/// The value was defined (in a call to [`FunctionBuilder::def_var`]) before
/// it was declared (in a call to [`FunctionBuilder::declare_var`]).
DefinedBeforeDeclared(Variable),
}

impl fmt::Display for DefVariableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DefVariableError::TypeMismatch(variable, value) => {
write!(
f,
"the types of variable {} and value {} are not the same.
The `Value` supplied to `def_var` must be of the same type as
the variable was declared to be of in `declare_var`.",
variable.index(),
value.as_u32()
)?;
}
DefVariableError::DefinedBeforeDeclared(variable) => {
write!(
f,
"the value of variabe {} was declared before it was defined",
variable.index()
)?;
}
}
Ok(())
}
}

/// This module allows you to create a function in Cranelift IR in a straightforward way, hiding
/// all the complexity of its internal representation.
///
Expand Down Expand Up @@ -290,27 +376,33 @@ impl<'a> FunctionBuilder<'a> {
self.handle_ssa_side_effects(side_effects);
}

/// In order to use a variable in a `use_var`, you need to declare its type with this method.
pub fn declare_var(&mut self, var: Variable, ty: Type) {
debug_assert_eq!(
self.func_ctx.types[var],
types::INVALID,
"variable {:?} is declared twice",
var
);
/// Declares the type of a variable, so that it can be used later (by calling
/// [`FunctionBuilder::use_var`]). This function will return an error if it
/// was not possible to use the variable.
pub fn try_declare_var(&mut self, var: Variable, ty: Type) -> Result<(), DeclareVariableError> {
if self.func_ctx.types[var] != types::INVALID {
return Err(DeclareVariableError::DeclaredMultipleTimes(var));
}
self.func_ctx.types[var] = ty;
Ok(())
}

/// Returns the Cranelift IR value corresponding to the utilization at the current program
/// position of a previously defined user variable.
pub fn use_var(&mut self, var: Variable) -> Value {
/// In order to use a variable (by calling [`FunctionBuilder::use_var`]), you need
/// to first declare its type with this method.
pub fn declare_var(&mut self, var: Variable, ty: Type) {
self.try_declare_var(var, ty)
.unwrap_or_else(|_| panic!("the variable {:?} has been declared multiple times", var))
}

/// Returns the Cranelift IR necessary to use a previously defined user
/// variable, returning an error if this is not possible.
pub fn try_use_var(&mut self, var: Variable) -> Result<Value, UseVariableError> {
let (val, side_effects) = {
let ty = *self.func_ctx.types.get(var).unwrap_or_else(|| {
panic!(
"variable {:?} is used but its type has not been declared",
var
)
});
let ty = *self
.func_ctx
.types
.get(var)
.ok_or(UseVariableError::UsedBeforeDeclared(var))?;
debug_assert_ne!(
ty,
types::INVALID,
Expand All @@ -322,24 +414,55 @@ impl<'a> FunctionBuilder<'a> {
.use_var(self.func, var, ty, self.position.unwrap())
};
self.handle_ssa_side_effects(side_effects);
val
Ok(val)
}

/// Register a new definition of a user variable. The type of the value must be
/// the same as the type registered for the variable.
pub fn def_var(&mut self, var: Variable, val: Value) {
debug_assert_eq!(
*self.func_ctx.types.get(var).unwrap_or_else(|| panic!(
/// Returns the Cranelift IR value corresponding to the utilization at the current program
/// position of a previously defined user variable.
pub fn use_var(&mut self, var: Variable) -> Value {
self.try_use_var(var).unwrap_or_else(|_| {
panic!(
"variable {:?} is used but its type has not been declared",
var
)),
self.func.dfg.value_type(val),
"declared type of variable {:?} doesn't match type of value {}",
var,
val
);
)
})
}

/// Registers a new definition of a user variable. This function will return
/// an error if the value supplied does not match the type the variable was
/// declared to have.
pub fn try_def_var(&mut self, var: Variable, val: Value) -> Result<(), DefVariableError> {
let var_ty = *self
.func_ctx
.types
.get(var)
.ok_or(DefVariableError::DefinedBeforeDeclared(var))?;
if var_ty != self.func.dfg.value_type(val) {
return Err(DefVariableError::TypeMismatch(var, val));
}

self.func_ctx.ssa.def_var(var, val, self.position.unwrap());
Ok(())
}

/// Register a new definition of a user variable. The type of the value must be
/// the same as the type registered for the variable.
pub fn def_var(&mut self, var: Variable, val: Value) {
self.try_def_var(var, val)
.unwrap_or_else(|error| match error {
DefVariableError::TypeMismatch(var, val) => {
panic!(
"declared type of variable {:?} doesn't match type of value {}",
var, val
);
}
DefVariableError::DefinedBeforeDeclared(var) => {
panic!(
"variable {:?} is used but its type has not been declared",
var
);
}
})
}

/// Set label for Value
Expand Down Expand Up @@ -971,7 +1094,10 @@ impl<'a> FunctionBuilder<'a> {
#[cfg(test)]
mod tests {
use super::greatest_divisible_power_of_two;
use crate::frontend::{FunctionBuilder, FunctionBuilderContext};
use crate::frontend::{
DeclareVariableError, DefVariableError, FunctionBuilder, FunctionBuilderContext,
UseVariableError,
};
use crate::Variable;
use alloc::string::ToString;
use cranelift_codegen::entity::EntityRef;
Expand Down Expand Up @@ -1669,4 +1795,41 @@ block0:
assert_eq!(8, greatest_divisible_power_of_two(24));
assert_eq!(1, greatest_divisible_power_of_two(25));
}

#[test]
fn try_use_var() {
let sig = Signature::new(CallConv::SystemV);

let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ExternalName::testcase("sample"), sig);
{
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);

let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);

assert_eq!(
builder.try_use_var(Variable::with_u32(0)),
Err(UseVariableError::UsedBeforeDeclared(Variable::with_u32(0)))
);

let value = builder.ins().iconst(cranelift_codegen::ir::types::I32, 0);

assert_eq!(
builder.try_def_var(Variable::with_u32(0), value),
Err(DefVariableError::DefinedBeforeDeclared(Variable::with_u32(
0
)))
);

builder.declare_var(Variable::with_u32(0), cranelift_codegen::ir::types::I32);
assert_eq!(
builder.try_declare_var(Variable::with_u32(0), cranelift_codegen::ir::types::I32),
Err(DeclareVariableError::DeclaredMultipleTimes(
Variable::with_u32(0)
))
);
}
}
}

0 comments on commit ad223c5

Please sign in to comment.