ISLE: add support for extended left-hand sides with if-let clauses. (#4072)

This PR adds support for `if-let` clauses, as proposed in
bytecodealliance/rfcs#21. These clauses augment the left-hand side
(pattern-matching phase) of rules in the ISLE instruction-selection DSL
with sub-patterns matching on sub-expressions. The ability to list
additional match operations to perform, out-of-line from the original
toplevel pattern, greatly simplifies some idioms. See the RFC for more
details and examples of use.
This commit is contained in:
Chris Fallin
2022-04-28 16:37:11 -07:00
committed by GitHub
parent 128c42fa09
commit 5b7d56f6f7
12 changed files with 496 additions and 55 deletions

View File

@@ -69,17 +69,27 @@ pub struct Decl {
pub term: Ident,
pub arg_tys: Vec<Ident>,
pub ret_ty: Ident,
/// Whether this term's constructor is pure.
pub pure: bool,
pub pos: Pos,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Rule {
pub pattern: Pattern,
pub iflets: Vec<IfLet>,
pub expr: Expr,
pub pos: Pos,
pub prio: Option<i64>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct IfLet {
pub pattern: Pattern,
pub expr: Expr,
pub pos: Pos,
}
/// An extractor macro: (A x y) becomes (B x _ y ...). Expanded during
/// ast-to-sema pass.
#[derive(Clone, PartialEq, Eq, Debug)]

View File

@@ -68,7 +68,7 @@ impl<'a> Codegen<'a> {
.unwrap();
writeln!(
code,
"#![allow(unused_imports, unused_variables, non_snake_case)]"
"#![allow(unused_imports, unused_variables, non_snake_case, unused_mut)]"
)
.unwrap();
writeln!(code, "#![allow(irrefutable_let_patterns)]").unwrap();
@@ -626,7 +626,7 @@ impl<'a> Codegen<'a> {
ref seq, output_ty, ..
} => {
let closure_name = format!("closure{}", id.index());
writeln!(code, "{}let {} = || {{", indent, closure_name).unwrap();
writeln!(code, "{}let mut {} = || {{", indent, closure_name).unwrap();
let subindent = format!("{} ", indent);
let mut subctx = ctx.clone();
let mut returns = vec![];

View File

@@ -628,13 +628,14 @@ impl ExprSequence {
}
TermKind::Decl {
constructor_kind: Some(ConstructorKind::ExternalConstructor { .. }),
pure,
..
} => {
self.add_construct(
&arg_values_tys[..],
ty,
term,
/* infallible = */ true,
/* infallible = */ !pure,
)
}
TermKind::Decl {
@@ -675,6 +676,23 @@ pub fn lower_rule(
&mut vars,
);
// Lower the `if-let` clauses into the pattern seq, using
// `PatternInst::Expr` for the sub-exprs (right-hand sides).
for iflet in &ruledata.iflets {
let mut subexpr_seq: ExprSequence = Default::default();
let subexpr_ret_value = subexpr_seq.gen_expr(tyenv, termenv, &iflet.rhs, &mut vars);
subexpr_seq.add_return(iflet.rhs.ty(), subexpr_ret_value);
let pattern_value =
pattern_seq.add_expr_seq(subexpr_seq, subexpr_ret_value, iflet.rhs.ty());
pattern_seq.gen_pattern(
ValueOrArgs::Value(pattern_value),
tyenv,
termenv,
&iflet.lhs,
&mut vars,
);
}
// Lower the expression, making use of the bound variables
// from the pattern.
let rhs_root_val = expr_seq.gen_expr(tyenv, termenv, &ruledata.rhs, &vars);

View File

@@ -18,6 +18,14 @@ struct Parser<'a> {
lexer: Lexer<'a>,
}
/// Used during parsing a `(rule ...)` to encapsulate some form that
/// comes after the top-level pattern: an if-let clause, or the final
/// top-level expr.
enum IfLetOrExpr {
IfLet(IfLet),
Expr(Expr),
}
impl<'a> Parser<'a> {
/// Construct a new parser from the given lexer.
pub fn new(lexer: Lexer<'a>) -> Parser<'a> {
@@ -281,6 +289,14 @@ impl<'a> Parser<'a> {
fn parse_decl(&mut self) -> Result<Decl> {
let pos = self.pos();
let pure = if self.is_sym_str("pure") {
self.symbol()?;
true
} else {
false
};
let term = self.parse_ident()?;
self.lparen()?;
@@ -296,6 +312,7 @@ impl<'a> Parser<'a> {
term,
arg_tys,
ret_ty,
pure,
pos,
})
}
@@ -304,6 +321,7 @@ impl<'a> Parser<'a> {
let pos = self.pos();
if self.is_sym_str("constructor") {
self.symbol()?;
let term = self.parse_ident()?;
let func = self.parse_ident()?;
Ok(Extern::Constructor { term, func, pos })
@@ -387,13 +405,23 @@ impl<'a> Parser<'a> {
None
};
let pattern = self.parse_pattern()?;
let expr = self.parse_expr()?;
Ok(Rule {
pattern,
expr,
pos,
prio,
})
let mut iflets = vec![];
loop {
match self.parse_iflet_or_expr()? {
IfLetOrExpr::IfLet(iflet) => {
iflets.push(iflet);
}
IfLetOrExpr::Expr(expr) => {
return Ok(Rule {
pattern,
iflets,
expr,
pos,
prio,
});
}
}
}
}
fn parse_pattern(&mut self) -> Result<Pattern> {
@@ -452,31 +480,52 @@ impl<'a> Parser<'a> {
}
}
fn parse_iflet_or_expr(&mut self) -> Result<IfLetOrExpr> {
let pos = self.pos();
if self.is_lparen() {
self.lparen()?;
let ret = if self.is_sym_str("if-let") {
self.symbol()?;
IfLetOrExpr::IfLet(self.parse_iflet()?)
} else if self.is_sym_str("if") {
// Shorthand form: `(if (x))` desugars to `(if-let _
// (x))`.
self.symbol()?;
IfLetOrExpr::IfLet(self.parse_iflet_if()?)
} else {
IfLetOrExpr::Expr(self.parse_expr_inner_parens(pos)?)
};
self.rparen()?;
Ok(ret)
} else {
self.parse_expr().map(|expr| IfLetOrExpr::Expr(expr))
}
}
fn parse_iflet(&mut self) -> Result<IfLet> {
let pos = self.pos();
let pattern = self.parse_pattern()?;
let expr = self.parse_expr()?;
Ok(IfLet { pattern, expr, pos })
}
fn parse_iflet_if(&mut self) -> Result<IfLet> {
let pos = self.pos();
let expr = self.parse_expr()?;
Ok(IfLet {
pattern: Pattern::Wildcard { pos },
expr,
pos,
})
}
fn parse_expr(&mut self) -> Result<Expr> {
let pos = self.pos();
if self.is_lparen() {
self.lparen()?;
if self.is_sym_str("let") {
self.symbol()?;
self.lparen()?;
let mut defs = vec![];
while !self.is_rparen() {
let def = self.parse_letdef()?;
defs.push(def);
}
self.rparen()?;
let body = Box::new(self.parse_expr()?);
self.rparen()?;
Ok(Expr::Let { defs, body, pos })
} else {
let sym = self.parse_ident()?;
let mut args = vec![];
while !self.is_rparen() {
args.push(self.parse_expr()?);
}
self.rparen()?;
Ok(Expr::Term { sym, args, pos })
}
let ret = self.parse_expr_inner_parens(pos)?;
self.rparen()?;
Ok(ret)
} else if self.is_sym_str("#t") {
self.symbol()?;
Ok(Expr::ConstInt { val: 1, pos })
@@ -497,6 +546,28 @@ impl<'a> Parser<'a> {
}
}
fn parse_expr_inner_parens(&mut self, pos: Pos) -> Result<Expr> {
if self.is_sym_str("let") {
self.symbol()?;
self.lparen()?;
let mut defs = vec![];
while !self.is_rparen() {
let def = self.parse_letdef()?;
defs.push(def);
}
self.rparen()?;
let body = Box::new(self.parse_expr()?);
Ok(Expr::Let { defs, body, pos })
} else {
let sym = self.parse_ident()?;
let mut args = vec![];
while !self.is_rparen() {
args.push(self.parse_expr()?);
}
Ok(Expr::Term { sym, args, pos })
}
}
fn parse_letdef(&mut self) -> Result<LetDef> {
let pos = self.pos();
self.lparen()?;

View File

@@ -232,6 +232,8 @@ pub enum TermKind {
},
/// A term declared via a `(decl ...)` form.
Decl {
/// Whether the term is marked as `pure`.
pure: bool,
/// The kind of this term's constructor, if any.
constructor_kind: Option<ConstructorKind>,
/// The kind of this term's extractor, if any.
@@ -392,13 +394,14 @@ impl Term {
match &self.kind {
TermKind::Decl {
constructor_kind: Some(ConstructorKind::ExternalConstructor { name }),
pure,
..
} => Some(ExternalSig {
func_name: tyenv.syms[name.index()].clone(),
full_name: format!("C::{}", tyenv.syms[name.index()]),
param_tys: self.arg_tys.clone(),
ret_tys: vec![self.ret_ty],
infallible: true,
infallible: !pure,
}),
TermKind::Decl {
constructor_kind: Some(ConstructorKind::InternalConstructor { .. }),
@@ -410,6 +413,10 @@ impl Term {
full_name: name,
param_tys: self.arg_tys.clone(),
ret_tys: vec![self.ret_ty],
// Internal constructors are always fallible, even
// if not pure, because ISLE allows partial
// matching at the toplevel (an entry point can
// fail to rewrite).
infallible: false,
})
}
@@ -425,6 +432,8 @@ pub struct Rule {
pub id: RuleId,
/// The left-hand side pattern that this rule matches.
pub lhs: Pattern,
/// Any subpattern "if-let" clauses.
pub iflets: Vec<IfLet>,
/// The right-hand side expression that this rule evaluates upon successful
/// match.
pub rhs: Expr,
@@ -434,6 +443,18 @@ pub struct Rule {
pub pos: Pos,
}
/// An `if-let` clause with a subpattern match on an expr after the
/// main LHS matches.
#[derive(Clone, Debug)]
pub struct IfLet {
/// The left-hand side pattern that this `if-let` clause matches
/// against the expression below.
pub lhs: Pattern,
/// The right-hand side expression that this pattern
/// evaluates. Must be pure.
pub rhs: Expr,
}
/// A left-hand side pattern of some rule.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Pattern {
@@ -861,6 +882,7 @@ impl TermEnv {
kind: TermKind::Decl {
constructor_kind: None,
extractor_kind: None,
pure: decl.pure,
},
});
}
@@ -1337,6 +1359,18 @@ impl TermEnv {
}
};
let pure = match &self.terms[rule_term.index()].kind {
&TermKind::Decl { pure, .. } => pure,
_ => {
tyenv.report_error(
pos,
"Cannot define a rule on a left-hand-side that is an enum variant"
.to_string(),
);
continue;
}
};
let (lhs, ty) = unwrap_or_continue!(self.translate_pattern(
tyenv,
rule_term,
@@ -1345,17 +1379,25 @@ impl TermEnv {
&mut bindings,
/* is_root = */ true,
));
let iflets = unwrap_or_continue!(self.translate_iflets(
tyenv,
rule_term,
&rule.iflets[..],
&mut bindings,
));
let rhs = unwrap_or_continue!(self.translate_expr(
tyenv,
&rule.expr,
ty,
&mut bindings
Some(ty),
&mut bindings,
pure,
));
let rid = RuleId(self.rules.len());
self.rules.push(Rule {
id: rid,
lhs,
iflets,
rhs,
prio: rule.prio,
pos,
@@ -1829,7 +1871,13 @@ impl TermEnv {
return None;
}
let ty = expected_ty.unwrap();
let expr = self.translate_expr(tyenv, expr, expected_ty.unwrap(), bindings)?;
let expr = self.translate_expr(
tyenv,
expr,
expected_ty,
bindings,
/* pure = */ true,
)?;
Some((TermArgPattern::Expr(expr), ty))
}
}
@@ -1863,8 +1911,9 @@ impl TermEnv {
&self,
tyenv: &mut TypeEnv,
expr: &ast::Expr,
ty: TypeId,
ty: Option<TypeId>,
bindings: &mut Bindings,
pure: bool,
) -> Option<Expr> {
log::trace!("translate_expr: {:?}", expr);
match expr {
@@ -1890,26 +1939,44 @@ impl TermEnv {
// are doing an implicit conversion. Report an error
// if types don't match and no conversion is possible.
let ret_ty = self.terms[tid.index()].ret_ty;
let ty = if ret_ty != ty {
let ty = if ty.is_some() && ret_ty != ty.unwrap() {
// Is there a converter for this type mismatch?
if let Some(expanded_expr) =
self.maybe_implicit_convert_expr(tyenv, expr, ret_ty, ty)
self.maybe_implicit_convert_expr(tyenv, expr, ret_ty, ty.unwrap())
{
return self.translate_expr(tyenv, &expanded_expr, ty, bindings);
return self.translate_expr(tyenv, &expanded_expr, ty, bindings, pure);
}
tyenv.report_error(
pos,
format!("Mismatched types: expression expects type '{}' but term has return type '{}'",
tyenv.types[ty.index()].name(tyenv),
tyenv.types[ty.unwrap().index()].name(tyenv),
tyenv.types[ret_ty.index()].name(tyenv)));
// Keep going, to discover more errors.
ret_ty
} else {
ty
ret_ty
};
// Check that the term's constructor is pure.
match &self.terms[tid.index()].kind {
TermKind::Decl {
pure: ctor_is_pure, ..
} => {
if pure && !ctor_is_pure {
tyenv.report_error(
pos,
format!(
"Used non-pure constructor '{}' in pure expression context",
tyenv.syms[name.index()]
),
);
}
}
_ => {}
}
// Check that we have the correct argument count.
if self.terms[tid.index()].arg_tys.len() != args.len() {
tyenv.report_error(
@@ -1928,8 +1995,13 @@ impl TermEnv {
for (i, arg) in args.iter().enumerate() {
let term = unwrap_or_continue!(self.terms.get(tid.index()));
let arg_ty = unwrap_or_continue!(term.arg_tys.get(i).copied());
let subexpr =
unwrap_or_continue!(self.translate_expr(tyenv, arg, arg_ty, bindings));
let subexpr = unwrap_or_continue!(self.translate_expr(
tyenv,
arg,
Some(arg_ty),
bindings,
pure
));
subexprs.push(subexpr);
}
@@ -1947,12 +2019,12 @@ impl TermEnv {
};
// Verify type. Maybe do an implicit conversion.
if bv.ty != ty {
if ty.is_some() && bv.ty != ty.unwrap() {
// Is there a converter for this type mismatch?
if let Some(expanded_expr) =
self.maybe_implicit_convert_expr(tyenv, expr, bv.ty, ty)
self.maybe_implicit_convert_expr(tyenv, expr, bv.ty, ty.unwrap())
{
return self.translate_expr(tyenv, &expanded_expr, ty, bindings);
return self.translate_expr(tyenv, &expanded_expr, ty, bindings, pure);
}
tyenv.report_error(
@@ -1961,7 +2033,7 @@ impl TermEnv {
"Variable '{}' has type {} but we need {} in context",
name.0,
tyenv.types[bv.ty.index()].name(tyenv),
tyenv.types[ty.index()].name(tyenv)
tyenv.types[ty.unwrap().index()].name(tyenv)
),
);
}
@@ -1969,6 +2041,15 @@ impl TermEnv {
Some(Expr::Var(bv.ty, bv.id))
}
&ast::Expr::ConstInt { val, pos } => {
if ty.is_none() {
tyenv.report_error(
pos,
"integer literal in a context that needs an explicit type".to_string(),
);
return None;
}
let ty = ty.unwrap();
if !tyenv.types[ty.index()].is_prim() {
tyenv.report_error(
pos,
@@ -1990,19 +2071,19 @@ impl TermEnv {
return None;
}
};
if const_ty != ty {
if ty.is_some() && const_ty != ty.unwrap() {
tyenv.report_error(
pos,
format!(
"Constant '{}' has wrong type: expected {}, but is actually {}",
tyenv.syms[val.index()],
tyenv.types[ty.index()].name(tyenv),
tyenv.types[ty.unwrap().index()].name(tyenv),
tyenv.types[const_ty.index()].name(tyenv)
),
);
return None;
}
Some(Expr::ConstPrim(ty, val))
Some(Expr::ConstPrim(const_ty, val))
}
&ast::Expr::Let {
ref defs,
@@ -2043,20 +2124,24 @@ impl TermEnv {
};
// Evaluate the variable's value.
let val = Box::new(unwrap_or_continue!(
self.translate_expr(tyenv, &def.val, tid, bindings)
));
let val = Box::new(unwrap_or_continue!(self.translate_expr(
tyenv,
&def.val,
Some(tid),
bindings,
pure
)));
// Bind the var with the given type.
let id = VarId(bindings.next_var);
bindings.next_var += 1;
bindings.vars.push(BoundVar { name, id, ty: tid });
let_defs.push((id, ty, val));
let_defs.push((id, tid, val));
}
// Evaluate the body, expecting the type of the overall let-expr.
let body = Box::new(self.translate_expr(tyenv, body, ty, bindings)?);
let body = Box::new(self.translate_expr(tyenv, body, ty, bindings, pure)?);
let body_ty = body.ty();
// Pop the bindings.
@@ -2070,6 +2155,45 @@ impl TermEnv {
}
}
}
fn translate_iflets(
&self,
tyenv: &mut TypeEnv,
rule_term: TermId,
iflets: &[ast::IfLet],
bindings: &mut Bindings,
) -> Option<Vec<IfLet>> {
let mut translated = vec![];
for iflet in iflets {
translated.push(unwrap_or_continue!(
self.translate_iflet(tyenv, rule_term, iflet, bindings)
));
}
Some(translated)
}
fn translate_iflet(
&self,
tyenv: &mut TypeEnv,
rule_term: TermId,
iflet: &ast::IfLet,
bindings: &mut Bindings,
) -> Option<IfLet> {
// Translate the expr first. Ensure it's pure.
let rhs =
self.translate_expr(tyenv, &iflet.expr, None, bindings, /* pure = */ true)?;
let ty = rhs.ty();
let (lhs, _lhs_ty) = self.translate_pattern(
tyenv,
rule_term,
&iflet.pattern,
Some(ty),
bindings,
/* is_root = */ true,
)?;
Some(IfLet { lhs, rhs })
}
}
#[cfg(test)]