From 84b7612b98f53be1c0e9f42d99f07a21994107c3 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 29 Jun 2021 17:00:43 -0700 Subject: [PATCH 01/95] Initial public commit of ISLE prototype DSL compiler. --- cranelift/isle/.gitignore | 3 + cranelift/isle/Cargo.lock | 199 ++++++ cranelift/isle/Cargo.toml | 11 + cranelift/isle/examples/test.isle | 12 + cranelift/isle/src/ast.rs | 135 ++++ cranelift/isle/src/compile.rs | 111 +++ cranelift/isle/src/error.rs | 48 ++ cranelift/isle/src/ir.rs | 1089 +++++++++++++++++++++++++++++ cranelift/isle/src/lexer.rs | 241 +++++++ cranelift/isle/src/lower.rs | 33 + cranelift/isle/src/main.rs | 28 + cranelift/isle/src/parser.rs | 429 ++++++++++++ cranelift/isle/src/sema.rs | 862 +++++++++++++++++++++++ 13 files changed, 3201 insertions(+) create mode 100644 cranelift/isle/.gitignore create mode 100644 cranelift/isle/Cargo.lock create mode 100644 cranelift/isle/Cargo.toml create mode 100644 cranelift/isle/examples/test.isle create mode 100644 cranelift/isle/src/ast.rs create mode 100644 cranelift/isle/src/compile.rs create mode 100644 cranelift/isle/src/error.rs create mode 100644 cranelift/isle/src/ir.rs create mode 100644 cranelift/isle/src/lexer.rs create mode 100644 cranelift/isle/src/lower.rs create mode 100644 cranelift/isle/src/main.rs create mode 100644 cranelift/isle/src/parser.rs create mode 100644 cranelift/isle/src/sema.rs diff --git a/cranelift/isle/.gitignore b/cranelift/isle/.gitignore new file mode 100644 index 0000000000..3110c83344 --- /dev/null +++ b/cranelift/isle/.gitignore @@ -0,0 +1,3 @@ +/target +*~ +.*.swp diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock new file mode 100644 index 0000000000..b890d9e546 --- /dev/null +++ b/cranelift/isle/Cargo.lock @@ -0,0 +1,199 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "isle" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "thiserror", +] + +[[package]] +name = "libc" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "syn" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml new file mode 100644 index 0000000000..8774800064 --- /dev/null +++ b/cranelift/isle/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "isle" +version = "0.1.0" +authors = ["Chris Fallin "] +edition = "2018" +license = "Apache-2.0 WITH LLVM-exception" + +[dependencies] +log = "0.4" +env_logger = "0.8" +thiserror = "1.0" diff --git a/cranelift/isle/examples/test.isle b/cranelift/isle/examples/test.isle new file mode 100644 index 0000000000..1ea1c3ce98 --- /dev/null +++ b/cranelift/isle/examples/test.isle @@ -0,0 +1,12 @@ +(type u32 (primitive u32)) +(type A (enum (A1 (x u32)) (A2 (x u32)))) +(type B (enum (B1 (x u32)) (B2 (x u32)))) + +(decl Input (A) u32) +(extractor Input get_input) ;; fn get_input(ctx: &mut C, ret: u32) -> Option<(A,)> + +(decl Lower (A) B) + +(rule + (Lower (A.A1 sub @ (Input (A.A2 42)))) + (B.B2 sub)) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs new file mode 100644 index 0000000000..97b7facffa --- /dev/null +++ b/cranelift/isle/src/ast.rs @@ -0,0 +1,135 @@ +use crate::lexer::Pos; + +/// The parsed form of an ISLE file. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Defs { + pub defs: Vec, + pub filename: String, +} + +/// One toplevel form in an ISLE file. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Def { + Type(Type), + Rule(Rule), + Decl(Decl), + Extern(Extern), +} + +/// An identifier -- a variable, term symbol, or type. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Ident(pub String); + +/// A declaration of a type. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Type { + pub name: Ident, + pub is_extern: bool, + pub ty: TypeValue, + pub pos: Pos, +} + +/// The actual type-value: a primitive or an enum with variants. +/// +/// TODO: add structs as well? +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TypeValue { + Primitive(Ident), + Enum(Vec), +} + +/// One variant of an enum type. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Variant { + pub name: Ident, + pub fields: Vec, +} + +/// One field of an enum variant. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Field { + pub name: Ident, + pub ty: Ident, +} + +/// A declaration of a term with its argument and return types. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Decl { + pub term: Ident, + pub arg_tys: Vec, + pub ret_ty: Ident, + pub pos: Pos, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Rule { + pub pattern: Pattern, + pub expr: Expr, + pub pos: Pos, + pub prio: Option, +} + +/// A pattern: the left-hand side of a rule. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Pattern { + /// An operator that binds a variable to a subterm and match the + /// subpattern. + BindPattern { var: Ident, subpat: Box }, + /// A variable that has already been bound (`=x` syntax). + Var { var: Ident }, + /// An operator that matches a constant integer value. + ConstInt { val: i64 }, + /// An application of a type variant or term. + Term { sym: Ident, args: Vec }, + /// An operator that matches anything. + Wildcard, +} + +/// An expression: the right-hand side of a rule. +/// +/// Note that this *almost* looks like a core Lisp or lambda calculus, +/// except that there is no abstraction (lambda). This first-order +/// limit is what makes it analyzable. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Expr { + /// A term: `(sym args...)`. + Term { sym: Ident, args: Vec }, + /// A variable use. + Var { name: Ident }, + /// A constant integer. + ConstInt { val: i64 }, + /// The `(let ((var ty val)*) body)` form. + Let { defs: Vec, body: Box }, +} + +/// One variable locally bound in a `(let ...)` expression. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct LetDef { + pub var: Ident, + pub ty: Ident, + pub val: Box, +} + +/// An external binding: an extractor or constructor function attached +/// to a term. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Extern { + /// An external extractor: `(extractor Term rustfunc)` form. + Extractor { + /// The term to which this external extractor is attached. + term: Ident, + /// The Rust function name. + func: Ident, + /// The position of this decl. + pos: Pos, + }, + /// An external constructor: `(constructor Term rustfunc)` form. + Constructor { + /// The term to which this external constructor is attached. + term: Ident, + /// The Rust function name. + func: Ident, + /// The position of this decl. + pos: Pos, + }, +} diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/src/compile.rs new file mode 100644 index 0000000000..0aeea048b9 --- /dev/null +++ b/cranelift/isle/src/compile.rs @@ -0,0 +1,111 @@ +//! Compilation process, from AST to Sema to Sequences of Insts. + +use crate::error::*; +use crate::{ast, ir, sema}; +use std::collections::HashMap; + +/// A Compiler manages the compilation pipeline from AST to Sequences. +pub struct Compiler<'a> { + ast: &'a ast::Defs, + type_env: sema::TypeEnv, + term_env: sema::TermEnv, + seqs: Vec, + // TODO: if this becomes a perf issue, then build a better data + // structure. For now we index on root term/variant. + // + // TODO: index at callsites (extractors/constructors) too. We'll + // need tree-summaries of arg and expected return value at each + // callsite. + term_db: HashMap, +} + +#[derive(Clone, Debug, Default)] +struct TermData { + producers: Vec<(ir::TreeSummary, sema::RuleId)>, + consumers: Vec<(ir::TreeSummary, sema::RuleId)>, + has_constructor: bool, + has_extractor: bool, +} + +pub type CompileResult = Result; + +impl<'a> Compiler<'a> { + pub fn new(ast: &'a ast::Defs) -> CompileResult> { + let mut type_env = sema::TypeEnv::from_ast(ast)?; + let term_env = sema::TermEnv::from_ast(&mut type_env, ast)?; + Ok(Compiler { + ast, + type_env, + term_env, + seqs: vec![], + term_db: HashMap::new(), + }) + } + + pub fn build_sequences(&mut self) -> CompileResult<()> { + for rid in 0..self.term_env.rules.len() { + let rid = sema::RuleId(rid); + let seq = ir::Sequence::from_rule(&self.type_env, &self.term_env, rid); + self.seqs.push(seq); + } + Ok(()) + } + + pub fn collect_tree_summaries(&mut self) -> CompileResult<()> { + // For each rule, compute summaries of its LHS and RHS, then + // index it in the appropriate TermData. + for (i, seq) in self.seqs.iter().enumerate() { + let rule_id = sema::RuleId(i); + let consumer_summary = seq.input_tree_summary(); + let producer_summary = seq.output_tree_summary(); + if let Some(consumer_root_term) = consumer_summary.root() { + let consumer_termdb = self + .term_db + .entry(consumer_root_term.clone()) + .or_insert_with(|| Default::default()); + consumer_termdb.consumers.push((consumer_summary, rule_id)); + } + if let Some(producer_root_term) = producer_summary.root() { + let producer_termdb = self + .term_db + .entry(producer_root_term.clone()) + .or_insert_with(|| Default::default()); + producer_termdb.consumers.push((producer_summary, rule_id)); + } + } + + // For each term, if a constructor and/or extractor is + // present, note that. + for term in &self.term_env.terms { + if let sema::TermKind::Regular { + extractor, + constructor, + } = term.kind + { + if !extractor.is_some() && !constructor.is_some() { + continue; + } + let entry = self + .term_db + .entry(ir::TermOrVariant::Term(term.id)) + .or_insert_with(|| Default::default()); + if extractor.is_some() { + entry.has_extractor = true; + } + if constructor.is_some() { + entry.has_constructor = true; + } + } + } + + Ok(()) + } + + pub fn inline_internal_terms(&mut self) -> CompileResult<()> { + unimplemented!() + } + + pub fn to_sequences(self) -> Vec { + self.seqs + } +} diff --git a/cranelift/isle/src/error.rs b/cranelift/isle/src/error.rs new file mode 100644 index 0000000000..2399fb1231 --- /dev/null +++ b/cranelift/isle/src/error.rs @@ -0,0 +1,48 @@ +//! Error types. + +use crate::lexer::Pos; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Parse error")] + ParseError(#[from] ParseError), + #[error("Semantic error")] + SemaError(#[from] SemaError), + #[error("IO error")] + IoError(#[from] std::io::Error), +} + +#[derive(Clone, Debug, Error)] +pub struct ParseError { + pub msg: String, + pub filename: String, + pub pos: Pos, +} + +#[derive(Clone, Debug, Error)] +pub struct SemaError { + pub msg: String, + pub filename: String, + pub pos: Pos, +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}:{}:{}: {}", + self.filename, self.pos.line, self.pos.col, self.msg + ) + } +} + +impl std::fmt::Display for SemaError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}:{}:{}: {}", + self.filename, self.pos.line, self.pos.col, self.msg + ) + } +} diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs new file mode 100644 index 0000000000..3bf6c59a4c --- /dev/null +++ b/cranelift/isle/src/ir.rs @@ -0,0 +1,1089 @@ +//! Lowered matching IR. + +use crate::declare_id; +use crate::sema::*; +use std::collections::hash_map::Entry as HashEntry; +use std::collections::HashMap; + +declare_id!(InstId); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Value(InstId, usize); + +/// A single node in the sea-of-nodes. Each node produces one value. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Inst { + /// Get the input root-term value. + Arg { ty: TypeId }, + + /// Set the return value. Produces no values. + Return { ty: TypeId, value: Value }, + + /// Match a value as equal to another value. Produces no values. + MatchEqual { a: Value, b: Value, ty: TypeId }, + + /// Try matching the given value as the given integer. Produces no values. + MatchInt { + input: Value, + ty: TypeId, + int_val: i64, + }, + + /// Try matching the given value as the given variant, producing + /// `|arg_tys|` values as output. + MatchVariant { + input: Value, + input_ty: TypeId, + arg_tys: Vec, + variant: VariantId, + }, + + /// Invoke an extractor, taking the given value as input and + /// producing `|arg_tys|` values as output. + Extract { + input: Value, + input_ty: TypeId, + arg_tys: Vec, + term: TermId, + }, + + /// Produce a constant integer. + ConstInt { ty: TypeId, val: i64 }, + + /// Create a variant. + CreateVariant { + inputs: Vec<(Value, TypeId)>, + ty: TypeId, + variant: VariantId, + }, + + /// Invoke a constructor. + Construct { + inputs: Vec<(Value, TypeId)>, + ty: TypeId, + term: TermId, + }, + + /// Copy a value. Used mainly when rewriting/inlining. + Copy { ty: TypeId, val: Value }, + + /// A non-operation (nop). Used to "nop out" unused instructions + /// without renumbering all values. + Nop, +} + +impl Inst { + fn map_values Value>(&self, f: F) -> Self { + match self { + &Inst::Arg { ty } => Inst::Arg { ty }, + &Inst::Return { ty, value } => Inst::Return { + ty, + value: f(value), + }, + &Inst::MatchEqual { a, b, ty } => Inst::MatchEqual { + a: f(a), + b: f(b), + ty, + }, + &Inst::MatchInt { input, ty, int_val } => Inst::MatchInt { + input: f(input), + ty, + int_val, + }, + &Inst::MatchVariant { + input, + input_ty, + ref arg_tys, + variant, + } => Inst::MatchVariant { + input: f(input), + input_ty, + arg_tys: arg_tys.clone(), + variant, + }, + &Inst::Extract { + input, + input_ty, + ref arg_tys, + term, + } => Inst::Extract { + input: f(input), + input_ty, + arg_tys: arg_tys.clone(), + term, + }, + &Inst::ConstInt { ty, val } => Inst::ConstInt { ty, val }, + &Inst::CreateVariant { + ref inputs, + ty, + variant, + } => Inst::CreateVariant { + inputs: inputs + .iter() + .map(|(i, ty)| (f(*i), *ty)) + .collect::>(), + ty, + variant, + }, + &Inst::Construct { + ref inputs, + ty, + term, + } => Inst::Construct { + inputs: inputs + .iter() + .map(|(i, ty)| (f(*i), *ty)) + .collect::>(), + ty, + term, + }, + &Inst::Copy { ty, val } => Inst::Copy { ty, val: f(val) }, + &Inst::Nop => Inst::Nop, + } + } + + fn map_insts InstId>(&self, f: F) -> Self { + self.map_values(|val| Value(f(val.0), val.1)) + } + + fn num_results(&self) -> usize { + match self { + &Inst::Arg { .. } + | &Inst::ConstInt { .. } + | &Inst::Construct { .. } + | &Inst::CreateVariant { .. } + | &Inst::Copy { .. } => 1, + &Inst::Return { .. } | &Inst::MatchEqual { .. } | &Inst::MatchInt { .. } => 0, + &Inst::Extract { ref arg_tys, .. } | &Inst::MatchVariant { ref arg_tys, .. } => { + arg_tys.len() + } + &Inst::Nop => 0, + } + } +} + +impl Value { + fn map_inst InstId>(&self, f: F) -> Self { + Value(f(self.0), self.1) + } +} + +/// A linear sequence of instructions that either convert an input +/// value to an output value, or fail. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct Sequence { + /// Instruction sequence. InstId indexes into this sequence. + insts: Vec, +} + +impl Sequence { + fn add_inst(&mut self, inst: Inst) -> InstId { + let id = InstId(self.insts.len()); + self.insts.push(inst); + id + } + + fn add_arg(&mut self, ty: TypeId) -> Value { + let inst = InstId(self.insts.len()); + self.add_inst(Inst::Arg { ty }); + Value(inst, 0) + } + + fn add_return(&mut self, ty: TypeId, value: Value) { + self.add_inst(Inst::Return { ty, value }); + } + + fn add_match_equal(&mut self, a: Value, b: Value, ty: TypeId) { + self.add_inst(Inst::MatchEqual { a, b, ty }); + } + + fn add_match_int(&mut self, input: Value, ty: TypeId, int_val: i64) { + self.add_inst(Inst::MatchInt { input, ty, int_val }); + } + + fn add_match_variant( + &mut self, + input: Value, + input_ty: TypeId, + arg_tys: &[TypeId], + variant: VariantId, + ) -> Vec { + let inst = InstId(self.insts.len()); + let mut outs = vec![]; + for (i, _arg_ty) in arg_tys.iter().enumerate() { + let val = Value(inst, i); + outs.push(val); + } + let arg_tys = arg_tys.iter().cloned().collect(); + self.add_inst(Inst::MatchVariant { + input, + input_ty, + arg_tys, + variant, + }); + outs + } + + fn add_extract( + &mut self, + input: Value, + input_ty: TypeId, + arg_tys: &[TypeId], + term: TermId, + ) -> Vec { + let inst = InstId(self.insts.len()); + let mut outs = vec![]; + for (i, _arg_ty) in arg_tys.iter().enumerate() { + let val = Value(inst, i); + outs.push(val); + } + let arg_tys = arg_tys.iter().cloned().collect(); + self.add_inst(Inst::Extract { + input, + input_ty, + arg_tys, + term, + }); + outs + } + + fn add_const_int(&mut self, ty: TypeId, val: i64) -> Value { + let inst = InstId(self.insts.len()); + self.add_inst(Inst::ConstInt { ty, val }); + Value(inst, 0) + } + + fn add_create_variant( + &mut self, + inputs: &[(Value, TypeId)], + ty: TypeId, + variant: VariantId, + ) -> Value { + let inst = InstId(self.insts.len()); + let inputs = inputs.iter().cloned().collect(); + self.add_inst(Inst::CreateVariant { + inputs, + ty, + variant, + }); + Value(inst, 0) + } + + fn add_construct(&mut self, inputs: &[(Value, TypeId)], ty: TypeId, term: TermId) -> Value { + let inst = InstId(self.insts.len()); + let inputs = inputs.iter().cloned().collect(); + self.add_inst(Inst::Construct { inputs, ty, term }); + Value(inst, 0) + } + + fn gen_pattern( + &mut self, + input: Value, + typeenv: &TypeEnv, + termenv: &TermEnv, + pat: &Pattern, + vars: &mut HashMap, + ) { + match pat { + &Pattern::BindPattern(_ty, var, ref subpat) => { + // Bind the appropriate variable and recurse. + assert!(!vars.contains_key(&var)); + vars.insert(var, input); + self.gen_pattern(input, typeenv, termenv, &*subpat, vars); + } + &Pattern::Var(ty, var) => { + // Assert that the value matches the existing bound var. + let var_val = vars + .get(&var) + .cloned() + .expect("Variable should already be bound"); + self.add_match_equal(input, var_val, ty); + } + &Pattern::ConstInt(ty, value) => { + // Assert that the value matches the constant integer. + self.add_match_int(input, ty, value); + } + &Pattern::Term(ty, term, ref args) => { + // Determine whether the term has an external extractor or not. + let termdata = &termenv.terms[term.index()]; + let arg_tys = &termdata.arg_tys[..]; + match &termdata.kind { + &TermKind::EnumVariant { variant } => { + let arg_values = self.add_match_variant(input, ty, arg_tys, variant); + for (subpat, value) in args.iter().zip(arg_values.into_iter()) { + self.gen_pattern(value, typeenv, termenv, subpat, vars); + } + } + &TermKind::Regular { .. } => { + let arg_values = self.add_extract(input, ty, arg_tys, term); + for (subpat, value) in args.iter().zip(arg_values.into_iter()) { + self.gen_pattern(value, typeenv, termenv, subpat, vars); + } + } + } + } + &Pattern::Wildcard(_ty) => { + // Nothing! + } + } + } + + fn gen_expr( + &mut self, + typeenv: &TypeEnv, + termenv: &TermEnv, + expr: &Expr, + vars: &HashMap, + ) -> Value { + match expr { + &Expr::ConstInt(ty, val) => self.add_const_int(ty, val), + &Expr::Let(_ty, ref bindings, ref subexpr) => { + let mut vars = vars.clone(); + for &(var, _var_ty, ref var_expr) in bindings { + let var_value = self.gen_expr(typeenv, termenv, &*var_expr, &vars); + vars.insert(var, var_value); + } + self.gen_expr(typeenv, termenv, &*subexpr, &vars) + } + &Expr::Var(_ty, var_id) => vars.get(&var_id).cloned().unwrap(), + &Expr::Term(ty, term, ref arg_exprs) => { + let termdata = &termenv.terms[term.index()]; + let mut arg_values_tys = vec![]; + for (arg_ty, arg_expr) in termdata.arg_tys.iter().cloned().zip(arg_exprs.iter()) { + arg_values_tys + .push((self.gen_expr(typeenv, termenv, &*arg_expr, &vars), arg_ty)); + } + match &termdata.kind { + &TermKind::EnumVariant { variant } => { + self.add_create_variant(&arg_values_tys[..], ty, variant) + } + &TermKind::Regular { .. } => self.add_construct(&arg_values_tys[..], ty, term), + } + } + } + } +} + +impl Sequence { + /// Build a sequence from a rule. + pub fn from_rule(tyenv: &TypeEnv, termenv: &TermEnv, rule: RuleId) -> Sequence { + let mut seq: Sequence = Default::default(); + + // Lower the pattern, starting from the root input value. + let ruledata = &termenv.rules[rule.index()]; + let input_ty = ruledata.lhs.ty(); + let input = seq.add_arg(input_ty); + let mut vars = HashMap::new(); + seq.gen_pattern(input, tyenv, termenv, &ruledata.lhs, &mut vars); + + // Lower the expression, making use of the bound variables + // from the pattern. + let rhs_root = seq.gen_expr(tyenv, termenv, &ruledata.rhs, &vars); + // Return the root RHS value. + let output_ty = ruledata.rhs.ty(); + seq.add_return(output_ty, rhs_root); + + seq + } + + /// Inline sequence(s) in place of given instructions. + pub fn inline(&self, inlines: Vec<(InstId, &'_ Sequence)>) -> Sequence { + let mut seq: Sequence = Default::default(); + // Map from inst ID in this seq to inst ID in final seq. + let mut inst_map: HashMap = HashMap::new(); + + let mut next_inline = 0; + for (id, inst) in self.insts.iter().enumerate() { + let orig_inst_id = InstId(id); + + // If this is an inlining point, do the inlining. The + // inlining point must be at a Construct or Extract call. + // + // - For a Construct inlining, we emit the Construct + // *first*, taking its output value as the arg for the + // invoked sequence. The returned value will in turn be + // substituted for that value at the end of inlining. + // + // - For an Extract inlining, we emit the sequence first, + // taking the input of the Extract as the arg for the + // invoked sequence. The returned value will then be the + // new input to the Extract. + + if next_inline < inlines.len() && inlines[next_inline].0 == orig_inst_id { + let inlined_seq = &inlines[next_inline].1; + next_inline += 1; + + let (arg, arg_ty) = match inst { + &Inst::Construct { ty, .. } => { + // Emit the Construct, mapping its input + // values across the mapping, and saving its + // output as the arg for the inlined sequence. + let inst = inst.map_insts(|id| { + inst_map + .get(&id) + .cloned() + .expect("Should have inst mapping") + }); + let new_inst_id = seq.add_inst(inst); + (Value(new_inst_id, 0), ty) + } + &Inst::Extract { + input, input_ty, .. + } => { + // Map the input and save it as the arg, but + // don't emit the Extract yet. + ( + input.map_inst(|id| { + inst_map + .get(&id) + .cloned() + .expect("Should have inst mapping") + }), + input_ty, + ) + } + _ => panic!("Unexpected instruction {:?} at inlining point", inst), + }; + + // Copy the inlined insts into the output sequence. We + // map `Arg` to the input, and save the `Ret`, which + // must come last. + let mut inlined_inst_map: HashMap = HashMap::new(); + let mut ret: Option<(InstId, TypeId)> = None; + for (i, inst) in inlined_seq.insts.iter().enumerate() { + let inlined_orig_inst_id = InstId(i); + let new_inst_id = InstId(seq.insts.len()); + let inst = match inst { + &Inst::Return { ty, value } => { + let value = + value.map_inst(|id| inlined_inst_map.get(&id).cloned().unwrap()); + ret = Some((new_inst_id, ty)); + Inst::Copy { ty, val: value } + } + &Inst::Arg { ty } => { + assert_eq!(ty, arg_ty); + Inst::Copy { ty, val: arg } + } + _ => inst.map_insts(|id| inlined_inst_map.get(&id).cloned().unwrap()), + }; + let new_id = seq.add_inst(inst); + inlined_inst_map.insert(inlined_orig_inst_id, new_id); + } + + // Now, emit the Extract if appropriate (it comes + // after the inlined sequence, while Construct goes + // before), and map the old inst ID to the resulting + // output of either the Extract or the return above. + let final_inst_id = match inst { + &Inst::Extract { + input_ty, + ref arg_tys, + term, + .. + } => { + let input = Value(ret.unwrap().0, 0); + seq.add_inst(Inst::Extract { + input, + input_ty, + arg_tys: arg_tys.clone(), + term, + }) + } + &Inst::Construct { .. } => ret.unwrap().0, + _ => unreachable!(), + }; + + inst_map.insert(orig_inst_id, final_inst_id); + } else { + // Non-inlining-point instruction. Just copy over, + // mapping values as appropriate. + let inst = inst.map_insts(|id| { + inst_map + .get(&id) + .cloned() + .expect("inst ID should be present") + }); + let new_id = seq.add_inst(inst); + inst_map.insert(orig_inst_id, new_id); + } + } + + seq + } + + /// Perform constant-propagation / simplification across + /// construct/extract pairs, variants and integer values, and + /// copies. + pub fn simplify(&self) -> Option { + #[derive(Clone, Debug)] + enum SymbolicValue { + Value(Value), + ConstInt(Value, i64), + Variant(Value, VariantId, Vec), + Term(Value, TermId, Vec), + } + impl SymbolicValue { + fn to_value(&self) -> Value { + match self { + &SymbolicValue::Value(v) => v, + &SymbolicValue::ConstInt(v, ..) => v, + &SymbolicValue::Variant(v, ..) => v, + &SymbolicValue::Term(v, ..) => v, + } + } + } + let mut value_map: HashMap = HashMap::new(); + let mut seq: Sequence = Default::default(); + + for (i, inst) in self.insts.iter().enumerate() { + let orig_inst_id = InstId(i); + match inst { + &Inst::Arg { .. } => { + let new_inst = seq.add_inst(inst.clone()); + value_map.insert( + Value(orig_inst_id, 0), + SymbolicValue::Value(Value(new_inst, 0)), + ); + } + &Inst::Return { ty, value } => { + let inst = Inst::Return { + ty, + value: value_map.get(&value).unwrap().to_value(), + }; + seq.add_inst(inst); + } + &Inst::MatchEqual { a, b, ty } => { + let sym_a = value_map.get(&a).unwrap(); + let sym_b = value_map.get(&b).unwrap(); + match (sym_a, sym_b) { + ( + &SymbolicValue::ConstInt(_, int_a), + &SymbolicValue::ConstInt(_, int_b), + ) => { + if int_a == int_b { + // No-op -- we can skip it. + continue; + } else { + // We can't possibly match! + return None; + } + } + ( + &SymbolicValue::Term(_, term_a, _), + &SymbolicValue::Term(_, term_b, _), + ) => { + if term_a != term_b { + return None; + } + } + ( + &SymbolicValue::Variant(_, var_a, _), + &SymbolicValue::Variant(_, var_b, _), + ) => { + if var_a != var_b { + return None; + } + } + _ => {} + } + let val_a = sym_a.to_value(); + let val_b = sym_b.to_value(); + seq.add_inst(Inst::MatchEqual { + a: val_a, + b: val_b, + ty, + }); + } + &Inst::MatchInt { input, int_val, ty } => { + let sym_input = value_map.get(&input).unwrap(); + match sym_input { + &SymbolicValue::ConstInt(_, const_val) => { + if int_val == const_val { + // No runtime check needed -- we can continue. + continue; + } else { + // Static mismatch, so we can remove this + // whole Sequence. + return None; + } + } + _ => {} + } + let val_input = sym_input.to_value(); + seq.add_inst(Inst::MatchInt { + input: val_input, + int_val, + ty, + }); + } + &Inst::MatchVariant { + input, + input_ty, + variant, + ref arg_tys, + } => { + let sym_input = value_map.get(&input).unwrap(); + match sym_input { + &SymbolicValue::Variant(_, val_variant, ref args) => { + if val_variant != variant { + return None; + } + // Variant matches: unpack args' symbolic values into results. + let args = args.clone(); + for (i, arg) in args.iter().enumerate() { + let val = Value(orig_inst_id, i); + value_map.insert(val, arg.clone()); + } + } + _ => { + let val_input = sym_input.to_value(); + let new_inst = seq.add_inst(Inst::MatchVariant { + input: val_input, + input_ty, + variant, + arg_tys: arg_tys.clone(), + }); + for i in 0..arg_tys.len() { + let val = Value(orig_inst_id, i); + let sym = SymbolicValue::Value(Value(new_inst, i)); + value_map.insert(val, sym); + } + } + } + } + &Inst::Extract { + input, + input_ty, + term, + ref arg_tys, + } => { + let sym_input = value_map.get(&input).unwrap(); + match sym_input { + &SymbolicValue::Term(_, val_term, ref args) => { + if val_term != term { + return None; + } + // Term matches: unpack args' symbolic values into results. + let args = args.clone(); + for (i, arg) in args.iter().enumerate() { + let val = Value(orig_inst_id, i); + value_map.insert(val, arg.clone()); + } + } + _ => { + let val_input = sym_input.to_value(); + let new_inst = seq.add_inst(Inst::Extract { + input: val_input, + input_ty, + term, + arg_tys: arg_tys.clone(), + }); + for i in 0..arg_tys.len() { + let val = Value(orig_inst_id, i); + let sym = SymbolicValue::Value(Value(new_inst, i)); + value_map.insert(val, sym); + } + } + } + } + &Inst::ConstInt { ty, val } => { + let new_inst = seq.add_inst(Inst::ConstInt { ty, val }); + value_map.insert( + Value(orig_inst_id, 0), + SymbolicValue::ConstInt(Value(new_inst, 0), val), + ); + } + &Inst::CreateVariant { + ref inputs, + variant, + ty, + } => { + let sym_inputs = inputs + .iter() + .map(|input| value_map.get(&input.0).cloned().unwrap()) + .collect::>(); + let inputs = sym_inputs + .iter() + .zip(inputs.iter()) + .map(|(si, (_, ty))| (si.to_value(), *ty)) + .collect::>(); + let new_inst = seq.add_inst(Inst::CreateVariant { + inputs, + variant, + ty, + }); + value_map.insert( + Value(orig_inst_id, 0), + SymbolicValue::Variant(Value(new_inst, 0), variant, sym_inputs), + ); + } + &Inst::Construct { + ref inputs, + term, + ty, + } => { + let sym_inputs = inputs + .iter() + .map(|input| value_map.get(&input.0).cloned().unwrap()) + .collect::>(); + let inputs = sym_inputs + .iter() + .zip(inputs.iter()) + .map(|(si, (_, ty))| (si.to_value(), *ty)) + .collect::>(); + let new_inst = seq.add_inst(Inst::Construct { inputs, term, ty }); + value_map.insert( + Value(orig_inst_id, 0), + SymbolicValue::Term(Value(new_inst, 0), term, sym_inputs), + ); + } + &Inst::Copy { val, .. } => { + let sym_value = value_map.get(&val).cloned().unwrap(); + value_map.insert(Value(orig_inst_id, 0), sym_value); + } + &Inst::Nop => {} + }; + } + + // Now do a pass backward to track which instructions are used. + let mut used = vec![false; seq.insts.len()]; + for (id, inst) in seq.insts.iter().enumerate().rev() { + // Mark roots as used unconditionally: Return, MatchEqual, + // MatchInt, MatchVariant. + match inst { + &Inst::Return { .. } + | &Inst::MatchEqual { .. } + | &Inst::MatchInt { .. } + | &Inst::MatchVariant { .. } => used[id] = true, + _ => {} + } + // If this instruction is not used, continue. + if !used[id] { + continue; + } + // Otherwise, mark all inputs as used as well. + match inst { + &Inst::Return { value, .. } => used[value.0.index()] = true, + &Inst::MatchEqual { a, b, .. } => { + used[a.0.index()] = true; + used[b.0.index()] = true; + } + &Inst::MatchInt { input, .. } + | &Inst::MatchVariant { input, .. } + | &Inst::Extract { input, .. } => { + used[input.0.index()] = true; + } + &Inst::CreateVariant { ref inputs, .. } | Inst::Construct { ref inputs, .. } => { + for input in inputs { + used[input.0 .0.index()] = true; + } + } + &Inst::Copy { val, .. } => { + used[val.0.index()] = true; + } + &Inst::Arg { .. } | &Inst::ConstInt { .. } => {} + &Inst::Nop => {} + } + } + + // Now, remove any non-used instructions. + for id in 0..seq.insts.len() { + if !used[id] { + seq.insts[id] = Inst::Nop; + } + } + + Some(seq) + } + + /// Build a tree summary of the output produced by a sequence. + pub fn output_tree_summary(&self) -> TreeSummary { + // Scan forward, building a TreeSummary for what is known + // about each value (a "lower bound" on its shape). + let mut value_summaries: HashMap = HashMap::new(); + for (id, inst) in self.insts.iter().enumerate() { + let inst_id = InstId(id); + match inst { + &Inst::Arg { .. } => { + value_summaries.insert(Value(inst_id, 0), TreeSummary::Other); + } + &Inst::Return { value, .. } => { + return value_summaries + .get(&value) + .cloned() + .unwrap_or(TreeSummary::Other); + } + &Inst::MatchEqual { .. } + | &Inst::MatchInt { .. } + | &Inst::MatchVariant { .. } + | &Inst::Extract { .. } => {} + &Inst::ConstInt { val, .. } => { + value_summaries.insert(Value(inst_id, 0), TreeSummary::ConstInt(val)); + } + &Inst::CreateVariant { + ref inputs, + variant, + .. + } => { + let args = inputs + .iter() + .map(|(val, _)| { + value_summaries + .get(&val) + .cloned() + .unwrap_or(TreeSummary::Other) + }) + .collect::>(); + value_summaries.insert(Value(inst_id, 0), TreeSummary::Variant(variant, args)); + } + &Inst::Construct { + ref inputs, term, .. + } => { + let args = inputs + .iter() + .map(|(val, _)| { + value_summaries + .get(&val) + .cloned() + .unwrap_or(TreeSummary::Other) + }) + .collect::>(); + value_summaries.insert(Value(inst_id, 0), TreeSummary::Term(term, args)); + } + &Inst::Copy { val, .. } => { + // Copy summary from input to output. + let input_value = value_summaries + .get(&val) + .cloned() + .unwrap_or(TreeSummary::Other); + value_summaries.insert(Value(inst_id, 0), input_value); + } + &Inst::Nop => {} + } + } + + panic!("Sequence did not end in Return") + } + + /// Build a tree summary of the input expected by a sequence. + pub fn input_tree_summary(&self) -> TreeSummary { + // Scan backward, building a TreeSummary for each value (a + // "lower bound" on what it must be to satisfy the sequence's + // conditions). + let mut value_summaries: HashMap = HashMap::new(); + for (id, inst) in self.insts.iter().enumerate().rev() { + let inst_id = InstId(id); + match inst { + &Inst::Arg { .. } => { + // Must *start* with Arg; otherwise we might have missed some condition. + assert_eq!(id, 0); + return value_summaries + .get(&Value(inst_id, 0)) + .cloned() + .unwrap_or(TreeSummary::Other); + } + &Inst::Return { .. } => {} + + &Inst::MatchEqual { a, b, .. } => { + if value_summaries.contains_key(&a) && !value_summaries.contains_key(&b) { + let val = value_summaries.get(&a).cloned().unwrap(); + value_summaries.insert(b, val); + } else if value_summaries.contains_key(&b) && !value_summaries.contains_key(&a) + { + let val = value_summaries.get(&b).cloned().unwrap(); + value_summaries.insert(a, val); + } else if value_summaries.contains_key(&a) && value_summaries.contains_key(&b) { + let val_a = value_summaries.get(&a).cloned().unwrap(); + let val_b = value_summaries.get(&b).cloned().unwrap(); + let combined = TreeSummary::Conjunction(vec![val_a, val_b]); + value_summaries.insert(a, combined.clone()); + value_summaries.insert(b, combined); + } + } + &Inst::MatchInt { input, int_val, .. } => { + value_summaries.insert(input, TreeSummary::ConstInt(int_val)); + } + &Inst::MatchVariant { + input, + variant, + ref arg_tys, + .. + } => { + let args = (0..arg_tys.len()) + .map(|i| Value(inst_id, i)) + .map(|val| { + value_summaries + .get(&val) + .cloned() + .unwrap_or(TreeSummary::Other) + }) + .collect::>(); + let summary = TreeSummary::Variant(variant, args); + match value_summaries.entry(input) { + HashEntry::Vacant(v) => { + v.insert(summary); + } + HashEntry::Occupied(mut o) => { + let combined = TreeSummary::Conjunction(vec![ + summary, + std::mem::replace(o.get_mut(), TreeSummary::Other), + ]); + *o.get_mut() = combined; + } + } + } + + &Inst::Extract { + input, + term, + ref arg_tys, + .. + } => { + let args = (0..arg_tys.len()) + .map(|i| Value(inst_id, i)) + .map(|val| { + value_summaries + .get(&val) + .cloned() + .unwrap_or(TreeSummary::Other) + }) + .collect::>(); + let summary = TreeSummary::Term(term, args); + match value_summaries.entry(input) { + HashEntry::Vacant(v) => { + v.insert(summary); + } + HashEntry::Occupied(mut o) => { + let combined = TreeSummary::Conjunction(vec![ + summary, + std::mem::replace(o.get_mut(), TreeSummary::Other), + ]); + *o.get_mut() = combined; + } + } + } + + &Inst::ConstInt { .. } | &Inst::CreateVariant { .. } | &Inst::Construct { .. } => {} + + &Inst::Copy { val, .. } => { + // Copy summary from output to input. + let output_value = value_summaries + .get(&Value(inst_id, 0)) + .cloned() + .unwrap_or(TreeSummary::Other); + value_summaries.insert(val, output_value); + } + + &Inst::Nop => {} + } + } + + panic!("Sequence did not start with Arg") + } +} + +/// A "summary" of a tree shape -- a template that describes a tree of +/// terms and constant integer values. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TreeSummary { + /// A known term, with given subtrees. + Term(TermId, Vec), + /// A known enum variant, with given subtrees. + Variant(VariantId, Vec), + /// A known constant integer value. + ConstInt(i64), + /// All of a list of summaries: represents a combined list of + /// requirements. The "provides" relation is satisfied if the + /// provider provides *all* of the providee's summaries in the + /// conjunction. A conjunction on the provider side (i.e., as an + /// "output summary") is illegal. + Conjunction(Vec), + /// Something else. + Other, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum TreeSummaryOverlap { + Never, + Sometimes, + Always, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TermOrVariant { + Term(TermId), + Variant(VariantId), +} + +impl TreeSummary { + /// Does a term tree matching this summary "provide" the shape + /// described/expected by another summary? Answer can be "always", + /// "possibly", or "no". + pub fn provides(&self, other: &TreeSummary) -> TreeSummaryOverlap { + match (self, other) { + (_, &TreeSummary::Other) => TreeSummaryOverlap::Always, + (&TreeSummary::Other, _) => TreeSummaryOverlap::Sometimes, + + (&TreeSummary::Conjunction(..), _) => { + panic!("Conjunction on LHS of `provides` relation") + } + (this, &TreeSummary::Conjunction(ref args)) => args + .iter() + .map(|arg| this.provides(arg)) + .min() + .unwrap_or(TreeSummaryOverlap::Always), + + ( + &TreeSummary::Term(self_term, ref self_args), + &TreeSummary::Term(other_term, ref other_args), + ) => { + if self_term != other_term { + TreeSummaryOverlap::Never + } else { + assert_eq!(self_args.len(), other_args.len()); + self_args + .iter() + .zip(other_args.iter()) + .map(|(self_arg, other_arg)| self_arg.provides(other_arg)) + .min() + .unwrap_or(TreeSummaryOverlap::Always) + } + } + + ( + &TreeSummary::Variant(self_var, ref self_args), + &TreeSummary::Variant(other_var, ref other_args), + ) => { + if self_var != other_var { + TreeSummaryOverlap::Never + } else { + assert_eq!(self_args.len(), other_args.len()); + self_args + .iter() + .zip(other_args.iter()) + .map(|(self_arg, other_arg)| self_arg.provides(other_arg)) + .min() + .unwrap_or(TreeSummaryOverlap::Always) + } + } + + (&TreeSummary::ConstInt(i1), &TreeSummary::ConstInt(i2)) => { + if i1 != i2 { + TreeSummaryOverlap::Never + } else { + TreeSummaryOverlap::Always + } + } + + _ => TreeSummaryOverlap::Never, + } + } + + pub fn root(&self) -> Option { + match self { + &TreeSummary::Term(term, ..) => Some(TermOrVariant::Term(term)), + &TreeSummary::Variant(variant, ..) => Some(TermOrVariant::Variant(variant)), + _ => None, + } + } +} diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/src/lexer.rs new file mode 100644 index 0000000000..eafb72a462 --- /dev/null +++ b/cranelift/isle/src/lexer.rs @@ -0,0 +1,241 @@ +//! Lexer for the ISLE language. + +#[derive(Clone, Debug)] +pub struct Lexer<'a> { + buf: &'a [u8], + pos: Pos, + lookahead: Option<(Pos, Token<'a>)>, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Pos { + pub offset: usize, + pub line: usize, + pub col: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Token<'a> { + LParen, + RParen, + Symbol(&'a str), + Int(i64), +} + +impl<'a> Lexer<'a> { + pub fn new(s: &'a str) -> Lexer<'a> { + let mut l = Lexer { + buf: s.as_bytes(), + pos: Pos { + offset: 0, + line: 1, + col: 0, + }, + lookahead: None, + }; + l.reload(); + l + } + + pub fn offset(&self) -> usize { + self.pos.offset + } + + pub fn pos(&self) -> Pos { + self.pos + } + + fn next_token(&mut self) -> Option<(Pos, Token<'a>)> { + fn is_sym_first_char(c: u8) -> bool { + match c { + b'-' | b'0'..=b'9' | b'(' | b')' | b';' => false, + c if c.is_ascii_whitespace() => false, + _ => true, + } + } + fn is_sym_other_char(c: u8) -> bool { + match c { + b'(' | b')' | b';' => false, + c if c.is_ascii_whitespace() => false, + _ => true, + } + } + + // Skip any whitespace and any comments. + while self.pos.offset < self.buf.len() { + if self.buf[self.pos.offset].is_ascii_whitespace() { + self.pos.col += 1; + if self.buf[self.pos.offset] == b'\n' { + self.pos.line += 1; + self.pos.col = 0; + } + self.pos.offset += 1; + continue; + } + if self.buf[self.pos.offset] == b';' { + while self.pos.offset < self.buf.len() && self.buf[self.pos.offset] != b'\n' { + self.pos.offset += 1; + } + self.pos.line += 1; + self.pos.col = 0; + continue; + } + break; + } + + if self.pos.offset == self.buf.len() { + return None; + } + + let char_pos = self.pos; + match self.buf[self.pos.offset] { + b'(' => { + self.pos.offset += 1; + self.pos.col += 1; + Some((char_pos, Token::LParen)) + } + b')' => { + self.pos.offset += 1; + self.pos.col += 1; + Some((char_pos, Token::RParen)) + } + c if is_sym_first_char(c) => { + let start = self.pos.offset; + let start_pos = self.pos; + while self.pos.offset < self.buf.len() + && is_sym_other_char(self.buf[self.pos.offset]) + { + self.pos.col += 1; + self.pos.offset += 1; + } + let end = self.pos.offset; + let s = std::str::from_utf8(&self.buf[start..end]) + .expect("Only ASCII characters, should be UTF-8"); + Some((start_pos, Token::Symbol(s))) + } + c if (c >= b'0' && c <= b'9') || c == b'-' => { + let start_pos = self.pos; + let neg = if c == b'-' { + self.pos.offset += 1; + self.pos.col += 1; + true + } else { + false + }; + let mut num = 0; + while self.pos.offset < self.buf.len() + && (self.buf[self.pos.offset] >= b'0' && self.buf[self.pos.offset] <= b'9') + { + num = (num * 10) + (self.buf[self.pos.offset] - b'0') as i64; + self.pos.offset += 1; + self.pos.col += 1; + } + + let tok = if neg { + Token::Int(-num) + } else { + Token::Int(num) + }; + Some((start_pos, tok)) + } + c => panic!("Unexpected character '{}' at offset {}", c, self.pos.offset), + } + } + + fn reload(&mut self) { + if self.lookahead.is_none() && self.pos.offset < self.buf.len() { + self.lookahead = self.next_token(); + } + } + + pub fn peek(&self) -> Option<(Pos, Token<'a>)> { + self.lookahead + } + + pub fn eof(&self) -> bool { + self.lookahead.is_none() + } +} + +impl<'a> std::iter::Iterator for Lexer<'a> { + type Item = (Pos, Token<'a>); + + fn next(&mut self) -> Option<(Pos, Token<'a>)> { + let tok = self.lookahead.take(); + self.reload(); + tok + } +} + +impl<'a> Token<'a> { + pub fn is_int(&self) -> bool { + match self { + Token::Int(_) => true, + _ => false, + } + } + + pub fn is_sym(&self) -> bool { + match self { + Token::Symbol(_) => true, + _ => false, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn lexer_basic() { + assert_eq!( + Lexer::new(";; comment\n; another\r\n \t(one two three 23 -568 )\n") + .map(|(_, tok)| tok) + .collect::>(), + vec![ + Token::LParen, + Token::Symbol("one"), + Token::Symbol("two"), + Token::Symbol("three"), + Token::Int(23), + Token::Int(-568), + Token::RParen + ] + ); + } + + #[test] + fn ends_with_sym() { + assert_eq!( + Lexer::new("asdf").map(|(_, tok)| tok).collect::>(), + vec![Token::Symbol("asdf"),] + ); + } + + #[test] + fn ends_with_num() { + assert_eq!( + Lexer::new("23").map(|(_, tok)| tok).collect::>(), + vec![Token::Int(23)], + ); + } + + #[test] + fn weird_syms() { + assert_eq!( + Lexer::new("(+ [] => !! _test!;comment\n)") + .map(|(_, tok)| tok) + .collect::>(), + vec![ + Token::LParen, + Token::Symbol("+"), + Token::Symbol("[]"), + Token::Symbol("=>"), + Token::Symbol("!!"), + Token::Symbol("_test!"), + Token::RParen, + ] + ); + } +} diff --git a/cranelift/isle/src/lower.rs b/cranelift/isle/src/lower.rs new file mode 100644 index 0000000000..52736d4f34 --- /dev/null +++ b/cranelift/isle/src/lower.rs @@ -0,0 +1,33 @@ +use crate::ir::*; +use crate::sema; + +struct LowerState<'a> { + tyenv: &'a sema::TypeEnv, + func: &'a sema::Func, + builder: FuncBuilder, + control_flow: ControlInput, +} + +pub fn lower(tyenv: &sema::TypeEnv, func: &sema::Func) -> Func { + let mut builder = FuncBuilder::default(); + let entry = builder.intern(Node::Entry); + + let mut state = LowerState { + tyenv, + func, + builder, + control_flow: ControlInput(entry, 0), + }; + + if !func.is_extern && !func.is_inline { + for case in &func.cases { + state.lower_case(case); + } + } + + state.builder.build() +} + +impl<'a> LowerState<'a> { + fn lower_case(&mut self) {} +} diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs new file mode 100644 index 0000000000..b9364551e8 --- /dev/null +++ b/cranelift/isle/src/main.rs @@ -0,0 +1,28 @@ +#![allow(dead_code)] + +use std::io::stdin; +use std::io::Read; + +mod ast; +mod compile; +mod error; +mod ir; +mod lexer; +mod parser; +mod sema; + +fn main() -> Result<(), error::Error> { + let _ = env_logger::try_init(); + let mut input = String::new(); + stdin().read_to_string(&mut input)?; + let mut parser = parser::Parser::new("", &input[..]); + let defs = parser.parse_defs()?; + let mut compiler = compile::Compiler::new(&defs)?; + compiler.build_sequences()?; + compiler.collect_tree_summaries()?; + + for seq in compiler.to_sequences() { + println!("---\nsequence\n---\n{:?}\n", seq); + } + Ok(()) +} diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs new file mode 100644 index 0000000000..a7759cc306 --- /dev/null +++ b/cranelift/isle/src/parser.rs @@ -0,0 +1,429 @@ +//! Parser for ISLE language. + +use crate::ast::*; +use crate::error::*; +use crate::lexer::{Lexer, Pos, Token}; + +#[derive(Clone, Debug)] +pub struct Parser<'a> { + filename: &'a str, + lexer: Lexer<'a>, +} + +pub type ParseResult = std::result::Result; + +impl<'a> Parser<'a> { + pub fn new(filename: &'a str, s: &'a str) -> Parser<'a> { + Parser { + filename, + lexer: Lexer::new(s), + } + } + + pub fn error(&self, pos: Pos, msg: String) -> ParseError { + ParseError { + filename: self.filename.to_string(), + pos, + msg, + } + } + + fn take bool>(&mut self, f: F) -> ParseResult> { + if let Some((pos, peek)) = self.lexer.peek() { + if !f(peek) { + return Err(self.error(pos, format!("Unexpected token {:?}", peek))); + } + self.lexer.next(); + Ok(peek) + } else { + Err(self.error(self.lexer.pos(), "Unexpected EOF".to_string())) + } + } + + fn is bool>(&self, f: F) -> bool { + if let Some((_, peek)) = self.lexer.peek() { + f(peek) + } else { + false + } + } + + fn pos(&self) -> Option { + self.lexer.peek().map(|(pos, _)| pos) + } + + fn is_lparen(&self) -> bool { + self.is(|tok| tok == Token::LParen) + } + fn is_rparen(&self) -> bool { + self.is(|tok| tok == Token::RParen) + } + fn is_sym(&self) -> bool { + self.is(|tok| tok.is_sym()) + } + fn is_int(&self) -> bool { + self.is(|tok| tok.is_int()) + } + fn is_sym_str(&self, s: &str) -> bool { + self.is(|tok| tok == Token::Symbol(s)) + } + + fn lparen(&mut self) -> ParseResult<()> { + self.take(|tok| tok == Token::LParen).map(|_| ()) + } + fn rparen(&mut self) -> ParseResult<()> { + self.take(|tok| tok == Token::RParen).map(|_| ()) + } + + fn symbol(&mut self) -> ParseResult<&'a str> { + match self.take(|tok| tok.is_sym())? { + Token::Symbol(s) => Ok(s), + _ => unreachable!(), + } + } + + fn int(&mut self) -> ParseResult { + match self.take(|tok| tok.is_int())? { + Token::Int(i) => Ok(i), + _ => unreachable!(), + } + } + + pub fn parse_defs(&mut self) -> ParseResult { + let mut defs = vec![]; + while !self.lexer.eof() { + defs.push(self.parse_def()?); + } + Ok(Defs { + defs, + filename: self.filename.to_string(), + }) + } + + fn parse_def(&mut self) -> ParseResult { + self.lparen()?; + let pos = self.pos(); + let def = match self.symbol()? { + "type" => Def::Type(self.parse_type()?), + "rule" => Def::Rule(self.parse_rule()?), + "decl" => Def::Decl(self.parse_decl()?), + "constructor" => Def::Extern(self.parse_ctor()?), + "extractor" => Def::Extern(self.parse_etor()?), + s => { + return Err(self.error(pos.unwrap(), format!("Unexpected identifier: {}", s))); + } + }; + self.rparen()?; + Ok(def) + } + + fn str_to_ident(&self, pos: Pos, s: &str) -> ParseResult { + let first = s.chars().next().unwrap(); + if !first.is_alphabetic() && first != '_' { + return Err(self.error( + pos, + format!("Identifier '{}' does not start with letter or _", s), + )); + } + if s.chars() + .skip(1) + .any(|c| !c.is_alphanumeric() && c != '_' && c != '.') + { + return Err(self.error( + pos, + format!( + "Identifier '{}' contains invalid character (not a-z, A-Z, 0-9, _, .)", + s + ), + )); + } + Ok(Ident(s.to_string())) + } + + fn parse_ident(&mut self) -> ParseResult { + let pos = self.pos(); + let s = self.symbol()?; + self.str_to_ident(pos.unwrap(), s) + } + + fn parse_type(&mut self) -> ParseResult { + let pos = self.pos(); + let name = self.parse_ident()?; + let mut is_extern = false; + if self.is_sym_str("extern") { + self.symbol()?; + is_extern = true; + } + let ty = self.parse_typevalue()?; + Ok(Type { + name, + is_extern, + ty, + pos: pos.unwrap(), + }) + } + + fn parse_typevalue(&mut self) -> ParseResult { + let pos = self.pos(); + self.lparen()?; + if self.is_sym_str("primitive") { + self.symbol()?; + let primitive_ident = self.parse_ident()?; + self.rparen()?; + Ok(TypeValue::Primitive(primitive_ident)) + } else if self.is_sym_str("enum") { + self.symbol()?; + let mut variants = vec![]; + while !self.is_rparen() { + let variant = self.parse_type_variant()?; + variants.push(variant); + } + self.rparen()?; + Ok(TypeValue::Enum(variants)) + } else { + Err(self.error(pos.unwrap(), "Unknown type definition".to_string())) + } + } + + fn parse_type_variant(&mut self) -> ParseResult { + self.lparen()?; + let name = self.parse_ident()?; + let mut fields = vec![]; + while !self.is_rparen() { + fields.push(self.parse_type_field()?); + } + self.rparen()?; + Ok(Variant { name, fields }) + } + + fn parse_type_field(&mut self) -> ParseResult { + self.lparen()?; + let name = self.parse_ident()?; + let ty = self.parse_ident()?; + self.rparen()?; + Ok(Field { name, ty }) + } + + fn parse_decl(&mut self) -> ParseResult { + let pos = self.pos(); + let term = self.parse_ident()?; + + self.lparen()?; + let mut arg_tys = vec![]; + while !self.is_rparen() { + arg_tys.push(self.parse_ident()?); + } + self.rparen()?; + + let ret_ty = self.parse_ident()?; + + Ok(Decl { + term, + arg_tys, + ret_ty, + pos: pos.unwrap(), + }) + } + + fn parse_ctor(&mut self) -> ParseResult { + let pos = self.pos(); + let term = self.parse_ident()?; + let func = self.parse_ident()?; + Ok(Extern::Constructor { + term, + func, + pos: pos.unwrap(), + }) + } + + fn parse_etor(&mut self) -> ParseResult { + let pos = self.pos(); + let term = self.parse_ident()?; + let func = self.parse_ident()?; + Ok(Extern::Extractor { + term, + func, + pos: pos.unwrap(), + }) + } + + fn parse_rule(&mut self) -> ParseResult { + let pos = self.pos(); + let prio = if self.is_int() { + Some(self.int()?) + } else { + None + }; + let pattern = self.parse_pattern()?; + let expr = self.parse_expr()?; + Ok(Rule { + pattern, + expr, + pos: pos.unwrap(), + prio, + }) + } + + fn parse_pattern(&mut self) -> ParseResult { + let pos = self.pos(); + if self.is_int() { + Ok(Pattern::ConstInt { val: self.int()? }) + } else if self.is_sym_str("_") { + self.symbol()?; + Ok(Pattern::Wildcard) + } else if self.is_sym() { + let s = self.symbol()?; + if s.starts_with("=") { + let s = &s[1..]; + let var = self.str_to_ident(pos.unwrap(), s)?; + Ok(Pattern::Var { var }) + } else { + let var = self.str_to_ident(pos.unwrap(), s)?; + if self.is_sym_str("@") { + self.symbol()?; + let subpat = Box::new(self.parse_pattern()?); + Ok(Pattern::BindPattern { var, subpat }) + } else { + Ok(Pattern::BindPattern { + var, + subpat: Box::new(Pattern::Wildcard), + }) + } + } + } else if self.is_lparen() { + self.lparen()?; + let sym = self.parse_ident()?; + let mut args = vec![]; + while !self.is_rparen() { + args.push(self.parse_pattern()?); + } + self.rparen()?; + Ok(Pattern::Term { sym, args }) + } else { + Err(self.error(pos.unwrap(), "Unexpected pattern".into())) + } + } + + fn parse_expr(&mut self) -> ParseResult { + 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 }) + } 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 }) + } + } else if self.is_sym() { + let name = self.parse_ident()?; + Ok(Expr::Var { name }) + } else if self.is_int() { + let val = self.int()?; + Ok(Expr::ConstInt { val }) + } else { + Err(self.error(pos.unwrap(), "Invalid expression".into())) + } + } + + fn parse_letdef(&mut self) -> ParseResult { + self.lparen()?; + let var = self.parse_ident()?; + let ty = self.parse_ident()?; + let val = Box::new(self.parse_expr()?); + self.rparen()?; + Ok(LetDef { var, ty, val }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_type() { + let text = r" + ;; comment + (type Inst extern (enum + (Alu (a Reg) (b Reg) (dest Reg)) + (Load (a Reg) (dest Reg)))) + (type u32 (primitive u32)) + "; + let defs = Parser::new("(none)", text) + .parse_defs() + .expect("should parse"); + assert_eq!( + defs, + Defs { + filename: "(none)".to_string(), + defs: vec![ + Def::Type(Type { + name: Ident("Inst".to_string()), + is_extern: true, + ty: TypeValue::Enum(vec![ + Variant { + name: Ident("Alu".to_string()), + fields: vec![ + Field { + name: Ident("a".to_string()), + ty: Ident("Reg".to_string()), + }, + Field { + name: Ident("b".to_string()), + ty: Ident("Reg".to_string()), + }, + Field { + name: Ident("dest".to_string()), + ty: Ident("Reg".to_string()), + }, + ], + }, + Variant { + name: Ident("Load".to_string()), + fields: vec![ + Field { + name: Ident("a".to_string()), + ty: Ident("Reg".to_string()), + }, + Field { + name: Ident("dest".to_string()), + ty: Ident("Reg".to_string()), + }, + ], + } + ]), + pos: Pos { + offset: 42, + line: 4, + col: 18, + }, + }), + Def::Type(Type { + name: Ident("u32".to_string()), + is_extern: false, + ty: TypeValue::Primitive(Ident("u32".to_string())), + pos: Pos { + offset: 167, + line: 7, + col: 18, + }, + }), + ] + } + ); + } +} diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs new file mode 100644 index 0000000000..a11faccc49 --- /dev/null +++ b/cranelift/isle/src/sema.rs @@ -0,0 +1,862 @@ +//! Semantic analysis. + +use crate::ast; +use crate::error::*; +use crate::lexer::Pos; +use std::collections::HashMap; + +pub type SemaResult = std::result::Result; + +#[macro_export] +macro_rules! declare_id { + ($name:ident) => { + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct $name(pub usize); + impl $name { + pub fn index(self) -> usize { + self.0 + } + } + }; +} + +declare_id!(Sym); +declare_id!(TypeId); +declare_id!(VariantId); +declare_id!(FieldId); +declare_id!(TermId); +declare_id!(RuleId); +declare_id!(VarId); + +#[derive(Clone, Debug)] +pub struct TypeEnv { + pub filename: String, + pub syms: Vec, + pub sym_map: HashMap, + pub types: Vec, + pub type_map: HashMap, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Type { + Primitive(TypeId, Sym), + Enum { + name: Sym, + id: TypeId, + is_extern: bool, + variants: Vec, + pos: Pos, + }, +} + +impl Type { + fn name<'a>(&self, tyenv: &'a TypeEnv) -> &'a str { + match self { + Self::Primitive(_, name) | Self::Enum { name, .. } => &tyenv.syms[name.index()], + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Variant { + pub name: Sym, + pub id: VariantId, + pub fields: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Field { + pub name: Sym, + pub id: FieldId, + pub ty: TypeId, +} + +#[derive(Clone, Debug)] +pub struct TermEnv { + pub terms: Vec, + pub term_map: HashMap, + pub rules: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Term { + pub id: TermId, + pub name: Sym, + pub arg_tys: Vec, + pub ret_ty: TypeId, + pub kind: TermKind, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TermKind { + EnumVariant { + variant: VariantId, + }, + Regular { + // Producer and consumer rules are catalogued separately after + // building Sequences. Here we just record whether an + // extractor and/or constructor is known. + extractor: Option, + constructor: Option, + }, +} + +#[derive(Clone, Debug)] +pub struct Rule { + pub id: RuleId, + pub lhs: Pattern, + pub rhs: Expr, + pub prio: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Pattern { + BindPattern(TypeId, VarId, Box), + Var(TypeId, VarId), + ConstInt(TypeId, i64), + Term(TypeId, TermId, Vec), + Wildcard(TypeId), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Expr { + Term(TypeId, TermId, Vec), + Var(TypeId, VarId), + ConstInt(TypeId, i64), + Let(TypeId, Vec<(VarId, TypeId, Box)>, Box), +} + +impl Pattern { + pub fn ty(&self) -> TypeId { + match self { + &Self::BindPattern(t, ..) => t, + &Self::Var(t, ..) => t, + &Self::ConstInt(t, ..) => t, + &Self::Term(t, ..) => t, + &Self::Wildcard(t, ..) => t, + } + } +} + +impl Expr { + pub fn ty(&self) -> TypeId { + match self { + &Self::Term(t, ..) => t, + &Self::Var(t, ..) => t, + &Self::ConstInt(t, ..) => t, + &Self::Let(t, ..) => t, + } + } +} + +impl TypeEnv { + pub fn from_ast(defs: &ast::Defs) -> SemaResult { + let mut tyenv = TypeEnv { + filename: defs.filename.clone(), + syms: vec![], + sym_map: HashMap::new(), + types: vec![], + type_map: HashMap::new(), + }; + + // Traverse defs, assigning type IDs to type names. We'll fill + // in types on a second pass. + for def in &defs.defs { + match def { + &ast::Def::Type(ref td) => { + let tid = TypeId(tyenv.type_map.len()); + let name = tyenv.intern_mut(&td.name); + if tyenv.type_map.contains_key(&name) { + return Err(tyenv.error( + td.pos, + format!("Type name defined more than once: '{}'", td.name.0), + )); + } + tyenv.type_map.insert(name, tid); + } + _ => {} + } + } + + // Now lower AST nodes to type definitions, raising errors + // where typenames of fields are undefined or field names are + // duplicated. + let mut tid = 0; + for def in &defs.defs { + match def { + &ast::Def::Type(ref td) => { + let ty = tyenv.type_from_ast(TypeId(tid), td)?; + tyenv.types.push(ty); + tid += 1; + } + _ => {} + } + } + + Ok(tyenv) + } + + fn type_from_ast(&mut self, tid: TypeId, ty: &ast::Type) -> SemaResult { + let name = self.intern(&ty.name).unwrap(); + match &ty.ty { + &ast::TypeValue::Primitive(ref id) => Ok(Type::Primitive(tid, self.intern_mut(id))), + &ast::TypeValue::Enum(ref ty_variants) => { + let mut variants = vec![]; + for variant in ty_variants { + let combined_ident = ast::Ident(format!("{}.{}", ty.name.0, variant.name.0)); + let var_name = self.intern_mut(&combined_ident); + let id = VariantId(variants.len()); + if variants.iter().any(|v: &Variant| v.name == var_name) { + return Err(self.error( + ty.pos, + format!("Duplicate variant name in type: '{}'", variant.name.0), + )); + } + let mut fields = vec![]; + for field in &variant.fields { + let field_name = self.intern_mut(&field.name); + if fields.iter().any(|f: &Field| f.name == field_name) { + return Err(self.error( + ty.pos, + format!( + "Duplicate field name '{}' in variant '{}' of type", + field.name.0, variant.name.0 + ), + )); + } + let field_ty = self.intern_mut(&field.ty); + let field_tid = match self.type_map.get(&field_ty) { + Some(tid) => *tid, + None => { + return Err(self.error( + ty.pos, + format!( + "Unknown type '{}' for field '{}' in variant '{}'", + field.ty.0, field.name.0, variant.name.0 + ), + )); + } + }; + fields.push(Field { + name: field_name, + id: FieldId(fields.len()), + ty: field_tid, + }); + } + variants.push(Variant { + name: var_name, + id, + fields, + }); + } + Ok(Type::Enum { + name, + id: tid, + is_extern: ty.is_extern, + variants, + pos: ty.pos, + }) + } + } + } + + fn error(&self, pos: Pos, msg: String) -> SemaError { + SemaError { + filename: self.filename.clone(), + pos, + msg, + } + } + + pub fn intern_mut(&mut self, ident: &ast::Ident) -> Sym { + if let Some(s) = self.sym_map.get(&ident.0).cloned() { + s + } else { + let s = Sym(self.syms.len()); + self.syms.push(ident.0.clone()); + self.sym_map.insert(ident.0.clone(), s); + s + } + } + + pub fn intern(&self, ident: &ast::Ident) -> Option { + self.sym_map.get(&ident.0).cloned() + } +} + +struct Bindings { + next_var: usize, + vars: Vec, +} + +struct BoundVar { + name: Sym, + id: VarId, + ty: TypeId, +} + +impl TermEnv { + pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult { + let mut env = TermEnv { + terms: vec![], + term_map: HashMap::new(), + rules: vec![], + }; + + env.collect_term_sigs(tyenv, defs)?; + env.collect_enum_variant_terms(tyenv)?; + env.collect_rules(tyenv, defs)?; + + Ok(env) + } + + fn collect_term_sigs(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { + for def in &defs.defs { + match def { + &ast::Def::Decl(ref decl) => { + let tid = TermId(self.terms.len()); + let name = tyenv.intern_mut(&decl.term); + if self.term_map.contains_key(&name) { + return Err( + tyenv.error(decl.pos, format!("Duplicate decl for '{}'", decl.term.0)) + ); + } + self.term_map.insert(name, tid); + + let arg_tys = decl + .arg_tys + .iter() + .map(|id| { + let sym = tyenv.intern_mut(id); + tyenv.type_map.get(&sym).cloned().ok_or_else(|| { + tyenv.error(decl.pos, format!("Unknown arg type: '{}'", id.0)) + }) + }) + .collect::>>()?; + let ret_ty = { + let sym = tyenv.intern_mut(&decl.ret_ty); + tyenv.type_map.get(&sym).cloned().ok_or_else(|| { + tyenv.error( + decl.pos, + format!("Unknown return type: '{}'", decl.ret_ty.0), + ) + })? + }; + + self.terms.push(Term { + id: tid, + name, + arg_tys, + ret_ty, + kind: TermKind::Regular { + extractor: None, + constructor: None, + }, + }); + } + _ => {} + } + } + + Ok(()) + } + + fn collect_enum_variant_terms(&mut self, tyenv: &mut TypeEnv) -> SemaResult<()> { + for ty in &tyenv.types { + match ty { + &Type::Enum { + pos, + id, + ref variants, + .. + } => { + for variant in variants { + if self.term_map.contains_key(&variant.name) { + return Err(tyenv.error( + pos, + format!( + "Duplicate enum variant constructor: '{}'", + tyenv.syms[variant.name.index()] + ), + )); + } + let tid = TermId(self.terms.len()); + let arg_tys = variant.fields.iter().map(|fld| fld.ty).collect::>(); + let ret_ty = id; + self.terms.push(Term { + id: tid, + name: variant.name, + arg_tys, + ret_ty, + kind: TermKind::EnumVariant { + variant: variant.id, + }, + }); + self.term_map.insert(variant.name, tid); + } + } + _ => {} + } + } + + Ok(()) + } + + fn collect_rules(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { + for def in &defs.defs { + match def { + &ast::Def::Rule(ref rule) => { + let mut bindings = Bindings { + next_var: 0, + vars: vec![], + }; + + let (lhs, ty) = self.translate_pattern( + tyenv, + rule.pos, + &rule.pattern, + None, + &mut bindings, + )?; + let rhs = + self.translate_expr(tyenv, rule.pos, &rule.expr, ty, &mut bindings)?; + let rid = RuleId(self.rules.len()); + self.rules.push(Rule { + id: rid, + lhs, + rhs, + prio: rule.prio, + }); + } + &ast::Def::Extern(ast::Extern::Constructor { + ref term, + ref func, + pos, + }) => { + let term_sym = tyenv.intern_mut(term); + let func_sym = tyenv.intern_mut(func); + let term_id = match self.term_map.get(&term_sym) { + Some(term) => term, + None => { + return Err(tyenv.error( + pos, + format!("Constructor declared on undefined term '{}'", term.0), + )) + } + }; + match &mut self.terms[term_id.index()].kind { + &mut TermKind::EnumVariant { .. } => { + return Err(tyenv.error( + pos, + format!("Constructor defined on enum type '{}'", term.0), + )); + } + &mut TermKind::Regular { + ref mut constructor, + .. + } => { + if constructor.is_some() { + return Err(tyenv.error( + pos, + format!( + "Constructor defined more than once on term '{}'", + term.0 + ), + )); + } + *constructor = Some(func_sym); + } + } + } + &ast::Def::Extern(ast::Extern::Extractor { + ref term, + ref func, + pos, + }) => { + let term_sym = tyenv.intern_mut(term); + let func_sym = tyenv.intern_mut(func); + let term_id = match self.term_map.get(&term_sym) { + Some(term) => term, + None => { + return Err(tyenv.error( + pos, + format!("Extractor declared on undefined term '{}'", term.0), + )) + } + }; + match &mut self.terms[term_id.index()].kind { + &mut TermKind::EnumVariant { .. } => { + return Err(tyenv.error( + pos, + format!("Extractor defined on enum type '{}'", term.0), + )); + } + &mut TermKind::Regular { + ref mut extractor, .. + } => { + if extractor.is_some() { + return Err(tyenv.error( + pos, + format!( + "Extractor defined more than once on term '{}'", + term.0 + ), + )); + } + *extractor = Some(func_sym); + } + } + } + _ => {} + } + } + + Ok(()) + } + + fn translate_pattern( + &self, + tyenv: &mut TypeEnv, + pos: Pos, + pat: &ast::Pattern, + expected_ty: Option, + bindings: &mut Bindings, + ) -> SemaResult<(Pattern, TypeId)> { + match pat { + // TODO: flag on primitive type decl indicating it's an integer type? + &ast::Pattern::ConstInt { val } => { + let ty = expected_ty.ok_or_else(|| { + tyenv.error(pos, "Need an implied type for an integer constant".into()) + })?; + Ok((Pattern::ConstInt(ty, val), ty)) + } + &ast::Pattern::Wildcard => { + let ty = expected_ty.ok_or_else(|| { + tyenv.error(pos, "Need an implied type for a wildcard".into()) + })?; + Ok((Pattern::Wildcard(ty), ty)) + } + &ast::Pattern::BindPattern { + ref var, + ref subpat, + } => { + // Do the subpattern first so we can resolve the type for sure. + let (subpat, ty) = + self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; + + let name = tyenv.intern_mut(var); + if bindings.vars.iter().any(|bv| bv.name == name) { + return Err(tyenv.error( + pos, + format!("Rebound variable name in LHS pattern: '{}'", var.0), + )); + } + let id = VarId(bindings.next_var); + bindings.next_var += 1; + bindings.vars.push(BoundVar { name, id, ty }); + + Ok((Pattern::BindPattern(ty, id, Box::new(subpat)), ty)) + } + &ast::Pattern::Var { ref var } => { + // Look up the variable; it must already have been bound. + let name = tyenv.intern_mut(var); + let bv = match bindings.vars.iter().rev().find(|bv| bv.name == name) { + None => { + return Err(tyenv.error( + pos, + format!( + "Unknown variable '{}' in bound-var pattern '={}'", + var.0, var.0 + ), + )) + } + Some(bv) => bv, + }; + let ty = match expected_ty { + None => bv.ty, + Some(expected_ty) if expected_ty == bv.ty => bv.ty, + Some(expected_ty) => { + return Err(tyenv.error(pos, format!("Mismatched types: pattern expects type '{}' but already-bound var '{}' has type '{}'", tyenv.types[expected_ty.index()].name(tyenv), var.0, tyenv.types[bv.ty.index()].name(tyenv)))); + } + }; + Ok((Pattern::Var(ty, bv.id), ty)) + } + &ast::Pattern::Term { ref sym, ref args } => { + let name = tyenv.intern_mut(&sym); + // Look up the term. + let tid = self.term_map.get(&name).ok_or_else(|| { + tyenv.error(pos, format!("Unknown term in pattern: '{}'", sym.0)) + })?; + + // Get the return type and arg types. Verify the + // expected type of this pattern, if any, against the + // return type of the term. + let ret_ty = self.terms[tid.index()].ret_ty; + let ty = match expected_ty { + None => ret_ty, + Some(expected_ty) if expected_ty == ret_ty => ret_ty, + Some(expected_ty) => { + return Err(tyenv.error(pos, format!("Mismatched types: pattern expects type '{}' but term has return type '{}'", tyenv.types[expected_ty.index()].name(tyenv), tyenv.types[ret_ty.index()].name(tyenv)))); + } + }; + + // Check that we have the correct argument count. + if self.terms[tid.index()].arg_tys.len() != args.len() { + return Err(tyenv.error( + pos, + format!( + "Incorrect argument count for term '{}': got {}, expect {}", + sym.0, + args.len(), + self.terms[tid.index()].arg_tys.len() + ), + )); + } + + // Resolve subpatterns. + let mut subpats = vec![]; + for (i, arg) in args.iter().enumerate() { + let arg_ty = self.terms[tid.index()].arg_tys[i]; + let (subpat, _) = + self.translate_pattern(tyenv, pos, arg, Some(arg_ty), bindings)?; + subpats.push(subpat); + } + + Ok((Pattern::Term(ty, *tid, subpats), ty)) + } + } + } + + fn translate_expr( + &self, + tyenv: &mut TypeEnv, + pos: Pos, + expr: &ast::Expr, + ty: TypeId, + bindings: &mut Bindings, + ) -> SemaResult { + match expr { + &ast::Expr::Term { ref sym, ref args } => { + // Look up the term. + let name = tyenv.intern_mut(&sym); + // Look up the term. + let tid = self.term_map.get(&name).ok_or_else(|| { + tyenv.error(pos, format!("Unknown term in pattern: '{}'", sym.0)) + })?; + + // Get the return type and arg types. Verify the + // expected type of this pattern, if any, against the + // return type of the term. + let ret_ty = self.terms[tid.index()].ret_ty; + if ret_ty != ty { + return Err(tyenv.error(pos, format!("Mismatched types: expression expects type '{}' but term has return type '{}'", tyenv.types[ty.index()].name(tyenv), tyenv.types[ret_ty.index()].name(tyenv)))); + } + + // Check that we have the correct argument count. + if self.terms[tid.index()].arg_tys.len() != args.len() { + return Err(tyenv.error( + pos, + format!( + "Incorrect argument count for term '{}': got {}, expect {}", + sym.0, + args.len(), + self.terms[tid.index()].arg_tys.len() + ), + )); + } + + // Resolve subexpressions. + let mut subexprs = vec![]; + for (i, arg) in args.iter().enumerate() { + let arg_ty = self.terms[tid.index()].arg_tys[i]; + let subexpr = self.translate_expr(tyenv, pos, arg, arg_ty, bindings)?; + subexprs.push(subexpr); + } + + Ok(Expr::Term(ty, *tid, subexprs)) + } + &ast::Expr::Var { ref name } => { + let sym = tyenv.intern_mut(name); + // Look through bindings, innermost (most recent) first. + let bv = match bindings.vars.iter().rev().find(|b| b.name == sym) { + None => { + return Err(tyenv.error(pos, format!("Unknown variable '{}'", name.0))); + } + Some(bv) => bv, + }; + + // Verify type. + if bv.ty != ty { + return Err(tyenv.error( + pos, + format!( + "Variable '{}' has type {} but we need {} in context", + name.0, + tyenv.types[bv.ty.index()].name(tyenv), + tyenv.types[ty.index()].name(tyenv) + ), + )); + } + + Ok(Expr::Var(bv.ty, bv.id)) + } + &ast::Expr::ConstInt { val } => Ok(Expr::ConstInt(ty, val)), + &ast::Expr::Let { ref defs, ref body } => { + let orig_binding_len = bindings.vars.len(); + + // For each new binding... + let mut let_defs = vec![]; + for def in defs { + // Check that the given variable name does not already exist. + let name = tyenv.intern_mut(&def.var); + if bindings.vars.iter().any(|bv| bv.name == name) { + return Err( + tyenv.error(pos, format!("Variable '{}' already bound", def.var.0)) + ); + } + + // Look up the type. + let tysym = match tyenv.intern(&def.ty) { + Some(ty) => ty, + None => { + return Err(tyenv.error( + pos, + format!("Unknown type {} for variable '{}'", def.ty.0, def.var.0), + )) + } + }; + let tid = match tyenv.type_map.get(&tysym) { + Some(tid) => *tid, + None => { + return Err(tyenv.error( + pos, + format!("Unknown type {} for variable '{}'", def.ty.0, def.var.0), + )) + } + }; + + // Evaluate the variable's value. + let val = Box::new(self.translate_expr(tyenv, pos, &def.val, ty, bindings)?); + + // 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)); + } + + // Evaluate the body, expecting the type of the overall let-expr. + let body = Box::new(self.translate_expr(tyenv, pos, body, ty, bindings)?); + let body_ty = body.ty(); + + // Pop the bindings. + bindings.vars.truncate(orig_binding_len); + + Ok(Expr::Let(body_ty, let_defs, body)) + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ast::Ident; + use crate::parser::Parser; + + #[test] + fn build_type_env() { + let text = r" + (type u32 (primitive u32)) + (type A extern (enum (B (f1 u32) (f2 u32)) (C (f1 u32)))) + "; + let ast = Parser::new("file.isle", text) + .parse_defs() + .expect("should parse"); + let tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); + + let sym_a = tyenv.intern(&Ident("A".to_string())).unwrap(); + let sym_b = tyenv.intern(&Ident("A.B".to_string())).unwrap(); + let sym_c = tyenv.intern(&Ident("A.C".to_string())).unwrap(); + let sym_u32 = tyenv.intern(&Ident("u32".to_string())).unwrap(); + let sym_f1 = tyenv.intern(&Ident("f1".to_string())).unwrap(); + let sym_f2 = tyenv.intern(&Ident("f2".to_string())).unwrap(); + + assert_eq!(tyenv.type_map.get(&sym_u32).unwrap(), &TypeId(0)); + assert_eq!(tyenv.type_map.get(&sym_a).unwrap(), &TypeId(1)); + + assert_eq!( + tyenv.types, + vec![ + Type::Primitive(TypeId(0), sym_u32), + Type::Enum { + name: sym_a, + id: TypeId(1), + is_extern: true, + variants: vec![ + Variant { + name: sym_b, + id: VariantId(0), + fields: vec![ + Field { + name: sym_f1, + id: FieldId(0), + ty: TypeId(0), + }, + Field { + name: sym_f2, + id: FieldId(1), + ty: TypeId(0), + }, + ], + }, + Variant { + name: sym_c, + id: VariantId(1), + fields: vec![Field { + name: sym_f1, + id: FieldId(0), + ty: TypeId(0), + },], + }, + ], + pos: Pos { + offset: 58, + line: 3, + col: 18, + }, + }, + ] + ); + } + + #[test] + fn build_rules() { + let text = r" + (type u32 (primitive u32)) + (type A extern (enum (B (f1 u32) (f2 u32)) (C (f1 u32)))) + + (decl T1 (A) u32) + (decl T2 (A A) A) + (decl T3 (u32) A) + + (constructor T1 t1_ctor) + (extractor T2 t2_etor) + + (rule + (T1 _) 1) + (rule + (T2 x =x) (T3 42)) + (rule + (T3 1) (A.C 2)) + (rule -1 + (T3 _) (A.C 3)) + "; + let ast = Parser::new("file.isle", text) + .parse_defs() + .expect("should parse"); + let mut tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); + let _ = TermEnv::from_ast(&mut tyenv, &ast).expect("could not typecheck rules"); + } +} From e08160845e29e4f877b4ec5969bd1f2792999004 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 2 Sep 2021 19:30:28 -0700 Subject: [PATCH 02/95] WIP: rip out a bunch of stuff and rework --- cranelift/isle/.gitmodules | 3 + cranelift/isle/Cargo.lock | 5 + cranelift/isle/Cargo.toml | 1 + cranelift/isle/src/codegen.rs | 123 ++++ cranelift/isle/src/compile.rs | 110 ---- cranelift/isle/src/ir.rs | 1072 ++++++--------------------------- cranelift/isle/src/main.rs | 9 +- cranelift/isle/wasmtime | 1 + 8 files changed, 331 insertions(+), 993 deletions(-) create mode 100644 cranelift/isle/.gitmodules create mode 100644 cranelift/isle/src/codegen.rs create mode 160000 cranelift/isle/wasmtime diff --git a/cranelift/isle/.gitmodules b/cranelift/isle/.gitmodules new file mode 100644 index 0000000000..dd607ad0d9 --- /dev/null +++ b/cranelift/isle/.gitmodules @@ -0,0 +1,3 @@ +[submodule "wasmtime"] + path = wasmtime + url = https://github.com/cfallin/wasmtime diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index b890d9e546..f24658108c 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -62,6 +62,7 @@ version = "0.1.0" dependencies = [ "env_logger", "log", + "peepmatic-automata", "thiserror", ] @@ -86,6 +87,10 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +[[package]] +name = "peepmatic-automata" +version = "0.75.0" + [[package]] name = "proc-macro2" version = "1.0.28" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml index 8774800064..590d8e69bb 100644 --- a/cranelift/isle/Cargo.toml +++ b/cranelift/isle/Cargo.toml @@ -9,3 +9,4 @@ license = "Apache-2.0 WITH LLVM-exception" log = "0.4" env_logger = "0.8" thiserror = "1.0" +peepmatic-automata = { version = "*", path = "wasmtime/cranelift/peepmatic/crates/automata" } diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs new file mode 100644 index 0000000000..e6fb4605b2 --- /dev/null +++ b/cranelift/isle/src/codegen.rs @@ -0,0 +1,123 @@ +//! Generate Rust code from a series of Sequences. + +use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence, Value}; +use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; +use peepmatic_automata::{Automaton, Builder as AutomatonBuilder}; +use std::collections::HashMap; + +// TODO: automata built by output term as well + +/// Builder context for one function in generated code corresponding +/// to one root input term. +struct TermFunctionBuilder { + root_term: TermId, + automaton: AutomatonBuilder, +} + +impl TermFunctionBuilder { + fn new(root_term: TermId) -> Self { + TermFunctionBuilder { + root_term, + automaton: AutomatonBuilder::new(), + } + } + + fn add_rule(&mut self, pattern_seq: PatternSequence, expr_seq: ExprSequence) { + let mut insertion = self.automaton.insert(); + + let mut out_idx = 0; + for (i, inst) in pattern_seq.insts.into_iter().enumerate() { + // Determine how much of the output we can emit at this + // stage (with the `Value`s that will be defined so far, + // given input insts 0..=i). + let out_start = out_idx; + let mut out_end = out_start; + while out_end < expr_seq.insts.len() { + let mut max_input_inst = 0; + expr_seq.insts[out_end].visit_values(|val| { + if let Value::Pattern { inst, .. } = val { + max_input_inst = std::cmp::max(max_input_inst, inst.index()); + } + }); + if max_input_inst > i { + break; + } + out_end += 1; + } + + // Create an ExprSequence for the instructions that we can + // output at this point. + let out_insts = expr_seq.insts[out_start..out_end] + .iter() + .cloned() + .collect::>(); + let out_seq = ExprSequence { insts: out_insts }; + out_idx = out_end; + + insertion.next(inst, out_seq); + } + + insertion.finish(); + } +} + +struct TermFunctionsBuilder<'a> { + typeenv: &'a TypeEnv, + termenv: &'a TermEnv, + builders_by_input: HashMap, + builders_by_output: HashMap, +} + +impl<'a> TermFunctionsBuilder<'a> { + fn new(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Self { + Self { + builders_by_input: HashMap::new(), + builders_by_output: HashMap::new(), + typeenv, + termenv, + } + } + + fn build(&mut self) { + for rule in 0..self.termenv.rules.len() { + let rule = RuleId(rule); + let (lhs_root, pattern, rhs_root, expr) = lower_rule(self.typeenv, self.termenv, rule); + if let Some(input_root_term) = lhs_root { + self.builders_by_input + .entry(input_root_term) + .or_insert_with(|| TermFunctionBuilder::new(input_root_term)) + .add_rule(pattern.clone(), expr.clone()); + } + if let Some(output_root_term) = rhs_root { + self.builders_by_output + .entry(output_root_term) + .or_insert_with(|| TermFunctionBuilder::new(output_root_term)) + .add_rule(pattern, expr); + } + } + } + + fn create_automata(self) -> Automata { + let automata_by_input = self + .builders_by_input + .into_iter() + .map(|(k, mut v)| (k, v.automaton.finish())) + .collect::>(); + let automata_by_output = self + .builders_by_output + .into_iter() + .map(|(k, mut v)| (k, v.automaton.finish())) + .collect::>(); + Automata { + automata_by_input, + automata_by_output, + } + } +} + +pub struct Automata { + pub automata_by_input: HashMap>, + pub automata_by_output: HashMap>, +} + +impl Automata {} diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/src/compile.rs index 0aeea048b9..1544b4c075 100644 --- a/cranelift/isle/src/compile.rs +++ b/cranelift/isle/src/compile.rs @@ -1,111 +1 @@ //! Compilation process, from AST to Sema to Sequences of Insts. - -use crate::error::*; -use crate::{ast, ir, sema}; -use std::collections::HashMap; - -/// A Compiler manages the compilation pipeline from AST to Sequences. -pub struct Compiler<'a> { - ast: &'a ast::Defs, - type_env: sema::TypeEnv, - term_env: sema::TermEnv, - seqs: Vec, - // TODO: if this becomes a perf issue, then build a better data - // structure. For now we index on root term/variant. - // - // TODO: index at callsites (extractors/constructors) too. We'll - // need tree-summaries of arg and expected return value at each - // callsite. - term_db: HashMap, -} - -#[derive(Clone, Debug, Default)] -struct TermData { - producers: Vec<(ir::TreeSummary, sema::RuleId)>, - consumers: Vec<(ir::TreeSummary, sema::RuleId)>, - has_constructor: bool, - has_extractor: bool, -} - -pub type CompileResult = Result; - -impl<'a> Compiler<'a> { - pub fn new(ast: &'a ast::Defs) -> CompileResult> { - let mut type_env = sema::TypeEnv::from_ast(ast)?; - let term_env = sema::TermEnv::from_ast(&mut type_env, ast)?; - Ok(Compiler { - ast, - type_env, - term_env, - seqs: vec![], - term_db: HashMap::new(), - }) - } - - pub fn build_sequences(&mut self) -> CompileResult<()> { - for rid in 0..self.term_env.rules.len() { - let rid = sema::RuleId(rid); - let seq = ir::Sequence::from_rule(&self.type_env, &self.term_env, rid); - self.seqs.push(seq); - } - Ok(()) - } - - pub fn collect_tree_summaries(&mut self) -> CompileResult<()> { - // For each rule, compute summaries of its LHS and RHS, then - // index it in the appropriate TermData. - for (i, seq) in self.seqs.iter().enumerate() { - let rule_id = sema::RuleId(i); - let consumer_summary = seq.input_tree_summary(); - let producer_summary = seq.output_tree_summary(); - if let Some(consumer_root_term) = consumer_summary.root() { - let consumer_termdb = self - .term_db - .entry(consumer_root_term.clone()) - .or_insert_with(|| Default::default()); - consumer_termdb.consumers.push((consumer_summary, rule_id)); - } - if let Some(producer_root_term) = producer_summary.root() { - let producer_termdb = self - .term_db - .entry(producer_root_term.clone()) - .or_insert_with(|| Default::default()); - producer_termdb.consumers.push((producer_summary, rule_id)); - } - } - - // For each term, if a constructor and/or extractor is - // present, note that. - for term in &self.term_env.terms { - if let sema::TermKind::Regular { - extractor, - constructor, - } = term.kind - { - if !extractor.is_some() && !constructor.is_some() { - continue; - } - let entry = self - .term_db - .entry(ir::TermOrVariant::Term(term.id)) - .or_insert_with(|| Default::default()); - if extractor.is_some() { - entry.has_extractor = true; - } - if constructor.is_some() { - entry.has_constructor = true; - } - } - } - - Ok(()) - } - - pub fn inline_internal_terms(&mut self) -> CompileResult<()> { - unimplemented!() - } - - pub fn to_sequences(self) -> Vec { - self.seqs - } -} diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 3bf6c59a4c..850200263c 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -2,23 +2,24 @@ use crate::declare_id; use crate::sema::*; -use std::collections::hash_map::Entry as HashEntry; use std::collections::HashMap; declare_id!(InstId); #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Value(InstId, usize); +pub enum Value { + /// A value produced by an instruction in the Pattern (LHS). + Pattern { inst: InstId, output: usize }, + /// A value produced by an instruction in the Expr (RHS). + Expr { inst: InstId, output: usize }, +} -/// A single node in the sea-of-nodes. Each node produces one value. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Inst { +/// A single Pattern instruction. +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum PatternInst { /// Get the input root-term value. Arg { ty: TypeId }, - /// Set the return value. Produces no values. - Return { ty: TypeId, value: Value }, - /// Match a value as equal to another value. Produces no values. MatchEqual { a: Value, b: Value, ty: TypeId }, @@ -46,7 +47,11 @@ pub enum Inst { arg_tys: Vec, term: TermId, }, +} +/// A single Expr instruction. +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ExprInst { /// Produce a constant integer. ConstInt { ty: TypeId, val: i64 }, @@ -64,120 +69,50 @@ pub enum Inst { term: TermId, }, - /// Copy a value. Used mainly when rewriting/inlining. - Copy { ty: TypeId, val: Value }, - - /// A non-operation (nop). Used to "nop out" unused instructions - /// without renumbering all values. - Nop, + /// Set the return value. Produces no values. + Return { ty: TypeId, value: Value }, } -impl Inst { - fn map_values Value>(&self, f: F) -> Self { +impl ExprInst { + pub fn visit_values(&self, mut f: F) { match self { - &Inst::Arg { ty } => Inst::Arg { ty }, - &Inst::Return { ty, value } => Inst::Return { - ty, - value: f(value), - }, - &Inst::MatchEqual { a, b, ty } => Inst::MatchEqual { - a: f(a), - b: f(b), - ty, - }, - &Inst::MatchInt { input, ty, int_val } => Inst::MatchInt { - input: f(input), - ty, - int_val, - }, - &Inst::MatchVariant { - input, - input_ty, - ref arg_tys, - variant, - } => Inst::MatchVariant { - input: f(input), - input_ty, - arg_tys: arg_tys.clone(), - variant, - }, - &Inst::Extract { - input, - input_ty, - ref arg_tys, - term, - } => Inst::Extract { - input: f(input), - input_ty, - arg_tys: arg_tys.clone(), - term, - }, - &Inst::ConstInt { ty, val } => Inst::ConstInt { ty, val }, - &Inst::CreateVariant { - ref inputs, - ty, - variant, - } => Inst::CreateVariant { - inputs: inputs - .iter() - .map(|(i, ty)| (f(*i), *ty)) - .collect::>(), - ty, - variant, - }, - &Inst::Construct { - ref inputs, - ty, - term, - } => Inst::Construct { - inputs: inputs - .iter() - .map(|(i, ty)| (f(*i), *ty)) - .collect::>(), - ty, - term, - }, - &Inst::Copy { ty, val } => Inst::Copy { ty, val: f(val) }, - &Inst::Nop => Inst::Nop, - } - } - - fn map_insts InstId>(&self, f: F) -> Self { - self.map_values(|val| Value(f(val.0), val.1)) - } - - fn num_results(&self) -> usize { - match self { - &Inst::Arg { .. } - | &Inst::ConstInt { .. } - | &Inst::Construct { .. } - | &Inst::CreateVariant { .. } - | &Inst::Copy { .. } => 1, - &Inst::Return { .. } | &Inst::MatchEqual { .. } | &Inst::MatchInt { .. } => 0, - &Inst::Extract { ref arg_tys, .. } | &Inst::MatchVariant { ref arg_tys, .. } => { - arg_tys.len() + &ExprInst::ConstInt { .. } => {} + &ExprInst::Construct { ref inputs, .. } + | &ExprInst::CreateVariant { ref inputs, .. } => { + for (input, _ty) in inputs { + f(*input); + } + } + &ExprInst::Return { value, .. } => { + f(value); } - &Inst::Nop => 0, } } } -impl Value { - fn map_inst InstId>(&self, f: F) -> Self { - Value(f(self.0), self.1) - } -} - -/// A linear sequence of instructions that either convert an input -/// value to an output value, or fail. +/// A linear sequence of instructions that match on and destructure an +/// argument. A pattern is fallible (may not match). If it does not +/// fail, its result consists of the values produced by the +/// `PatternInst`s, which may be used by a subsequent `Expr`. #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct Sequence { - /// Instruction sequence. InstId indexes into this sequence. - insts: Vec, +pub struct PatternSequence { + /// Instruction sequence for pattern. InstId indexes into this + /// sequence for `Value::Pattern` values. + pub insts: Vec, } -impl Sequence { - fn add_inst(&mut self, inst: Inst) -> InstId { +/// A linear sequence of instructions that produce a new value from +/// the right-hand side of a rule, given bindings that come from a +/// `Pattern` derived from the left-hand side. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +pub struct ExprSequence { + /// Instruction sequence for expression. InstId indexes into this + /// sequence for `Value::Expr` values. + pub insts: Vec, +} + +impl PatternSequence { + fn add_inst(&mut self, inst: PatternInst) -> InstId { let id = InstId(self.insts.len()); self.insts.push(inst); id @@ -185,20 +120,16 @@ impl Sequence { fn add_arg(&mut self, ty: TypeId) -> Value { let inst = InstId(self.insts.len()); - self.add_inst(Inst::Arg { ty }); - Value(inst, 0) - } - - fn add_return(&mut self, ty: TypeId, value: Value) { - self.add_inst(Inst::Return { ty, value }); + self.add_inst(PatternInst::Arg { ty }); + Value::Pattern { inst, output: 0 } } fn add_match_equal(&mut self, a: Value, b: Value, ty: TypeId) { - self.add_inst(Inst::MatchEqual { a, b, ty }); + self.add_inst(PatternInst::MatchEqual { a, b, ty }); } fn add_match_int(&mut self, input: Value, ty: TypeId, int_val: i64) { - self.add_inst(Inst::MatchInt { input, ty, int_val }); + self.add_inst(PatternInst::MatchInt { input, ty, int_val }); } fn add_match_variant( @@ -211,11 +142,11 @@ impl Sequence { let inst = InstId(self.insts.len()); let mut outs = vec![]; for (i, _arg_ty) in arg_tys.iter().enumerate() { - let val = Value(inst, i); + let val = Value::Pattern { inst, output: i }; outs.push(val); } let arg_tys = arg_tys.iter().cloned().collect(); - self.add_inst(Inst::MatchVariant { + self.add_inst(PatternInst::MatchVariant { input, input_ty, arg_tys, @@ -234,11 +165,11 @@ impl Sequence { let inst = InstId(self.insts.len()); let mut outs = vec![]; for (i, _arg_ty) in arg_tys.iter().enumerate() { - let val = Value(inst, i); + let val = Value::Pattern { inst, output: i }; outs.push(val); } let arg_tys = arg_tys.iter().cloned().collect(); - self.add_inst(Inst::Extract { + self.add_inst(PatternInst::Extract { input, input_ty, arg_tys, @@ -247,61 +178,39 @@ impl Sequence { outs } - fn add_const_int(&mut self, ty: TypeId, val: i64) -> Value { - let inst = InstId(self.insts.len()); - self.add_inst(Inst::ConstInt { ty, val }); - Value(inst, 0) - } - - fn add_create_variant( - &mut self, - inputs: &[(Value, TypeId)], - ty: TypeId, - variant: VariantId, - ) -> Value { - let inst = InstId(self.insts.len()); - let inputs = inputs.iter().cloned().collect(); - self.add_inst(Inst::CreateVariant { - inputs, - ty, - variant, - }); - Value(inst, 0) - } - - fn add_construct(&mut self, inputs: &[(Value, TypeId)], ty: TypeId, term: TermId) -> Value { - let inst = InstId(self.insts.len()); - let inputs = inputs.iter().cloned().collect(); - self.add_inst(Inst::Construct { inputs, ty, term }); - Value(inst, 0) - } - + /// Generate PatternInsts to match the given (sub)pattern. Works + /// recursively down the AST. Returns the root term matched by + /// this pattern, if any. fn gen_pattern( &mut self, input: Value, typeenv: &TypeEnv, termenv: &TermEnv, pat: &Pattern, - vars: &mut HashMap, - ) { + vars: &mut HashMap, Value)>, + ) -> Option { match pat { &Pattern::BindPattern(_ty, var, ref subpat) => { // Bind the appropriate variable and recurse. assert!(!vars.contains_key(&var)); - vars.insert(var, input); - self.gen_pattern(input, typeenv, termenv, &*subpat, vars); + vars.insert(var, (None, input)); // bind first, so subpat can use it + let root_term = self.gen_pattern(input, typeenv, termenv, &*subpat, vars); + vars.get_mut(&var).unwrap().0 = root_term; + root_term } &Pattern::Var(ty, var) => { // Assert that the value matches the existing bound var. - let var_val = vars + let (var_val_term, var_val) = vars .get(&var) .cloned() .expect("Variable should already be bound"); self.add_match_equal(input, var_val, ty); + var_val_term } &Pattern::ConstInt(ty, value) => { // Assert that the value matches the constant integer. self.add_match_int(input, ty, value); + None } &Pattern::Term(ty, term, ref args) => { // Determine whether the term has an external extractor or not. @@ -313,35 +222,83 @@ impl Sequence { for (subpat, value) in args.iter().zip(arg_values.into_iter()) { self.gen_pattern(value, typeenv, termenv, subpat, vars); } + None } &TermKind::Regular { .. } => { let arg_values = self.add_extract(input, ty, arg_tys, term); for (subpat, value) in args.iter().zip(arg_values.into_iter()) { self.gen_pattern(value, typeenv, termenv, subpat, vars); } + Some(term) } } } &Pattern::Wildcard(_ty) => { // Nothing! + None } } } +} +impl ExprSequence { + fn add_inst(&mut self, inst: ExprInst) -> InstId { + let id = InstId(self.insts.len()); + self.insts.push(inst); + id + } + + fn add_const_int(&mut self, ty: TypeId, val: i64) -> Value { + let inst = InstId(self.insts.len()); + self.add_inst(ExprInst::ConstInt { ty, val }); + Value::Expr { inst, output: 0 } + } + + fn add_create_variant( + &mut self, + inputs: &[(Value, TypeId)], + ty: TypeId, + variant: VariantId, + ) -> Value { + let inst = InstId(self.insts.len()); + let inputs = inputs.iter().cloned().collect(); + self.add_inst(ExprInst::CreateVariant { + inputs, + ty, + variant, + }); + Value::Expr { inst, output: 0 } + } + + fn add_construct(&mut self, inputs: &[(Value, TypeId)], ty: TypeId, term: TermId) -> Value { + let inst = InstId(self.insts.len()); + let inputs = inputs.iter().cloned().collect(); + self.add_inst(ExprInst::Construct { inputs, ty, term }); + Value::Expr { inst, output: 0 } + } + + fn add_return(&mut self, ty: TypeId, value: Value) { + self.add_inst(ExprInst::Return { ty, value }); + } + + /// Creates a sequence of ExprInsts to generate the given + /// expression value. Returns the value ID as well as the root + /// term ID, if any. fn gen_expr( &mut self, typeenv: &TypeEnv, termenv: &TermEnv, expr: &Expr, - vars: &HashMap, - ) -> Value { + vars: &HashMap, Value)>, + ) -> (Option, Value) { match expr { - &Expr::ConstInt(ty, val) => self.add_const_int(ty, val), + &Expr::ConstInt(ty, val) => (None, self.add_const_int(ty, val)), &Expr::Let(_ty, ref bindings, ref subexpr) => { let mut vars = vars.clone(); for &(var, _var_ty, ref var_expr) in bindings { - let var_value = self.gen_expr(typeenv, termenv, &*var_expr, &vars); - vars.insert(var, var_value); + let (var_value_term, var_value) = + self.gen_expr(typeenv, termenv, &*var_expr, &vars); + vars.insert(var, (var_value_term, var_value)); } self.gen_expr(typeenv, termenv, &*subexpr, &vars) } @@ -351,739 +308,102 @@ impl Sequence { let mut arg_values_tys = vec![]; for (arg_ty, arg_expr) in termdata.arg_tys.iter().cloned().zip(arg_exprs.iter()) { arg_values_tys - .push((self.gen_expr(typeenv, termenv, &*arg_expr, &vars), arg_ty)); + .push((self.gen_expr(typeenv, termenv, &*arg_expr, &vars).1, arg_ty)); } match &termdata.kind { - &TermKind::EnumVariant { variant } => { - self.add_create_variant(&arg_values_tys[..], ty, variant) - } - &TermKind::Regular { .. } => self.add_construct(&arg_values_tys[..], ty, term), + &TermKind::EnumVariant { variant } => ( + None, + self.add_create_variant(&arg_values_tys[..], ty, variant), + ), + &TermKind::Regular { .. } => ( + Some(termdata.id), + self.add_construct(&arg_values_tys[..], ty, term), + ), } } } } } -impl Sequence { - /// Build a sequence from a rule. - pub fn from_rule(tyenv: &TypeEnv, termenv: &TermEnv, rule: RuleId) -> Sequence { - let mut seq: Sequence = Default::default(); +/// Build a sequence from a rule. +pub fn lower_rule( + tyenv: &TypeEnv, + termenv: &TermEnv, + rule: RuleId, +) -> ( + Option, + PatternSequence, + Option, + ExprSequence, +) { + let mut pattern_seq: PatternSequence = Default::default(); + let mut expr_seq: ExprSequence = Default::default(); - // Lower the pattern, starting from the root input value. - let ruledata = &termenv.rules[rule.index()]; - let input_ty = ruledata.lhs.ty(); - let input = seq.add_arg(input_ty); - let mut vars = HashMap::new(); - seq.gen_pattern(input, tyenv, termenv, &ruledata.lhs, &mut vars); + // Lower the pattern, starting from the root input value. + let ruledata = &termenv.rules[rule.index()]; + let input_ty = ruledata.lhs.ty(); + let input = pattern_seq.add_arg(input_ty); + let mut vars = HashMap::new(); + let lhs_root_term = pattern_seq.gen_pattern(input, tyenv, termenv, &ruledata.lhs, &mut vars); - // Lower the expression, making use of the bound variables - // from the pattern. - let rhs_root = seq.gen_expr(tyenv, termenv, &ruledata.rhs, &vars); - // Return the root RHS value. - let output_ty = ruledata.rhs.ty(); - seq.add_return(output_ty, rhs_root); + // Lower the expression, making use of the bound variables + // from the pattern. + let (rhs_root_term, rhs_root) = expr_seq.gen_expr(tyenv, termenv, &ruledata.rhs, &vars); + // Return the root RHS value. + let output_ty = ruledata.rhs.ty(); + expr_seq.add_return(output_ty, rhs_root); - seq - } - - /// Inline sequence(s) in place of given instructions. - pub fn inline(&self, inlines: Vec<(InstId, &'_ Sequence)>) -> Sequence { - let mut seq: Sequence = Default::default(); - // Map from inst ID in this seq to inst ID in final seq. - let mut inst_map: HashMap = HashMap::new(); - - let mut next_inline = 0; - for (id, inst) in self.insts.iter().enumerate() { - let orig_inst_id = InstId(id); - - // If this is an inlining point, do the inlining. The - // inlining point must be at a Construct or Extract call. - // - // - For a Construct inlining, we emit the Construct - // *first*, taking its output value as the arg for the - // invoked sequence. The returned value will in turn be - // substituted for that value at the end of inlining. - // - // - For an Extract inlining, we emit the sequence first, - // taking the input of the Extract as the arg for the - // invoked sequence. The returned value will then be the - // new input to the Extract. - - if next_inline < inlines.len() && inlines[next_inline].0 == orig_inst_id { - let inlined_seq = &inlines[next_inline].1; - next_inline += 1; - - let (arg, arg_ty) = match inst { - &Inst::Construct { ty, .. } => { - // Emit the Construct, mapping its input - // values across the mapping, and saving its - // output as the arg for the inlined sequence. - let inst = inst.map_insts(|id| { - inst_map - .get(&id) - .cloned() - .expect("Should have inst mapping") - }); - let new_inst_id = seq.add_inst(inst); - (Value(new_inst_id, 0), ty) - } - &Inst::Extract { - input, input_ty, .. - } => { - // Map the input and save it as the arg, but - // don't emit the Extract yet. - ( - input.map_inst(|id| { - inst_map - .get(&id) - .cloned() - .expect("Should have inst mapping") - }), - input_ty, - ) - } - _ => panic!("Unexpected instruction {:?} at inlining point", inst), - }; - - // Copy the inlined insts into the output sequence. We - // map `Arg` to the input, and save the `Ret`, which - // must come last. - let mut inlined_inst_map: HashMap = HashMap::new(); - let mut ret: Option<(InstId, TypeId)> = None; - for (i, inst) in inlined_seq.insts.iter().enumerate() { - let inlined_orig_inst_id = InstId(i); - let new_inst_id = InstId(seq.insts.len()); - let inst = match inst { - &Inst::Return { ty, value } => { - let value = - value.map_inst(|id| inlined_inst_map.get(&id).cloned().unwrap()); - ret = Some((new_inst_id, ty)); - Inst::Copy { ty, val: value } - } - &Inst::Arg { ty } => { - assert_eq!(ty, arg_ty); - Inst::Copy { ty, val: arg } - } - _ => inst.map_insts(|id| inlined_inst_map.get(&id).cloned().unwrap()), - }; - let new_id = seq.add_inst(inst); - inlined_inst_map.insert(inlined_orig_inst_id, new_id); - } - - // Now, emit the Extract if appropriate (it comes - // after the inlined sequence, while Construct goes - // before), and map the old inst ID to the resulting - // output of either the Extract or the return above. - let final_inst_id = match inst { - &Inst::Extract { - input_ty, - ref arg_tys, - term, - .. - } => { - let input = Value(ret.unwrap().0, 0); - seq.add_inst(Inst::Extract { - input, - input_ty, - arg_tys: arg_tys.clone(), - term, - }) - } - &Inst::Construct { .. } => ret.unwrap().0, - _ => unreachable!(), - }; - - inst_map.insert(orig_inst_id, final_inst_id); - } else { - // Non-inlining-point instruction. Just copy over, - // mapping values as appropriate. - let inst = inst.map_insts(|id| { - inst_map - .get(&id) - .cloned() - .expect("inst ID should be present") - }); - let new_id = seq.add_inst(inst); - inst_map.insert(orig_inst_id, new_id); - } - } - - seq - } - - /// Perform constant-propagation / simplification across - /// construct/extract pairs, variants and integer values, and - /// copies. - pub fn simplify(&self) -> Option { - #[derive(Clone, Debug)] - enum SymbolicValue { - Value(Value), - ConstInt(Value, i64), - Variant(Value, VariantId, Vec), - Term(Value, TermId, Vec), - } - impl SymbolicValue { - fn to_value(&self) -> Value { - match self { - &SymbolicValue::Value(v) => v, - &SymbolicValue::ConstInt(v, ..) => v, - &SymbolicValue::Variant(v, ..) => v, - &SymbolicValue::Term(v, ..) => v, - } - } - } - let mut value_map: HashMap = HashMap::new(); - let mut seq: Sequence = Default::default(); - - for (i, inst) in self.insts.iter().enumerate() { - let orig_inst_id = InstId(i); - match inst { - &Inst::Arg { .. } => { - let new_inst = seq.add_inst(inst.clone()); - value_map.insert( - Value(orig_inst_id, 0), - SymbolicValue::Value(Value(new_inst, 0)), - ); - } - &Inst::Return { ty, value } => { - let inst = Inst::Return { - ty, - value: value_map.get(&value).unwrap().to_value(), - }; - seq.add_inst(inst); - } - &Inst::MatchEqual { a, b, ty } => { - let sym_a = value_map.get(&a).unwrap(); - let sym_b = value_map.get(&b).unwrap(); - match (sym_a, sym_b) { - ( - &SymbolicValue::ConstInt(_, int_a), - &SymbolicValue::ConstInt(_, int_b), - ) => { - if int_a == int_b { - // No-op -- we can skip it. - continue; - } else { - // We can't possibly match! - return None; - } - } - ( - &SymbolicValue::Term(_, term_a, _), - &SymbolicValue::Term(_, term_b, _), - ) => { - if term_a != term_b { - return None; - } - } - ( - &SymbolicValue::Variant(_, var_a, _), - &SymbolicValue::Variant(_, var_b, _), - ) => { - if var_a != var_b { - return None; - } - } - _ => {} - } - let val_a = sym_a.to_value(); - let val_b = sym_b.to_value(); - seq.add_inst(Inst::MatchEqual { - a: val_a, - b: val_b, - ty, - }); - } - &Inst::MatchInt { input, int_val, ty } => { - let sym_input = value_map.get(&input).unwrap(); - match sym_input { - &SymbolicValue::ConstInt(_, const_val) => { - if int_val == const_val { - // No runtime check needed -- we can continue. - continue; - } else { - // Static mismatch, so we can remove this - // whole Sequence. - return None; - } - } - _ => {} - } - let val_input = sym_input.to_value(); - seq.add_inst(Inst::MatchInt { - input: val_input, - int_val, - ty, - }); - } - &Inst::MatchVariant { - input, - input_ty, - variant, - ref arg_tys, - } => { - let sym_input = value_map.get(&input).unwrap(); - match sym_input { - &SymbolicValue::Variant(_, val_variant, ref args) => { - if val_variant != variant { - return None; - } - // Variant matches: unpack args' symbolic values into results. - let args = args.clone(); - for (i, arg) in args.iter().enumerate() { - let val = Value(orig_inst_id, i); - value_map.insert(val, arg.clone()); - } - } - _ => { - let val_input = sym_input.to_value(); - let new_inst = seq.add_inst(Inst::MatchVariant { - input: val_input, - input_ty, - variant, - arg_tys: arg_tys.clone(), - }); - for i in 0..arg_tys.len() { - let val = Value(orig_inst_id, i); - let sym = SymbolicValue::Value(Value(new_inst, i)); - value_map.insert(val, sym); - } - } - } - } - &Inst::Extract { - input, - input_ty, - term, - ref arg_tys, - } => { - let sym_input = value_map.get(&input).unwrap(); - match sym_input { - &SymbolicValue::Term(_, val_term, ref args) => { - if val_term != term { - return None; - } - // Term matches: unpack args' symbolic values into results. - let args = args.clone(); - for (i, arg) in args.iter().enumerate() { - let val = Value(orig_inst_id, i); - value_map.insert(val, arg.clone()); - } - } - _ => { - let val_input = sym_input.to_value(); - let new_inst = seq.add_inst(Inst::Extract { - input: val_input, - input_ty, - term, - arg_tys: arg_tys.clone(), - }); - for i in 0..arg_tys.len() { - let val = Value(orig_inst_id, i); - let sym = SymbolicValue::Value(Value(new_inst, i)); - value_map.insert(val, sym); - } - } - } - } - &Inst::ConstInt { ty, val } => { - let new_inst = seq.add_inst(Inst::ConstInt { ty, val }); - value_map.insert( - Value(orig_inst_id, 0), - SymbolicValue::ConstInt(Value(new_inst, 0), val), - ); - } - &Inst::CreateVariant { - ref inputs, - variant, - ty, - } => { - let sym_inputs = inputs - .iter() - .map(|input| value_map.get(&input.0).cloned().unwrap()) - .collect::>(); - let inputs = sym_inputs - .iter() - .zip(inputs.iter()) - .map(|(si, (_, ty))| (si.to_value(), *ty)) - .collect::>(); - let new_inst = seq.add_inst(Inst::CreateVariant { - inputs, - variant, - ty, - }); - value_map.insert( - Value(orig_inst_id, 0), - SymbolicValue::Variant(Value(new_inst, 0), variant, sym_inputs), - ); - } - &Inst::Construct { - ref inputs, - term, - ty, - } => { - let sym_inputs = inputs - .iter() - .map(|input| value_map.get(&input.0).cloned().unwrap()) - .collect::>(); - let inputs = sym_inputs - .iter() - .zip(inputs.iter()) - .map(|(si, (_, ty))| (si.to_value(), *ty)) - .collect::>(); - let new_inst = seq.add_inst(Inst::Construct { inputs, term, ty }); - value_map.insert( - Value(orig_inst_id, 0), - SymbolicValue::Term(Value(new_inst, 0), term, sym_inputs), - ); - } - &Inst::Copy { val, .. } => { - let sym_value = value_map.get(&val).cloned().unwrap(); - value_map.insert(Value(orig_inst_id, 0), sym_value); - } - &Inst::Nop => {} - }; - } - - // Now do a pass backward to track which instructions are used. - let mut used = vec![false; seq.insts.len()]; - for (id, inst) in seq.insts.iter().enumerate().rev() { - // Mark roots as used unconditionally: Return, MatchEqual, - // MatchInt, MatchVariant. - match inst { - &Inst::Return { .. } - | &Inst::MatchEqual { .. } - | &Inst::MatchInt { .. } - | &Inst::MatchVariant { .. } => used[id] = true, - _ => {} - } - // If this instruction is not used, continue. - if !used[id] { - continue; - } - // Otherwise, mark all inputs as used as well. - match inst { - &Inst::Return { value, .. } => used[value.0.index()] = true, - &Inst::MatchEqual { a, b, .. } => { - used[a.0.index()] = true; - used[b.0.index()] = true; - } - &Inst::MatchInt { input, .. } - | &Inst::MatchVariant { input, .. } - | &Inst::Extract { input, .. } => { - used[input.0.index()] = true; - } - &Inst::CreateVariant { ref inputs, .. } | Inst::Construct { ref inputs, .. } => { - for input in inputs { - used[input.0 .0.index()] = true; - } - } - &Inst::Copy { val, .. } => { - used[val.0.index()] = true; - } - &Inst::Arg { .. } | &Inst::ConstInt { .. } => {} - &Inst::Nop => {} - } - } - - // Now, remove any non-used instructions. - for id in 0..seq.insts.len() { - if !used[id] { - seq.insts[id] = Inst::Nop; - } - } - - Some(seq) - } - - /// Build a tree summary of the output produced by a sequence. - pub fn output_tree_summary(&self) -> TreeSummary { - // Scan forward, building a TreeSummary for what is known - // about each value (a "lower bound" on its shape). - let mut value_summaries: HashMap = HashMap::new(); - for (id, inst) in self.insts.iter().enumerate() { - let inst_id = InstId(id); - match inst { - &Inst::Arg { .. } => { - value_summaries.insert(Value(inst_id, 0), TreeSummary::Other); - } - &Inst::Return { value, .. } => { - return value_summaries - .get(&value) - .cloned() - .unwrap_or(TreeSummary::Other); - } - &Inst::MatchEqual { .. } - | &Inst::MatchInt { .. } - | &Inst::MatchVariant { .. } - | &Inst::Extract { .. } => {} - &Inst::ConstInt { val, .. } => { - value_summaries.insert(Value(inst_id, 0), TreeSummary::ConstInt(val)); - } - &Inst::CreateVariant { - ref inputs, - variant, - .. - } => { - let args = inputs - .iter() - .map(|(val, _)| { - value_summaries - .get(&val) - .cloned() - .unwrap_or(TreeSummary::Other) - }) - .collect::>(); - value_summaries.insert(Value(inst_id, 0), TreeSummary::Variant(variant, args)); - } - &Inst::Construct { - ref inputs, term, .. - } => { - let args = inputs - .iter() - .map(|(val, _)| { - value_summaries - .get(&val) - .cloned() - .unwrap_or(TreeSummary::Other) - }) - .collect::>(); - value_summaries.insert(Value(inst_id, 0), TreeSummary::Term(term, args)); - } - &Inst::Copy { val, .. } => { - // Copy summary from input to output. - let input_value = value_summaries - .get(&val) - .cloned() - .unwrap_or(TreeSummary::Other); - value_summaries.insert(Value(inst_id, 0), input_value); - } - &Inst::Nop => {} - } - } - - panic!("Sequence did not end in Return") - } - - /// Build a tree summary of the input expected by a sequence. - pub fn input_tree_summary(&self) -> TreeSummary { - // Scan backward, building a TreeSummary for each value (a - // "lower bound" on what it must be to satisfy the sequence's - // conditions). - let mut value_summaries: HashMap = HashMap::new(); - for (id, inst) in self.insts.iter().enumerate().rev() { - let inst_id = InstId(id); - match inst { - &Inst::Arg { .. } => { - // Must *start* with Arg; otherwise we might have missed some condition. - assert_eq!(id, 0); - return value_summaries - .get(&Value(inst_id, 0)) - .cloned() - .unwrap_or(TreeSummary::Other); - } - &Inst::Return { .. } => {} - - &Inst::MatchEqual { a, b, .. } => { - if value_summaries.contains_key(&a) && !value_summaries.contains_key(&b) { - let val = value_summaries.get(&a).cloned().unwrap(); - value_summaries.insert(b, val); - } else if value_summaries.contains_key(&b) && !value_summaries.contains_key(&a) - { - let val = value_summaries.get(&b).cloned().unwrap(); - value_summaries.insert(a, val); - } else if value_summaries.contains_key(&a) && value_summaries.contains_key(&b) { - let val_a = value_summaries.get(&a).cloned().unwrap(); - let val_b = value_summaries.get(&b).cloned().unwrap(); - let combined = TreeSummary::Conjunction(vec![val_a, val_b]); - value_summaries.insert(a, combined.clone()); - value_summaries.insert(b, combined); - } - } - &Inst::MatchInt { input, int_val, .. } => { - value_summaries.insert(input, TreeSummary::ConstInt(int_val)); - } - &Inst::MatchVariant { - input, - variant, - ref arg_tys, - .. - } => { - let args = (0..arg_tys.len()) - .map(|i| Value(inst_id, i)) - .map(|val| { - value_summaries - .get(&val) - .cloned() - .unwrap_or(TreeSummary::Other) - }) - .collect::>(); - let summary = TreeSummary::Variant(variant, args); - match value_summaries.entry(input) { - HashEntry::Vacant(v) => { - v.insert(summary); - } - HashEntry::Occupied(mut o) => { - let combined = TreeSummary::Conjunction(vec![ - summary, - std::mem::replace(o.get_mut(), TreeSummary::Other), - ]); - *o.get_mut() = combined; - } - } - } - - &Inst::Extract { - input, - term, - ref arg_tys, - .. - } => { - let args = (0..arg_tys.len()) - .map(|i| Value(inst_id, i)) - .map(|val| { - value_summaries - .get(&val) - .cloned() - .unwrap_or(TreeSummary::Other) - }) - .collect::>(); - let summary = TreeSummary::Term(term, args); - match value_summaries.entry(input) { - HashEntry::Vacant(v) => { - v.insert(summary); - } - HashEntry::Occupied(mut o) => { - let combined = TreeSummary::Conjunction(vec![ - summary, - std::mem::replace(o.get_mut(), TreeSummary::Other), - ]); - *o.get_mut() = combined; - } - } - } - - &Inst::ConstInt { .. } | &Inst::CreateVariant { .. } | &Inst::Construct { .. } => {} - - &Inst::Copy { val, .. } => { - // Copy summary from output to input. - let output_value = value_summaries - .get(&Value(inst_id, 0)) - .cloned() - .unwrap_or(TreeSummary::Other); - value_summaries.insert(val, output_value); - } - - &Inst::Nop => {} - } - } - - panic!("Sequence did not start with Arg") - } + (lhs_root_term, pattern_seq, rhs_root_term, expr_seq) } -/// A "summary" of a tree shape -- a template that describes a tree of -/// terms and constant integer values. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TreeSummary { - /// A known term, with given subtrees. - Term(TermId, Vec), - /// A known enum variant, with given subtrees. - Variant(VariantId, Vec), - /// A known constant integer value. - ConstInt(i64), - /// All of a list of summaries: represents a combined list of - /// requirements. The "provides" relation is satisfied if the - /// provider provides *all* of the providee's summaries in the - /// conjunction. A conjunction on the provider side (i.e., as an - /// "output summary") is illegal. - Conjunction(Vec), - /// Something else. - Other, -} +impl peepmatic_automata::Output for ExprSequence { + fn empty() -> ExprSequence { + Default::default() + } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum TreeSummaryOverlap { - Never, - Sometimes, - Always, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum TermOrVariant { - Term(TermId), - Variant(VariantId), -} - -impl TreeSummary { - /// Does a term tree matching this summary "provide" the shape - /// described/expected by another summary? Answer can be "always", - /// "possibly", or "no". - pub fn provides(&self, other: &TreeSummary) -> TreeSummaryOverlap { - match (self, other) { - (_, &TreeSummary::Other) => TreeSummaryOverlap::Always, - (&TreeSummary::Other, _) => TreeSummaryOverlap::Sometimes, - - (&TreeSummary::Conjunction(..), _) => { - panic!("Conjunction on LHS of `provides` relation") + fn prefix(a: &Self, b: &Self) -> Self { + let mut prefix = vec![]; + for (a_inst, b_inst) in a.insts.iter().zip(b.insts.iter()) { + if *a_inst != *b_inst { + break; } - (this, &TreeSummary::Conjunction(ref args)) => args + prefix.push(a_inst.clone()); + } + ExprSequence { insts: prefix } + } + + fn difference(a: &Self, b: &Self) -> Self { + debug_assert!( + a.insts .iter() - .map(|arg| this.provides(arg)) - .min() - .unwrap_or(TreeSummaryOverlap::Always), - - ( - &TreeSummary::Term(self_term, ref self_args), - &TreeSummary::Term(other_term, ref other_args), - ) => { - if self_term != other_term { - TreeSummaryOverlap::Never - } else { - assert_eq!(self_args.len(), other_args.len()); - self_args - .iter() - .zip(other_args.iter()) - .map(|(self_arg, other_arg)| self_arg.provides(other_arg)) - .min() - .unwrap_or(TreeSummaryOverlap::Always) - } - } - - ( - &TreeSummary::Variant(self_var, ref self_args), - &TreeSummary::Variant(other_var, ref other_args), - ) => { - if self_var != other_var { - TreeSummaryOverlap::Never - } else { - assert_eq!(self_args.len(), other_args.len()); - self_args - .iter() - .zip(other_args.iter()) - .map(|(self_arg, other_arg)| self_arg.provides(other_arg)) - .min() - .unwrap_or(TreeSummaryOverlap::Always) - } - } - - (&TreeSummary::ConstInt(i1), &TreeSummary::ConstInt(i2)) => { - if i1 != i2 { - TreeSummaryOverlap::Never - } else { - TreeSummaryOverlap::Always - } - } - - _ => TreeSummaryOverlap::Never, + .zip(b.insts.iter()) + .all(|(a_inst, b_inst)| *a_inst == *b_inst), + "b ({:?}) is not a prefix of a ({:?})", + b, + a + ); + ExprSequence { + insts: a + .insts + .iter() + .cloned() + .skip(b.insts.len()) + .collect::>(), } } - pub fn root(&self) -> Option { - match self { - &TreeSummary::Term(term, ..) => Some(TermOrVariant::Term(term)), - &TreeSummary::Variant(variant, ..) => Some(TermOrVariant::Variant(variant)), - _ => None, + fn concat(a: &Self, b: &Self) -> Self { + ExprSequence { + insts: a + .insts + .iter() + .cloned() + .chain(b.insts.iter().cloned()) + .collect::>(), } } + + fn is_empty(&self) -> bool { + self.insts.is_empty() + } } diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs index b9364551e8..7ba3cce903 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/main.rs @@ -4,6 +4,7 @@ use std::io::stdin; use std::io::Read; mod ast; +mod codegen; mod compile; mod error; mod ir; @@ -16,13 +17,7 @@ fn main() -> Result<(), error::Error> { let mut input = String::new(); stdin().read_to_string(&mut input)?; let mut parser = parser::Parser::new("", &input[..]); - let defs = parser.parse_defs()?; - let mut compiler = compile::Compiler::new(&defs)?; - compiler.build_sequences()?; - compiler.collect_tree_summaries()?; + let _defs = parser.parse_defs()?; - for seq in compiler.to_sequences() { - println!("---\nsequence\n---\n{:?}\n", seq); - } Ok(()) } diff --git a/cranelift/isle/wasmtime b/cranelift/isle/wasmtime new file mode 160000 index 0000000000..e4d4b09243 --- /dev/null +++ b/cranelift/isle/wasmtime @@ -0,0 +1 @@ +Subproject commit e4d4b092431d12d0f0c6dff1cff923572a6fbb77 From 6a567924cd611fcd8d26583d26841d0fb2709aa9 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 2 Sep 2021 22:24:40 -0700 Subject: [PATCH 03/95] WIP --- cranelift/isle/src/codegen.rs | 20 +++++++++++++++++++- cranelift/isle/src/compile.rs | 10 ++++++++++ cranelift/isle/src/main.rs | 5 +++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index e6fb4605b2..92c4d94188 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -1,5 +1,6 @@ //! Generate Rust code from a series of Sequences. +use crate::error::Error; use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence, Value}; use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; use peepmatic_automata::{Automaton, Builder as AutomatonBuilder}; @@ -70,6 +71,8 @@ struct TermFunctionsBuilder<'a> { impl<'a> TermFunctionsBuilder<'a> { fn new(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Self { + log::trace!("typeenv: {:?}", typeenv); + log::trace!("termenv: {:?}", termenv); Self { builders_by_input: HashMap::new(), builders_by_output: HashMap::new(), @@ -82,6 +85,14 @@ impl<'a> TermFunctionsBuilder<'a> { for rule in 0..self.termenv.rules.len() { let rule = RuleId(rule); let (lhs_root, pattern, rhs_root, expr) = lower_rule(self.typeenv, self.termenv, rule); + log::trace!( + "build:\n- rule {:?}\n- lhs_root {:?} rhs_root {:?}\n- pattern {:?}\n- expr {:?}", + self.termenv.rules[rule.index()], + lhs_root, + rhs_root, + pattern, + expr + ); if let Some(input_root_term) = lhs_root { self.builders_by_input .entry(input_root_term) @@ -115,9 +126,16 @@ impl<'a> TermFunctionsBuilder<'a> { } } +#[derive(Clone, Debug)] pub struct Automata { pub automata_by_input: HashMap>, pub automata_by_output: HashMap>, } -impl Automata {} +impl Automata { + pub fn compile(typeenv: &TypeEnv, termenv: &TermEnv) -> Result { + let mut builder = TermFunctionsBuilder::new(typeenv, termenv); + builder.build(); + Ok(builder.create_automata()) + } +} diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/src/compile.rs index 1544b4c075..52e9c29483 100644 --- a/cranelift/isle/src/compile.rs +++ b/cranelift/isle/src/compile.rs @@ -1 +1,11 @@ //! Compilation process, from AST to Sema to Sequences of Insts. + +use crate::{ast, sema, ir, codegen}; +use crate::error::Error; + +pub fn compile(defs: &ast::Defs) -> Result { + let mut typeenv = sema::TypeEnv::from_ast(defs)?; + let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; + let automata = codegen::Automata::compile(&typeenv, &termenv)?; + Ok(automata) +} diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs index 7ba3cce903..922afbb57e 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/main.rs @@ -17,7 +17,8 @@ fn main() -> Result<(), error::Error> { let mut input = String::new(); stdin().read_to_string(&mut input)?; let mut parser = parser::Parser::new("", &input[..]); - let _defs = parser.parse_defs()?; - + let defs = parser.parse_defs()?; + let automata = compile::compile(&defs)?; + println!("automata: {:?}", automata); Ok(()) } From f2399c5384e4b78e854530c96a27cc044df103a3 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Fri, 3 Sep 2021 17:46:52 -0700 Subject: [PATCH 04/95] WIP -- more thinking about how to work priorities into FSM --- cranelift/isle/src/ast.rs | 2 + cranelift/isle/src/codegen.rs | 78 ++++++++++++++++++++++++++++++++++- cranelift/isle/src/parser.rs | 7 ++++ cranelift/isle/src/sema.rs | 10 ++++- 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index 97b7facffa..53a0698d95 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -122,6 +122,8 @@ pub enum Extern { func: Ident, /// The position of this decl. pos: Pos, + /// Whether this extractor is infallible (always matches). + infallible: bool, }, /// An external constructor: `(constructor Term rustfunc)` form. Constructor { diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 92c4d94188..3c22ed44a9 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -6,13 +6,87 @@ use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; use peepmatic_automata::{Automaton, Builder as AutomatonBuilder}; use std::collections::HashMap; -// TODO: automata built by output term as well +/// One "input symbol" for the automaton that handles matching on a +/// term. Each symbol represents one step: we either run a match op, +/// or we get a result from it. +/// +/// Note that in the original Peepmatic scheme, the problem that this +/// solves was handled slightly differently. The automaton responded +/// to alphabet symbols that corresponded only to match results, and +/// the "extra state" was used at each automaton node to represent the +/// op to run next. This extra state differentiated nodes that would +/// otherwise be merged together by deduplication. That scheme works +/// well enough, but the "extra state" is slightly confusing and +/// diverges slightly from a pure automaton. +/// +/// Instead, here, we imagine that the user of the automaton can query +/// the possible transition edges out of the current state. Each of +/// these edges corresponds to one possible match op to run. After +/// running a match op, we reach a new state corresponding to +/// successful matches up to that point. +/// +/// However, it's a bit more subtle than this; we add one additional +/// dimension to each match op, and an additional alphabet symbol. +/// +/// First, consider the prioritization problem. We want to give the +/// DSL user the ability to change the order in which rules apply, for +/// example to have a tier of "fallback rules" that apply only if more +/// custom rules do not match. +/// +/// A somewhat simplistic answer to this problem is "more specific +/// rule wins". However, this implies the existence of a total +/// ordering of linearized match sequences that may not fully capture +/// the intuitive meaning of "more specific". Consider four left-hand +/// sides: +/// +/// - (A _ _) +/// - (A (B _) _) +/// - (A _ (B _)) +/// +/// Intuitively, the first is the least specific. Given the input `(A +/// (B 1) (B 2)`, we can say for sure that the first should not be +/// chosen, because either the second or third would match "more" of +/// the input tree. But which of the second and third should be +/// chosen? A "lexicographic ordering" rule would say that we sort +/// left-hand sides such that the `(B _)` sub-pattern comes before the +/// wildcard `_`, so the second rule wins. But that is arbitrarily +/// privileging one over the other based on the order of the +/// arguments. +/// +/// Instead, we add a priority to every rule (optionally specified in +/// the source and defaulting to `0` otherwise) that conceptually +/// augments match-ops. Then, when we examine out-edges from a state +/// to decide on the next match, we sort these by highest priority +/// first. +/// +/// This, too, sacrifices some deduplication, so we refine the idea a +/// bit. First, we add an "End of Match" alphabet symbol that +/// represents a successful match. Then we stipulate that priorities +/// are attached *only* to "End of Match"... +/// +/// -- ah, this doesn't work because we need the (min, max) priority +/// range on outbound edges. When we see a possible transition to EOM +/// at prio 10 or a match op that could lead to an EOM at prio 0 or +/// 20, we need to do both, NFA-style. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum AutomataInput { + Match { op: PatternInst }, + EndOfMatch { prio: i32 }, +} /// Builder context for one function in generated code corresponding /// to one root input term. +/// +/// A `TermFunctionBuilder` can correspond to the matching +/// control-flow and operations that we execute either when evaluating +/// *forward* on a term, trying to match left-hand sides against it +/// and transforming it into another term; or *backward* on a term, +/// trying to match another rule's left-hand side against an input to +/// produce the term in question (when the term is used in the LHS of +/// the calling term). struct TermFunctionBuilder { root_term: TermId, - automaton: AutomatonBuilder, + automaton: AutomatonBuilder, } impl TermFunctionBuilder { diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index a7759cc306..7bd7c8d058 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -238,12 +238,19 @@ impl<'a> Parser<'a> { fn parse_etor(&mut self) -> ParseResult { let pos = self.pos(); + let infallible = if self.is_sym_str("infallible") { + self.symbol()?; + true + } else { + false + }; let term = self.parse_ident()?; let func = self.parse_ident()?; Ok(Extern::Extractor { term, func, pos: pos.unwrap(), + infallible, }) } diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index a11faccc49..9e2bab9fe0 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -90,13 +90,18 @@ pub struct Term { #[derive(Clone, Debug, PartialEq, Eq)] pub enum TermKind { EnumVariant { + /// Which variant of the enum: e.g. for enum type `A` if a + /// term is `(A.A1 ...)` then the variant ID corresponds to + /// `A1`. variant: VariantId, }, Regular { // Producer and consumer rules are catalogued separately after // building Sequences. Here we just record whether an // extractor and/or constructor is known. - extractor: Option, + /// Extractor func and `infallible` flag. + extractor: Option<(Sym, bool)>, + /// Constructor func. constructor: Option, }, } @@ -472,6 +477,7 @@ impl TermEnv { ref term, ref func, pos, + infallible, }) => { let term_sym = tyenv.intern_mut(term); let func_sym = tyenv.intern_mut(func); @@ -503,7 +509,7 @@ impl TermEnv { ), )); } - *extractor = Some(func_sym); + *extractor = Some((func_sym, infallible)); } } } From 77ed8618578abc7a7ad5848a054280dbfc680f84 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Fri, 3 Sep 2021 18:53:22 -0700 Subject: [PATCH 05/95] Start of significant rework: compile to a trie, not an FSM, and handle rule priorities appropriately. See long block comment in codegen.rs. In brief, I think we actually want to compile to a trie with priority-intervals, a sort of hybrid of a priority tree and a trie representing decisions keyed on match-ops (PatternInsts). The reasons are: 1. The lexicographic ordering that is fundamental to the FSM-building in the Peepmatic view of the problem is sort of fundamentally limited w.r.t. our notion of rule priorities. See the example in the block comment. 2. While the FSM is nice for interpreter-based execution, when compiling to a language with structured control flow, what we really want is a tree; otherwise, if we want to form DAGs to share substructure, we need something like a "diamond-recovery" algorithm that finds common suffixes of *input match-op sequences*, and then we need to incorporate something like phi-nodes in order to allow captures from either side of the diamond to be used. 3. One of the main advantages of the automaton/transducer approach, namely sharing suffixes of the *output* sequence (emitting partial output at each state transition), is unfortunately not applicable if we allow the overall function to be partial. Otherwise, there is always the possibility that we fail at the last match op, so we cannot allow any external constructors to be called until we reach the final state anyway. 4. Pragmatically, I found I was having to significantly edit the peepmatic_automata implementation to adapt to this use-case (compilation to Rust), and it seemed more practical to design the data structure we want than to try to shoehorn the existing thing into the new problem. WIP, hopefully working soon. --- cranelift/isle/Cargo.lock | 5 - cranelift/isle/Cargo.toml | 1 - cranelift/isle/src/codegen.rs | 255 +++++++++++++++++++++++----------- cranelift/isle/src/ir.rs | 52 ------- cranelift/isle/wasmtime | 1 - 5 files changed, 174 insertions(+), 140 deletions(-) delete mode 160000 cranelift/isle/wasmtime diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index f24658108c..b890d9e546 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -62,7 +62,6 @@ version = "0.1.0" dependencies = [ "env_logger", "log", - "peepmatic-automata", "thiserror", ] @@ -87,10 +86,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" -[[package]] -name = "peepmatic-automata" -version = "0.75.0" - [[package]] name = "proc-macro2" version = "1.0.28" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml index 590d8e69bb..8774800064 100644 --- a/cranelift/isle/Cargo.toml +++ b/cranelift/isle/Cargo.toml @@ -9,4 +9,3 @@ license = "Apache-2.0 WITH LLVM-exception" log = "0.4" env_logger = "0.8" thiserror = "1.0" -peepmatic-automata = { version = "*", path = "wasmtime/cranelift/peepmatic/crates/automata" } diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 3c22ed44a9..b9c0c55e58 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -3,11 +3,10 @@ use crate::error::Error; use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence, Value}; use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; -use peepmatic_automata::{Automaton, Builder as AutomatonBuilder}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; -/// One "input symbol" for the automaton that handles matching on a -/// term. Each symbol represents one step: we either run a match op, +/// One "input symbol" for the decision tree that handles matching on +/// a term. Each symbol represents one step: we either run a match op, /// or we get a result from it. /// /// Note that in the original Peepmatic scheme, the problem that this @@ -53,25 +52,166 @@ use std::collections::HashMap; /// privileging one over the other based on the order of the /// arguments. /// -/// Instead, we add a priority to every rule (optionally specified in -/// the source and defaulting to `0` otherwise) that conceptually -/// augments match-ops. Then, when we examine out-edges from a state -/// to decide on the next match, we sort these by highest priority -/// first. +/// Instead, we need a data structure that can associate matching +/// inputs *with priorities* to outputs, and provide us with a +/// decision tree as output. /// -/// This, too, sacrifices some deduplication, so we refine the idea a -/// bit. First, we add an "End of Match" alphabet symbol that -/// represents a successful match. Then we stipulate that priorities -/// are attached *only* to "End of Match"... +/// Why a tree and not a fully general FSM? Because we're compiling +/// to a structured language, Rust, and states become *program points* +/// rather than *data*, we cannot easily support a DAG structure. In +/// other words, we are not producing a FSM that we can interpret at +/// runtime; rather we are compiling code in which each state +/// corresponds to a sequence of statements and control-flow that +/// branches to a next state, we naturally need nesting; we cannot +/// codegen arbitrary state transitions in an efficient manner. We +/// could support a limited form of DAG that reifies "diamonds" (two +/// alternate paths that reconverge), but supporting this in a way +/// that lets the output refer to values from either side is very +/// complex (we need to invent phi-nodes), and the cases where we want +/// to do this rather than invoke a sub-term (that is compiled to a +/// separate function) are rare. Finally, note that one reason to +/// deduplicate nodes and turn a tree back into a DAG -- +/// "output-suffix sharing" as some other instruction-rewriter +/// engines, such as Peepmatic, do -- is not done. However, +/// "output-prefix sharing" is more important to deduplicate code and +/// we do do this.) /// -/// -- ah, this doesn't work because we need the (min, max) priority -/// range on outbound edges. When we see a possible transition to EOM -/// at prio 10 or a match op that could lead to an EOM at prio 0 or -/// 20, we need to do both, NFA-style. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum AutomataInput { +/// We prepare for codegen by building a "prioritized trie", where the +/// trie associates input strings with priorities to output values. +/// Each input string is a sequence of match operators followed by an +/// "end of match" token, and each output is a sequence of ops that +/// build the output expression. Each input-output mapping is +/// associated with a priority. The goal of the trie is to generate a +/// decision-tree procedure that lets us execute match ops in a +/// deterministic way, eventually landing at a state that corresponds +/// to the highest-priority matching rule and can produce the output. +/// +/// To build this trie, we construct nodes with edges to child nodes; +/// each edge consists of (i) one input token (a `PatternInst` or +/// EOM), and (ii) the minimum and maximum priorities of rules along +/// this edge. In a way this resembles an interval tree, though the +/// intervals of children need not be disjoint. +/// +/// To add a rule to this trie, we perform the usual trie-insertion +/// logic, creating edges and subnodes where necessary, and updating +/// the priority-range of each edge that we traverse to include the +/// priority of the inserted rule. +/// +/// However, we need to be a little bit careful, because with only +/// priority ranges in place and the potential for overlap, we have +/// something that resembles an NFA. For example, consider the case +/// where we reach a node in the trie and have two edges with two +/// match ops, one corresponding to a rule with priority 10, and the +/// other corresponding to two rules, with priorities 20 and 0. The +/// final match could lie along *either* path, so we have to traverse +/// both. +/// +/// So, to avoid this, we perform a sort of NFA-to-DFA conversion "on +/// the fly" as we insert nodes by duplicating subtrees. At any node, +/// when inserting with a priority P and when outgoing edges lie in a +/// range [P_lo, P_hi] such that P >= P_lo and P <= P_hi, we +/// "priority-split the edges" at priority P. +/// +/// To priority-split the edges in a node at priority P: +/// +/// - For each out-edge with priority [P_lo, P_hi] s.g. P \in [P_lo, +/// P_hi], and token T: +/// - Trim the subnode at P, yielding children C_lo and C_hi. +/// - Both children must be non-empty (have at least one leaf) +/// because the original node must have had a leaf at P_lo +/// and a leaf at P_hi. +/// - Replace the one edge with two edges, one for each child, with +/// the original match op, and with ranges calculated according to +/// the trimmed children. +/// +/// To trim a node into range [P_lo, P_hi]: +/// +/// - For a decision node: +/// - If any edges have a range outside the bounds of the trimming +/// range, trim the bounds of the edge, and trim the subtree under the +/// edge into the trimmed edge's range. If the subtree is trimmed +/// to `None`, remove the edge. +/// - If all edges are removed, the decision node becomes `None`. +/// - For a leaf node: +/// - If the priority is outside the range, the node becomes `None`. +/// +/// As we descend a path to insert a leaf node, we (i) priority-split +/// if any edges' priority ranges overlap the insertion priority +/// range, and (ii) expand priority ranges on edges to include the new +/// leaf node's priority. +/// +/// As long as we do this, we ensure the two key priority-trie +/// invariants: +/// +/// 1. At a given node, no two edges exist with priority ranges R_1, +/// R_2 such that R_1 ∩ R_2 ≠ ∅, unless R_1 and R_2 are unit ranges +/// ([x, x]) and are on edges with different match-ops. +/// 2. Along the path from the root to any leaf node with priority P, +/// each edge has a priority range R such that P ∈ R. +/// +/// Note that this means that multiple edges with a single match-op +/// may exist, with different priorities. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum TrieSymbol { Match { op: PatternInst }, - EndOfMatch { prio: i32 }, + EndOfMatch, +} + +#[derive(Clone, Debug)] +struct TrieEdge { + key: (PrioRange, TrieSymbol), + node: Box, +} + +type Prio = i64; + +#[derive(Clone, Copy, Debug)] +struct PrioRange(Prio, Prio); + +impl std::cmp::PartialOrd for PrioRange { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl std::cmp::Ord for PrioRange { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if self.1 < other.0 { + std::cmp::Ordering::Less + } else if self.0 > other.1 { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal + } + } +} +impl std::cmp::PartialEq for PrioRange { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == std::cmp::Ordering::Equal + } +} +impl std::cmp::Eq for PrioRange {} + +#[derive(Clone, Debug)] +enum TrieNode { + Decision { + edges: BTreeMap<(PrioRange, TrieSymbol), TrieNode>, + }, + Leaf { + prio: Prio, + output: Vec, + }, + Empty, +} + +impl TrieNode { + fn insert( + &mut self, + prio: Prio, + input: impl Iterator, + output: ExprSequence, + ) { + unimplemented!() + } } /// Builder context for one function in generated code corresponding @@ -86,53 +226,20 @@ enum AutomataInput { /// the calling term). struct TermFunctionBuilder { root_term: TermId, - automaton: AutomatonBuilder, + trie: TrieNode, } impl TermFunctionBuilder { fn new(root_term: TermId) -> Self { TermFunctionBuilder { root_term, - automaton: AutomatonBuilder::new(), + trie: TrieNode::Empty, } } - fn add_rule(&mut self, pattern_seq: PatternSequence, expr_seq: ExprSequence) { - let mut insertion = self.automaton.insert(); - - let mut out_idx = 0; - for (i, inst) in pattern_seq.insts.into_iter().enumerate() { - // Determine how much of the output we can emit at this - // stage (with the `Value`s that will be defined so far, - // given input insts 0..=i). - let out_start = out_idx; - let mut out_end = out_start; - while out_end < expr_seq.insts.len() { - let mut max_input_inst = 0; - expr_seq.insts[out_end].visit_values(|val| { - if let Value::Pattern { inst, .. } = val { - max_input_inst = std::cmp::max(max_input_inst, inst.index()); - } - }); - if max_input_inst > i { - break; - } - out_end += 1; - } - - // Create an ExprSequence for the instructions that we can - // output at this point. - let out_insts = expr_seq.insts[out_start..out_end] - .iter() - .cloned() - .collect::>(); - let out_seq = ExprSequence { insts: out_insts }; - out_idx = out_end; - - insertion.next(inst, out_seq); - } - - insertion.finish(); + fn add_rule(&mut self, prio: Prio, pattern_seq: PatternSequence, expr_seq: ExprSequence) { + self.trie + .insert(prio, pattern_seq.insts.into_iter(), expr_seq); } } @@ -158,6 +265,8 @@ impl<'a> TermFunctionsBuilder<'a> { fn build(&mut self) { for rule in 0..self.termenv.rules.len() { let rule = RuleId(rule); + let prio = self.termenv.rules[rule.index()].prio.unwrap_or(0); + let (lhs_root, pattern, rhs_root, expr) = lower_rule(self.typeenv, self.termenv, rule); log::trace!( "build:\n- rule {:?}\n- lhs_root {:?} rhs_root {:?}\n- pattern {:?}\n- expr {:?}", @@ -171,45 +280,29 @@ impl<'a> TermFunctionsBuilder<'a> { self.builders_by_input .entry(input_root_term) .or_insert_with(|| TermFunctionBuilder::new(input_root_term)) - .add_rule(pattern.clone(), expr.clone()); + .add_rule(prio, pattern.clone(), expr.clone()); } if let Some(output_root_term) = rhs_root { self.builders_by_output .entry(output_root_term) .or_insert_with(|| TermFunctionBuilder::new(output_root_term)) - .add_rule(pattern, expr); + .add_rule(prio, pattern, expr); } } } - - fn create_automata(self) -> Automata { - let automata_by_input = self - .builders_by_input - .into_iter() - .map(|(k, mut v)| (k, v.automaton.finish())) - .collect::>(); - let automata_by_output = self - .builders_by_output - .into_iter() - .map(|(k, mut v)| (k, v.automaton.finish())) - .collect::>(); - Automata { - automata_by_input, - automata_by_output, - } - } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Automata { - pub automata_by_input: HashMap>, - pub automata_by_output: HashMap>, + pub automata_by_input: HashMap, + pub automata_by_output: HashMap, } impl Automata { pub fn compile(typeenv: &TypeEnv, termenv: &TermEnv) -> Result { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); - Ok(builder.create_automata()) + // TODO + Ok(Automata::default()) } } diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 850200263c..cde82e748d 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -355,55 +355,3 @@ pub fn lower_rule( (lhs_root_term, pattern_seq, rhs_root_term, expr_seq) } - -impl peepmatic_automata::Output for ExprSequence { - fn empty() -> ExprSequence { - Default::default() - } - - fn prefix(a: &Self, b: &Self) -> Self { - let mut prefix = vec![]; - for (a_inst, b_inst) in a.insts.iter().zip(b.insts.iter()) { - if *a_inst != *b_inst { - break; - } - prefix.push(a_inst.clone()); - } - ExprSequence { insts: prefix } - } - - fn difference(a: &Self, b: &Self) -> Self { - debug_assert!( - a.insts - .iter() - .zip(b.insts.iter()) - .all(|(a_inst, b_inst)| *a_inst == *b_inst), - "b ({:?}) is not a prefix of a ({:?})", - b, - a - ); - ExprSequence { - insts: a - .insts - .iter() - .cloned() - .skip(b.insts.len()) - .collect::>(), - } - } - - fn concat(a: &Self, b: &Self) -> Self { - ExprSequence { - insts: a - .insts - .iter() - .cloned() - .chain(b.insts.iter().cloned()) - .collect::>(), - } - } - - fn is_empty(&self) -> bool { - self.insts.is_empty() - } -} diff --git a/cranelift/isle/wasmtime b/cranelift/isle/wasmtime deleted file mode 160000 index e4d4b09243..0000000000 --- a/cranelift/isle/wasmtime +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e4d4b092431d12d0f0c6dff1cff923572a6fbb77 From 02ec77a45b4e3c8883d8ec9c820630d4544bb47b Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 12:36:04 -0700 Subject: [PATCH 06/95] trie insertion --- cranelift/isle/src/codegen.rs | 249 +++++++++++++++++++++++++++++----- cranelift/isle/src/compile.rs | 2 +- 2 files changed, 216 insertions(+), 35 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index b9c0c55e58..4ffceb38c5 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -1,9 +1,9 @@ //! Generate Rust code from a series of Sequences. use crate::error::Error; -use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence, Value}; +use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence}; use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; /// One "input symbol" for the decision tree that handles matching on /// a term. Each symbol represents one step: we either run a match op, @@ -157,10 +157,13 @@ enum TrieSymbol { EndOfMatch, } -#[derive(Clone, Debug)] -struct TrieEdge { - key: (PrioRange, TrieSymbol), - node: Box, +impl TrieSymbol { + fn is_eom(&self) -> bool { + match self { + TrieSymbol::EndOfMatch => true, + _ => false, + } + } } type Prio = i64; @@ -168,49 +171,220 @@ type Prio = i64; #[derive(Clone, Copy, Debug)] struct PrioRange(Prio, Prio); -impl std::cmp::PartialOrd for PrioRange { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl PrioRange { + fn contains(&self, prio: Prio) -> bool { + prio >= self.0 && prio <= self.1 } -} -impl std::cmp::Ord for PrioRange { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - if self.1 < other.0 { - std::cmp::Ordering::Less - } else if self.0 > other.1 { - std::cmp::Ordering::Greater + + fn is_unit(&self) -> bool { + self.0 == self.1 + } + + fn overlaps(&self, other: PrioRange) -> bool { + // This can be derived via DeMorgan: !(self.begin > other.end + // OR other.begin > self.end). + self.0 <= other.1 && other.0 <= self.1 + } + + fn intersect(&self, other: PrioRange) -> PrioRange { + PrioRange( + std::cmp::max(self.0, other.0), + std::cmp::min(self.1, other.1), + ) + } + + fn union(&self, other: PrioRange) -> PrioRange { + PrioRange( + std::cmp::min(self.0, other.0), + std::cmp::max(self.1, other.1), + ) + } + + fn split_at(&self, prio: Prio) -> (PrioRange, PrioRange) { + assert!(self.contains(prio)); + assert!(!self.is_unit()); + if prio == self.0 { + (PrioRange(self.0, self.0), PrioRange(self.0 + 1, self.1)) } else { - std::cmp::Ordering::Equal + (PrioRange(self.0, prio - 1), PrioRange(prio, self.1)) } } } -impl std::cmp::PartialEq for PrioRange { - fn eq(&self, other: &Self) -> bool { - self.cmp(other) == std::cmp::Ordering::Equal - } + +#[derive(Clone, Debug)] +struct TrieEdge { + range: PrioRange, + symbol: TrieSymbol, + node: TrieNode, } -impl std::cmp::Eq for PrioRange {} #[derive(Clone, Debug)] enum TrieNode { - Decision { - edges: BTreeMap<(PrioRange, TrieSymbol), TrieNode>, - }, - Leaf { - prio: Prio, - output: Vec, - }, + Decision { edges: Vec }, + Leaf { prio: Prio, output: ExprSequence }, Empty, } impl TrieNode { + fn is_empty(&self) -> bool { + match self { + &TrieNode::Empty => true, + _ => false, + } + } + fn insert( &mut self, prio: Prio, - input: impl Iterator, + mut input: impl Iterator, output: ExprSequence, - ) { - unimplemented!() + ) -> bool { + // Take one input symbol. There must be *at least* one, EOM if + // nothing else. + let op = input + .next() + .expect("Cannot insert into trie with empty input sequence"); + let is_last = op.is_eom(); + + // If we are empty, turn into a decision node. + if self.is_empty() { + *self = TrieNode::Decision { edges: vec![] }; + } + + // We must be a decision node. + let edges = match self { + &mut TrieNode::Decision { ref mut edges } => edges, + _ => panic!("insert on leaf node!"), + }; + + // Do we need to split? + let needs_split = edges + .iter() + .any(|edge| edge.range.contains(prio) && !edge.range.is_unit()); + + // If so, pass over all edges/subnodes and split each. + if needs_split { + let mut new_edges = vec![]; + for edge in std::mem::take(edges) { + if !edge.range.contains(prio) || edge.range.is_unit() { + new_edges.push(edge); + continue; + } + + let (lo_range, hi_range) = edge.range.split_at(prio); + let lo = edge.node.trim(lo_range); + let hi = edge.node.trim(hi_range); + if let Some((node, range)) = lo { + new_edges.push(TrieEdge { + range, + symbol: edge.symbol.clone(), + node, + }); + } + if let Some((node, range)) = hi { + new_edges.push(TrieEdge { + range, + symbol: edge.symbol, + node, + }); + } + } + *edges = new_edges; + } + + // Now find or insert the appropriate edge. + let mut edge: Option = None; + for i in 0..edges.len() { + if edges[i].range.contains(prio) && edges[i].symbol == op { + edge = Some(i); + break; + } + if prio > edges[i].range.1 { + edges.insert( + i, + TrieEdge { + range: PrioRange(prio, prio), + symbol: op.clone(), + node: TrieNode::Empty, + }, + ); + edge = Some(i); + break; + } + } + let edge = edge.unwrap_or_else(|| { + edges.push(TrieEdge { + range: PrioRange(prio, prio), + symbol: op.clone(), + node: TrieNode::Empty, + }); + edges.len() - 1 + }); + let edge = &mut edges[edge]; + + if is_last { + if !edge.node.is_empty() { + // If a leaf node already exists at an overlapping + // prio for this op, there are two competing rules, so + // we can't insert this one. + return false; + } + edge.node = TrieNode::Leaf { prio, output }; + true + } else { + edge.node.insert(prio, input, output) + } + } + + fn trim(&self, range: PrioRange) -> Option<(TrieNode, PrioRange)> { + match self { + &TrieNode::Empty => None, + &TrieNode::Leaf { prio, ref output } => { + if range.contains(prio) { + Some(( + TrieNode::Leaf { + prio, + output: output.clone(), + }, + PrioRange(prio, prio), + )) + } else { + None + } + } + &TrieNode::Decision { ref edges } => { + let edges = edges + .iter() + .filter_map(|edge| { + if !edge.range.overlaps(range) { + None + } else { + let range = range.intersect(edge.range); + if let Some((node, range)) = edge.node.trim(range) { + Some(TrieEdge { + range, + symbol: edge.symbol.clone(), + node, + }) + } else { + None + } + } + }) + .collect::>(); + + if edges.is_empty() { + None + } else { + let range = edges + .iter() + .map(|edge| edge.range) + .reduce(|a, b| a.union(b)) + .expect("reduce on non-empty vec must not return None"); + Some((TrieNode::Decision { edges }, range)) + } + } + } } } @@ -224,6 +398,7 @@ impl TrieNode { /// trying to match another rule's left-hand side against an input to /// produce the term in question (when the term is used in the LHS of /// the calling term). +#[derive(Debug)] struct TermFunctionBuilder { root_term: TermId, trie: TrieNode, @@ -238,11 +413,16 @@ impl TermFunctionBuilder { } fn add_rule(&mut self, prio: Prio, pattern_seq: PatternSequence, expr_seq: ExprSequence) { - self.trie - .insert(prio, pattern_seq.insts.into_iter(), expr_seq); + let symbols = pattern_seq + .insts + .into_iter() + .map(|op| TrieSymbol::Match { op }) + .chain(std::iter::once(TrieSymbol::EndOfMatch)); + self.trie.insert(prio, symbols, expr_seq); } } +#[derive(Debug)] struct TermFunctionsBuilder<'a> { typeenv: &'a TypeEnv, termenv: &'a TermEnv, @@ -302,6 +482,7 @@ impl Automata { pub fn compile(typeenv: &TypeEnv, termenv: &TermEnv) -> Result { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); + log::trace!("builder: {:?}", builder); // TODO Ok(Automata::default()) } diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/src/compile.rs index 52e9c29483..3aa01e599c 100644 --- a/cranelift/isle/src/compile.rs +++ b/cranelift/isle/src/compile.rs @@ -1,7 +1,7 @@ //! Compilation process, from AST to Sema to Sequences of Insts. -use crate::{ast, sema, ir, codegen}; use crate::error::Error; +use crate::{ast, codegen, sema}; pub fn compile(defs: &ast::Defs) -> Result { let mut typeenv = sema::TypeEnv::from_ast(defs)?; From 5aa72bc060bff471a826c8ddf173b00d4fb5cfec Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 12:45:42 -0700 Subject: [PATCH 07/95] skeleton for codegen --- cranelift/isle/src/codegen.rs | 44 ++++++++++++++++++++++++++++------- cranelift/isle/src/compile.rs | 6 ++--- cranelift/isle/src/error.rs | 2 ++ cranelift/isle/src/main.rs | 4 ++-- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 4ffceb38c5..e080f30a25 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -470,20 +470,48 @@ impl<'a> TermFunctionsBuilder<'a> { } } } + + fn finalize(self) -> (HashMap, HashMap) { + let functions_by_input = self + .builders_by_input + .into_iter() + .map(|(term, builder)| (term, builder.trie)) + .collect::>(); + let functions_by_output = self + .builders_by_output + .into_iter() + .map(|(term, builder)| (term, builder.trie)) + .collect::>(); + (functions_by_input, functions_by_output) + } } -#[derive(Clone, Debug, Default)] -pub struct Automata { - pub automata_by_input: HashMap, - pub automata_by_output: HashMap, +#[derive(Clone, Debug)] +pub struct Codegen<'a> { + typeenv: &'a TypeEnv, + termenv: &'a TermEnv, + functions_by_input: HashMap, + functions_by_output: HashMap, } -impl Automata { - pub fn compile(typeenv: &TypeEnv, termenv: &TermEnv) -> Result { +impl<'a> Codegen<'a> { + pub fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Result, Error> { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); log::trace!("builder: {:?}", builder); - // TODO - Ok(Automata::default()) + let (functions_by_input, functions_by_output) = builder.finalize(); + Ok(Codegen { + typeenv, + termenv, + functions_by_input, + functions_by_output, + }) + } + + pub fn generate_rust(&self) -> Result { + use std::fmt::Write; + let mut code = String::new(); + writeln!(&mut code, "// GENERATED BY ISLE. DO NOT EDIT!")?; + Ok(code) } } diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/src/compile.rs index 3aa01e599c..618998980c 100644 --- a/cranelift/isle/src/compile.rs +++ b/cranelift/isle/src/compile.rs @@ -3,9 +3,9 @@ use crate::error::Error; use crate::{ast, codegen, sema}; -pub fn compile(defs: &ast::Defs) -> Result { +pub fn compile(defs: &ast::Defs) -> Result { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; - let automata = codegen::Automata::compile(&typeenv, &termenv)?; - Ok(automata) + let codegen = codegen::Codegen::compile(&typeenv, &termenv)?; + codegen.generate_rust() } diff --git a/cranelift/isle/src/error.rs b/cranelift/isle/src/error.rs index 2399fb1231..7fce2df669 100644 --- a/cranelift/isle/src/error.rs +++ b/cranelift/isle/src/error.rs @@ -11,6 +11,8 @@ pub enum Error { SemaError(#[from] SemaError), #[error("IO error")] IoError(#[from] std::io::Error), + #[error("Formatting error")] + FmtError(#[from] std::fmt::Error), } #[derive(Clone, Debug, Error)] diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs index 922afbb57e..038a8a7a85 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/main.rs @@ -18,7 +18,7 @@ fn main() -> Result<(), error::Error> { stdin().read_to_string(&mut input)?; let mut parser = parser::Parser::new("", &input[..]); let defs = parser.parse_defs()?; - let automata = compile::compile(&defs)?; - println!("automata: {:?}", automata); + let code = compile::compile(&defs)?; + println!("{}", code); Ok(()) } From e9a57d854dfcbef306c0fbe586f52dcc66306132 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 12:59:19 -0700 Subject: [PATCH 08/95] Generate internal enum types. --- cranelift/isle/src/codegen.rs | 45 +++++++++++++++++++++++++++++++++-- cranelift/isle/src/lexer.rs | 9 +++++++ cranelift/isle/src/sema.rs | 19 ++++++++------- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index e080f30a25..4a12c5d7c9 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -2,8 +2,9 @@ use crate::error::Error; use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence}; -use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; +use crate::sema::{RuleId, TermEnv, TermId, Type, TypeEnv}; use std::collections::HashMap; +use std::fmt::Write; /// One "input symbol" for the decision tree that handles matching on /// a term. Each symbol represents one step: we either run a match op, @@ -509,9 +510,49 @@ impl<'a> Codegen<'a> { } pub fn generate_rust(&self) -> Result { - use std::fmt::Write; let mut code = String::new(); writeln!(&mut code, "// GENERATED BY ISLE. DO NOT EDIT!")?; + writeln!( + &mut code, + "use super::*; // Pulls in all external types and ctors/etors" + )?; + self.generate_internal_types(&mut code)?; Ok(code) } + + fn generate_internal_types(&self, code: &mut dyn Write) -> Result<(), Error> { + for ty in &self.typeenv.types { + match ty { + &Type::Enum { + name, + is_extern, + ref variants, + pos, + .. + } if !is_extern => { + let name = &self.typeenv.syms[name.index()]; + writeln!( + code, + "\n// Internal type {}: defined at {}.", + name, + pos.pretty_print_line(&self.typeenv.filename) + )?; + writeln!(code, "enum {} {{", name)?; + for variant in variants { + let name = &self.typeenv.syms[variant.name.index()]; + writeln!(code, " {} {{", name)?; + for field in &variant.fields { + let name = &self.typeenv.syms[field.name.index()]; + let ty_name = self.typeenv.types[field.ty.index()].name(&self.typeenv); + writeln!(code, " {}: {},", name, ty_name)?; + } + writeln!(code, " }}")?; + } + writeln!(code, "}}")?; + } + _ => {} + } + } + Ok(()) + } } diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/src/lexer.rs index eafb72a462..f9672db298 100644 --- a/cranelift/isle/src/lexer.rs +++ b/cranelift/isle/src/lexer.rs @@ -14,6 +14,15 @@ pub struct Pos { pub col: usize, } +impl Pos { + pub fn pretty_print(&self, filename: &str) -> String { + format!("{}:{}:{}", filename, self.line, self.col) + } + pub fn pretty_print_line(&self, filename: &str) -> String { + format!("{} line {}", filename, self.line) + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Token<'a> { LParen, diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index 9e2bab9fe0..ff6a2d219e 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -50,7 +50,7 @@ pub enum Type { } impl Type { - fn name<'a>(&self, tyenv: &'a TypeEnv) -> &'a str { + pub fn name<'a>(&self, tyenv: &'a TypeEnv) -> &'a str { match self { Self::Primitive(_, name) | Self::Enum { name, .. } => &tyenv.syms[name.index()], } @@ -60,6 +60,7 @@ impl Type { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Variant { pub name: Sym, + pub fullname: Sym, pub id: VariantId, pub fields: Vec, } @@ -209,9 +210,10 @@ impl TypeEnv { let mut variants = vec![]; for variant in ty_variants { let combined_ident = ast::Ident(format!("{}.{}", ty.name.0, variant.name.0)); - let var_name = self.intern_mut(&combined_ident); + let fullname = self.intern_mut(&combined_ident); + let name = self.intern_mut(&variant.name); let id = VariantId(variants.len()); - if variants.iter().any(|v: &Variant| v.name == var_name) { + if variants.iter().any(|v: &Variant| v.name == name) { return Err(self.error( ty.pos, format!("Duplicate variant name in type: '{}'", variant.name.0), @@ -249,7 +251,8 @@ impl TypeEnv { }); } variants.push(Variant { - name: var_name, + name, + fullname, id, fields, }); @@ -376,12 +379,12 @@ impl TermEnv { .. } => { for variant in variants { - if self.term_map.contains_key(&variant.name) { + if self.term_map.contains_key(&variant.fullname) { return Err(tyenv.error( pos, format!( "Duplicate enum variant constructor: '{}'", - tyenv.syms[variant.name.index()] + tyenv.syms[variant.fullname.index()] ), )); } @@ -390,14 +393,14 @@ impl TermEnv { let ret_ty = id; self.terms.push(Term { id: tid, - name: variant.name, + name: variant.fullname, arg_tys, ret_ty, kind: TermKind::EnumVariant { variant: variant.id, }, }); - self.term_map.insert(variant.name, tid); + self.term_map.insert(variant.fullname, tid); } } _ => {} From 638c9edd01c8c437509c7c56cd989c07e9a52718 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 14:04:19 -0700 Subject: [PATCH 09/95] Support for file input and output, including multiple input files with proper position tracking. --- cranelift/isle/Cargo.lock | 58 ++++++++++++ cranelift/isle/Cargo.toml | 1 + cranelift/isle/src/ast.rs | 2 +- cranelift/isle/src/codegen.rs | 17 +++- cranelift/isle/src/lexer.rs | 168 +++++++++++++++++++++++----------- cranelift/isle/src/main.rs | 47 ++++++++-- cranelift/isle/src/parser.rs | 46 +++++----- cranelift/isle/src/sema.rs | 6 +- 8 files changed, 255 insertions(+), 90 deletions(-) diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index b890d9e546..f37ad00454 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "atty" version = "0.2.14" @@ -22,12 +31,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -60,6 +90,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" name = "isle" version = "0.1.0" dependencies = [ + "clap", "env_logger", "log", "thiserror", @@ -121,6 +152,12 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" version = "1.0.75" @@ -141,6 +178,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.26" @@ -161,12 +207,24 @@ dependencies = [ "syn", ] +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "winapi" version = "0.3.9" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml index 8774800064..d3f43cb1c5 100644 --- a/cranelift/isle/Cargo.toml +++ b/cranelift/isle/Cargo.toml @@ -9,3 +9,4 @@ license = "Apache-2.0 WITH LLVM-exception" log = "0.4" env_logger = "0.8" thiserror = "1.0" +clap = "2.33" diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index 53a0698d95..b0c06b68ae 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -4,7 +4,7 @@ use crate::lexer::Pos; #[derive(Clone, PartialEq, Eq, Debug)] pub struct Defs { pub defs: Vec, - pub filename: String, + pub filenames: Vec, } /// One toplevel form in an ISLE file. diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 4a12c5d7c9..d503c81515 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -512,9 +512,17 @@ impl<'a> Codegen<'a> { pub fn generate_rust(&self) -> Result { let mut code = String::new(); writeln!(&mut code, "// GENERATED BY ISLE. DO NOT EDIT!")?; + writeln!(&mut code, "//")?; writeln!( &mut code, - "use super::*; // Pulls in all external types and ctors/etors" + "// Generated automatically from the instruction-selection DSL code in:", + )?; + for file in &self.typeenv.filenames { + writeln!(&mut code, "// - {}", file)?; + } + writeln!( + &mut code, + "\nuse super::*; // Pulls in all external types and ctors/etors" )?; self.generate_internal_types(&mut code)?; Ok(code) @@ -533,10 +541,11 @@ impl<'a> Codegen<'a> { let name = &self.typeenv.syms[name.index()]; writeln!( code, - "\n// Internal type {}: defined at {}.", + "\n/// Internal type {}: defined at {}.", name, - pos.pretty_print_line(&self.typeenv.filename) + pos.pretty_print_line(&self.typeenv.filenames[..]) )?; + writeln!(code, "#[derive(Clone, Debug)]")?; writeln!(code, "enum {} {{", name)?; for variant in variants { let name = &self.typeenv.syms[variant.name.index()]; @@ -546,7 +555,7 @@ impl<'a> Codegen<'a> { let ty_name = self.typeenv.types[field.ty.index()].name(&self.typeenv); writeln!(code, " {}: {},", name, ty_name)?; } - writeln!(code, " }}")?; + writeln!(code, " }},")?; } writeln!(code, "}}")?; } diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/src/lexer.rs index f9672db298..af53d7313e 100644 --- a/cranelift/isle/src/lexer.rs +++ b/cranelift/isle/src/lexer.rs @@ -1,41 +1,56 @@ //! Lexer for the ISLE language. +use crate::error::Error; +use std::borrow::Cow; + #[derive(Clone, Debug)] pub struct Lexer<'a> { - buf: &'a [u8], + pub filenames: Vec, + file_starts: Vec, + buf: Cow<'a, [u8]>, pos: Pos, - lookahead: Option<(Pos, Token<'a>)>, + lookahead: Option<(Pos, Token)>, +} + +#[derive(Clone, Debug)] +enum LexerInput<'a> { + String { s: &'a str, filename: &'a str }, + File { content: String, filename: String }, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Pos { + pub file: usize, pub offset: usize, pub line: usize, pub col: usize, } impl Pos { - pub fn pretty_print(&self, filename: &str) -> String { - format!("{}:{}:{}", filename, self.line, self.col) + pub fn pretty_print(&self, filenames: &[String]) -> String { + format!("{}:{}:{}", filenames[self.file], self.line, self.col) } - pub fn pretty_print_line(&self, filename: &str) -> String { - format!("{} line {}", filename, self.line) + pub fn pretty_print_line(&self, filenames: &[String]) -> String { + format!("{} line {}", filenames[self.file], self.line) } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Token<'a> { +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Token { LParen, RParen, - Symbol(&'a str), + Symbol(String), Int(i64), } impl<'a> Lexer<'a> { - pub fn new(s: &'a str) -> Lexer<'a> { + pub fn from_str(s: &'a str, filename: &'a str) -> Lexer<'a> { let mut l = Lexer { - buf: s.as_bytes(), + filenames: vec![filename.to_string()], + file_starts: vec![0], + buf: Cow::Borrowed(s.as_bytes()), pos: Pos { + file: 0, offset: 0, line: 1, col: 0, @@ -46,6 +61,43 @@ impl<'a> Lexer<'a> { l } + pub fn from_files(filenames: Vec) -> Result, Error> { + assert!(!filenames.is_empty()); + let file_contents: Vec = filenames + .iter() + .map(|f| { + use std::io::Read; + let mut f = std::fs::File::open(f)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(s) + }) + .collect::, Error>>()?; + + let mut file_starts = vec![]; + let mut buf = String::new(); + for file in file_contents { + file_starts.push(buf.len()); + buf += &file; + buf += "\n"; + } + + let mut l = Lexer { + filenames, + buf: Cow::Owned(buf.into_bytes()), + file_starts, + pos: Pos { + file: 0, + offset: 0, + line: 1, + col: 0, + }, + lookahead: None, + }; + l.reload(); + Ok(l) + } + pub fn offset(&self) -> usize { self.pos.offset } @@ -54,7 +106,24 @@ impl<'a> Lexer<'a> { self.pos } - fn next_token(&mut self) -> Option<(Pos, Token<'a>)> { + fn advance_pos(&mut self) { + self.pos.col += 1; + if self.buf[self.pos.offset] == b'\n' { + self.pos.line += 1; + self.pos.col = 0; + } + self.pos.offset += 1; + if self.pos.file + 1 < self.file_starts.len() { + let next_start = self.file_starts[self.pos.file + 1]; + if self.pos.offset >= next_start { + assert!(self.pos.offset == next_start); + self.pos.file += 1; + self.pos.line = 1; + } + } + } + + fn next_token(&mut self) -> Option<(Pos, Token)> { fn is_sym_first_char(c: u8) -> bool { match c { b'-' | b'0'..=b'9' | b'(' | b')' | b';' => false, @@ -73,20 +142,13 @@ impl<'a> Lexer<'a> { // Skip any whitespace and any comments. while self.pos.offset < self.buf.len() { if self.buf[self.pos.offset].is_ascii_whitespace() { - self.pos.col += 1; - if self.buf[self.pos.offset] == b'\n' { - self.pos.line += 1; - self.pos.col = 0; - } - self.pos.offset += 1; + self.advance_pos(); continue; } if self.buf[self.pos.offset] == b';' { while self.pos.offset < self.buf.len() && self.buf[self.pos.offset] != b'\n' { - self.pos.offset += 1; + self.advance_pos(); } - self.pos.line += 1; - self.pos.col = 0; continue; } break; @@ -99,13 +161,11 @@ impl<'a> Lexer<'a> { let char_pos = self.pos; match self.buf[self.pos.offset] { b'(' => { - self.pos.offset += 1; - self.pos.col += 1; + self.advance_pos(); Some((char_pos, Token::LParen)) } b')' => { - self.pos.offset += 1; - self.pos.col += 1; + self.advance_pos(); Some((char_pos, Token::RParen)) } c if is_sym_first_char(c) => { @@ -114,19 +174,17 @@ impl<'a> Lexer<'a> { while self.pos.offset < self.buf.len() && is_sym_other_char(self.buf[self.pos.offset]) { - self.pos.col += 1; - self.pos.offset += 1; + self.advance_pos(); } let end = self.pos.offset; let s = std::str::from_utf8(&self.buf[start..end]) .expect("Only ASCII characters, should be UTF-8"); - Some((start_pos, Token::Symbol(s))) + Some((start_pos, Token::Symbol(s.to_string()))) } c if (c >= b'0' && c <= b'9') || c == b'-' => { let start_pos = self.pos; let neg = if c == b'-' { - self.pos.offset += 1; - self.pos.col += 1; + self.advance_pos(); true } else { false @@ -136,8 +194,7 @@ impl<'a> Lexer<'a> { && (self.buf[self.pos.offset] >= b'0' && self.buf[self.pos.offset] <= b'9') { num = (num * 10) + (self.buf[self.pos.offset] - b'0') as i64; - self.pos.offset += 1; - self.pos.col += 1; + self.advance_pos(); } let tok = if neg { @@ -157,8 +214,8 @@ impl<'a> Lexer<'a> { } } - pub fn peek(&self) -> Option<(Pos, Token<'a>)> { - self.lookahead + pub fn peek(&self) -> Option<&(Pos, Token)> { + self.lookahead.as_ref() } pub fn eof(&self) -> bool { @@ -167,16 +224,16 @@ impl<'a> Lexer<'a> { } impl<'a> std::iter::Iterator for Lexer<'a> { - type Item = (Pos, Token<'a>); + type Item = (Pos, Token); - fn next(&mut self) -> Option<(Pos, Token<'a>)> { + fn next(&mut self) -> Option<(Pos, Token)> { let tok = self.lookahead.take(); self.reload(); tok } } -impl<'a> Token<'a> { +impl Token { pub fn is_int(&self) -> bool { match self { Token::Int(_) => true, @@ -199,14 +256,17 @@ mod test { #[test] fn lexer_basic() { assert_eq!( - Lexer::new(";; comment\n; another\r\n \t(one two three 23 -568 )\n") - .map(|(_, tok)| tok) - .collect::>(), + Lexer::from_str( + ";; comment\n; another\r\n \t(one two three 23 -568 )\n", + "test" + ) + .map(|(_, tok)| tok) + .collect::>(), vec![ Token::LParen, - Token::Symbol("one"), - Token::Symbol("two"), - Token::Symbol("three"), + Token::Symbol("one".to_string()), + Token::Symbol("two".to_string()), + Token::Symbol("three".to_string()), Token::Int(23), Token::Int(-568), Token::RParen @@ -217,15 +277,19 @@ mod test { #[test] fn ends_with_sym() { assert_eq!( - Lexer::new("asdf").map(|(_, tok)| tok).collect::>(), - vec![Token::Symbol("asdf"),] + Lexer::from_str("asdf", "test") + .map(|(_, tok)| tok) + .collect::>(), + vec![Token::Symbol("asdf".to_string()),] ); } #[test] fn ends_with_num() { assert_eq!( - Lexer::new("23").map(|(_, tok)| tok).collect::>(), + Lexer::from_str("23", "test") + .map(|(_, tok)| tok) + .collect::>(), vec![Token::Int(23)], ); } @@ -233,16 +297,16 @@ mod test { #[test] fn weird_syms() { assert_eq!( - Lexer::new("(+ [] => !! _test!;comment\n)") + Lexer::from_str("(+ [] => !! _test!;comment\n)", "test") .map(|(_, tok)| tok) .collect::>(), vec![ Token::LParen, - Token::Symbol("+"), - Token::Symbol("[]"), - Token::Symbol("=>"), - Token::Symbol("!!"), - Token::Symbol("_test!"), + Token::Symbol("+".to_string()), + Token::Symbol("[]".to_string()), + Token::Symbol("=>".to_string()), + Token::Symbol("!!".to_string()), + Token::Symbol("_test!".to_string()), Token::RParen, ] ); diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs index 038a8a7a85..a09e21096a 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/main.rs @@ -1,7 +1,6 @@ #![allow(dead_code)] -use std::io::stdin; -use std::io::Read; +use clap::{App, Arg}; mod ast; mod codegen; @@ -14,11 +13,47 @@ mod sema; fn main() -> Result<(), error::Error> { let _ = env_logger::try_init(); - let mut input = String::new(); - stdin().read_to_string(&mut input)?; - let mut parser = parser::Parser::new("", &input[..]); + + let matches = App::new("isle") + .version(env!("CARGO_PKG_VERSION")) + .author("Chris Fallin ") + .about("Instruction selection logic engine (ISLE) code generator") + .arg( + Arg::with_name("input") + .short("i") + .long("input") + .value_name("FILE.isle") + .takes_value(true) + .multiple(true) + .required(true), + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .value_name("FILE.rs") + .takes_value(true) + .required(true), + ) + .get_matches(); + + let input_files = matches + .values_of("input") + .unwrap() + .map(|s| s.to_string()) + .collect::>(); + let output_file = matches.value_of("output").unwrap(); + + let lexer = lexer::Lexer::from_files(input_files)?; + let mut parser = parser::Parser::new(lexer); let defs = parser.parse_defs()?; let code = compile::compile(&defs)?; - println!("{}", code); + + { + use std::io::Write; + let mut f = std::fs::File::create(output_file)?; + writeln!(&mut f, "{}", code)?; + } + Ok(()) } diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index 7bd7c8d058..8d9591b175 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -6,42 +6,37 @@ use crate::lexer::{Lexer, Pos, Token}; #[derive(Clone, Debug)] pub struct Parser<'a> { - filename: &'a str, lexer: Lexer<'a>, } pub type ParseResult = std::result::Result; impl<'a> Parser<'a> { - pub fn new(filename: &'a str, s: &'a str) -> Parser<'a> { - Parser { - filename, - lexer: Lexer::new(s), - } + pub fn new(lexer: Lexer<'a>) -> Parser<'a> { + Parser { lexer } } pub fn error(&self, pos: Pos, msg: String) -> ParseError { ParseError { - filename: self.filename.to_string(), + filename: self.lexer.filenames[pos.file].clone(), pos, msg, } } - fn take bool>(&mut self, f: F) -> ParseResult> { - if let Some((pos, peek)) = self.lexer.peek() { + fn take bool>(&mut self, f: F) -> ParseResult { + if let Some(&(pos, ref peek)) = self.lexer.peek() { if !f(peek) { return Err(self.error(pos, format!("Unexpected token {:?}", peek))); } - self.lexer.next(); - Ok(peek) + Ok(self.lexer.next().unwrap().1) } else { Err(self.error(self.lexer.pos(), "Unexpected EOF".to_string())) } } - fn is bool>(&self, f: F) -> bool { - if let Some((_, peek)) = self.lexer.peek() { + fn is bool>(&self, f: F) -> bool { + if let Some(&(_, ref peek)) = self.lexer.peek() { f(peek) } else { false @@ -49,14 +44,14 @@ impl<'a> Parser<'a> { } fn pos(&self) -> Option { - self.lexer.peek().map(|(pos, _)| pos) + self.lexer.peek().map(|(pos, _)| *pos) } fn is_lparen(&self) -> bool { - self.is(|tok| tok == Token::LParen) + self.is(|tok| *tok == Token::LParen) } fn is_rparen(&self) -> bool { - self.is(|tok| tok == Token::RParen) + self.is(|tok| *tok == Token::RParen) } fn is_sym(&self) -> bool { self.is(|tok| tok.is_sym()) @@ -65,17 +60,20 @@ impl<'a> Parser<'a> { self.is(|tok| tok.is_int()) } fn is_sym_str(&self, s: &str) -> bool { - self.is(|tok| tok == Token::Symbol(s)) + self.is(|tok| match tok { + &Token::Symbol(ref tok_s) if tok_s == s => true, + _ => false, + }) } fn lparen(&mut self) -> ParseResult<()> { - self.take(|tok| tok == Token::LParen).map(|_| ()) + self.take(|tok| *tok == Token::LParen).map(|_| ()) } fn rparen(&mut self) -> ParseResult<()> { - self.take(|tok| tok == Token::RParen).map(|_| ()) + self.take(|tok| *tok == Token::RParen).map(|_| ()) } - fn symbol(&mut self) -> ParseResult<&'a str> { + fn symbol(&mut self) -> ParseResult { match self.take(|tok| tok.is_sym())? { Token::Symbol(s) => Ok(s), _ => unreachable!(), @@ -96,14 +94,14 @@ impl<'a> Parser<'a> { } Ok(Defs { defs, - filename: self.filename.to_string(), + filenames: self.lexer.filenames.clone(), }) } fn parse_def(&mut self) -> ParseResult { self.lparen()?; let pos = self.pos(); - let def = match self.symbol()? { + let def = match &self.symbol()?[..] { "type" => Def::Type(self.parse_type()?), "rule" => Def::Rule(self.parse_rule()?), "decl" => Def::Decl(self.parse_decl()?), @@ -143,7 +141,7 @@ impl<'a> Parser<'a> { fn parse_ident(&mut self) -> ParseResult { let pos = self.pos(); let s = self.symbol()?; - self.str_to_ident(pos.unwrap(), s) + self.str_to_ident(pos.unwrap(), &s) } fn parse_type(&mut self) -> ParseResult { @@ -285,7 +283,7 @@ impl<'a> Parser<'a> { let var = self.str_to_ident(pos.unwrap(), s)?; Ok(Pattern::Var { var }) } else { - let var = self.str_to_ident(pos.unwrap(), s)?; + let var = self.str_to_ident(pos.unwrap(), &s)?; if self.is_sym_str("@") { self.symbol()?; let subpat = Box::new(self.parse_pattern()?); diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index ff6a2d219e..2ff6f3cc1c 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -30,7 +30,7 @@ declare_id!(VarId); #[derive(Clone, Debug)] pub struct TypeEnv { - pub filename: String, + pub filenames: Vec, pub syms: Vec, pub sym_map: HashMap, pub types: Vec, @@ -158,7 +158,7 @@ impl Expr { impl TypeEnv { pub fn from_ast(defs: &ast::Defs) -> SemaResult { let mut tyenv = TypeEnv { - filename: defs.filename.clone(), + filenames: defs.filenames.clone(), syms: vec![], sym_map: HashMap::new(), types: vec![], @@ -270,7 +270,7 @@ impl TypeEnv { fn error(&self, pos: Pos, msg: String) -> SemaError { SemaError { - filename: self.filename.clone(), + filename: self.filenames[pos.file].clone(), pos, msg, } From 8c727b175adc279dfd2c419f45551edc2286f59c Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 14:23:47 -0700 Subject: [PATCH 10/95] more codegen WIP: start to generate functions --- cranelift/isle/src/codegen.rs | 106 +++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 8 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index d503c81515..e97ef8b6ce 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -2,7 +2,7 @@ use crate::error::Error; use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence}; -use crate::sema::{RuleId, TermEnv, TermId, Type, TypeEnv}; +use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; use std::collections::HashMap; use std::fmt::Write; @@ -511,21 +511,31 @@ impl<'a> Codegen<'a> { pub fn generate_rust(&self) -> Result { let mut code = String::new(); - writeln!(&mut code, "// GENERATED BY ISLE. DO NOT EDIT!")?; - writeln!(&mut code, "//")?; + + self.generate_header(&mut code)?; + self.generate_internal_types(&mut code)?; + self.generate_internal_term_constructors(&mut code)?; + self.generate_internal_term_extractors(&mut code)?; + + Ok(code) + } + + fn generate_header(&self, code: &mut dyn Write) -> Result<(), Error> { + writeln!(code, "// GENERATED BY ISLE. DO NOT EDIT!")?; + writeln!(code, "//")?; writeln!( - &mut code, + code, "// Generated automatically from the instruction-selection DSL code in:", )?; for file in &self.typeenv.filenames { - writeln!(&mut code, "// - {}", file)?; + writeln!(code, "// - {}", file)?; } writeln!( - &mut code, + code, "\nuse super::*; // Pulls in all external types and ctors/etors" )?; - self.generate_internal_types(&mut code)?; - Ok(code) + + Ok(()) } fn generate_internal_types(&self, code: &mut dyn Write) -> Result<(), Error> { @@ -564,4 +574,84 @@ impl<'a> Codegen<'a> { } Ok(()) } + + fn constructor_name(&self, term: TermId) -> String { + let termdata = &self.termenv.terms[term.index()]; + match &termdata.kind { + &TermKind::EnumVariant { .. } => panic!("using enum variant as constructor"), + &TermKind::Regular { + constructor: Some(sym), + .. + } => self.typeenv.syms[sym.index()].clone(), + &TermKind::Regular { + constructor: None, .. + } => { + format!("constructor_{}", self.typeenv.syms[termdata.name.index()]) + } + } + } + + fn type_name(&self, typeid: TypeId, by_ref: bool) -> String { + match &self.typeenv.types[typeid.index()] { + &Type::Primitive(_, sym) => self.typeenv.syms[sym.index()].clone(), + &Type::Enum { name, .. } => { + let r = if by_ref { "&" } else { "" }; + format!("{}{}", r, self.typeenv.syms[name.index()]) + } + } + } + + fn generate_internal_term_constructors(&self, code: &mut dyn Write) -> Result<(), Error> { + for (&termid, trie) in &self.functions_by_input { + let termdata = &self.termenv.terms[termid.index()]; + // Skip terms that are enum variants or that have external constructors. + match &termdata.kind { + &TermKind::EnumVariant { .. } => continue, + &TermKind::Regular { constructor, .. } if constructor.is_some() => continue, + _ => {} + } + + // Get the name of the term and build up the signature. + let func_name = self.constructor_name(termid); + let args = termdata + .arg_tys + .iter() + .enumerate() + .map(|(i, &arg_ty)| { + format!("arg{}: {}", i, self.type_name(arg_ty, /* by_ref = */ true)) + }) + .collect::>(); + writeln!( + code, + "\n// Generated as internal constructor for term {}.", + self.typeenv.syms[termdata.name.index()], + )?; + writeln!( + code, + "fn {}(ctx: &mut C, {}) -> Option<{}> {{", + func_name, + args.join(", "), + self.type_name(termdata.ret_ty, /* by_ref = */ false) + )?; + + self.generate_body(code, termid, trie)?; + + writeln!(code, "}}")?; + } + + Ok(()) + } + + fn generate_internal_term_extractors(&self, _code: &mut dyn Write) -> Result<(), Error> { + Ok(()) + } + + fn generate_body( + &self, + _code: &mut dyn Write, + _termid: TermId, + _trie: &TrieNode, + ) -> Result<(), Error> { + Ok(()) + } } From e5d76db97a11ba580df0b93984695a1368f1e10a Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 17:01:56 -0700 Subject: [PATCH 11/95] WIP. --- cranelift/isle/src/codegen.rs | 391 ++++++++++++++++++++++++++++++++-- cranelift/isle/src/ir.rs | 44 ++-- cranelift/isle/src/sema.rs | 14 +- 3 files changed, 417 insertions(+), 32 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index e97ef8b6ce..55f530f9c3 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -1,9 +1,9 @@ //! Generate Rust code from a series of Sequences. use crate::error::Error; -use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence}; -use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; -use std::collections::HashMap; +use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; +use crate::sema::{RuleId, Term, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; +use std::collections::{HashMap, HashSet}; use std::fmt::Write; /// One "input symbol" for the decision tree that handles matching on @@ -495,6 +495,11 @@ pub struct Codegen<'a> { functions_by_output: HashMap, } +#[derive(Clone, Debug, Default)] +struct BodyContext { + borrowed_values: HashSet, +} + impl<'a> Codegen<'a> { pub fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Result, Error> { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); @@ -591,19 +596,70 @@ impl<'a> Codegen<'a> { } } - fn type_name(&self, typeid: TypeId, by_ref: bool) -> String { + fn extractor_name_and_infallible(&self, term: TermId) -> (String, bool) { + let termdata = &self.termenv.terms[term.index()]; + match &termdata.kind { + &TermKind::EnumVariant { .. } => panic!("using enum variant as extractor"), + &TermKind::Regular { + extractor: Some((sym, infallible)), + .. + } => (self.typeenv.syms[sym.index()].clone(), infallible), + &TermKind::Regular { + extractor: None, .. + } => ( + format!("extractor_{}", self.typeenv.syms[termdata.name.index()]), + false, + ), + } + } + + fn type_name(&self, typeid: TypeId, by_ref: Option<&str>) -> String { match &self.typeenv.types[typeid.index()] { &Type::Primitive(_, sym) => self.typeenv.syms[sym.index()].clone(), &Type::Enum { name, .. } => { - let r = if by_ref { "&" } else { "" }; + let r = by_ref.unwrap_or(""); format!("{}{}", r, self.typeenv.syms[name.index()]) } } } + fn value_name(&self, value: &Value) -> String { + match value { + &Value::Pattern { inst, output } => format!("pattern{}_{}", inst.index(), output), + &Value::Expr { inst, output } => format!("expr{}_{}", inst.index(), output), + } + } + + fn value_by_ref(&self, value: &Value, ctx: &BodyContext) -> String { + let raw_name = self.value_name(value); + let name_is_ref = ctx.borrowed_values.contains(value); + if name_is_ref { + raw_name + } else { + format!("&{}", raw_name) + } + } + + fn value_by_val(&self, value: &Value, ctx: &BodyContext) -> String { + let raw_name = self.value_name(value); + let name_is_ref = ctx.borrowed_values.contains(value); + if name_is_ref { + format!("{}.clone()", raw_name) + } else { + raw_name + } + } + + fn define_val(&self, value: &Value, ctx: &mut BodyContext, is_ref: bool) { + if is_ref { + ctx.borrowed_values.insert(value.clone()); + } + } + fn generate_internal_term_constructors(&self, code: &mut dyn Write) -> Result<(), Error> { for (&termid, trie) in &self.functions_by_input { let termdata = &self.termenv.terms[termid.index()]; + // Skip terms that are enum variants or that have external constructors. match &termdata.kind { &TermKind::EnumVariant { .. } => continue, @@ -618,7 +674,11 @@ impl<'a> Codegen<'a> { .iter() .enumerate() .map(|(i, &arg_ty)| { - format!("arg{}: {}", i, self.type_name(arg_ty, /* by_ref = */ true)) + format!( + "arg{}: {}", + i, + self.type_name(arg_ty, /* by_ref = */ Some("&")) + ) }) .collect::>(); writeln!( @@ -631,10 +691,11 @@ impl<'a> Codegen<'a> { "fn {}(ctx: &mut C, {}) -> Option<{}> {{", func_name, args.join(", "), - self.type_name(termdata.ret_ty, /* by_ref = */ false) + self.type_name(termdata.ret_ty, /* by_ref = */ None) )?; - self.generate_body(code, termid, trie)?; + let mut body_ctx = Default::default(); + self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; writeln!(code, "}}")?; } @@ -642,16 +703,322 @@ impl<'a> Codegen<'a> { Ok(()) } - fn generate_internal_term_extractors(&self, _code: &mut dyn Write) -> Result<(), Error> { + fn generate_internal_term_extractors(&self, code: &mut dyn Write) -> Result<(), Error> { + for (&termid, trie) in &self.functions_by_output { + let termdata = &self.termenv.terms[termid.index()]; + + // Skip terms that are enum variants or that have external extractors. + match &termdata.kind { + &TermKind::EnumVariant { .. } => continue, + &TermKind::Regular { extractor, .. } if extractor.is_some() => continue, + _ => {} + } + + // Get the name of the term and build up the signature. + let (func_name, _) = self.extractor_name_and_infallible(termid); + let arg = format!( + "arg: {}", + self.type_name(termdata.ret_ty, /* by_ref = */ Some("&")) + ); + let ret_tuple_tys = termdata + .arg_tys + .iter() + .map(|ty| { + self.type_name(*ty, /* by_ref = */ None) + }) + .collect::>(); + + writeln!( + code, + "\n// Generated as internal extractor for term {}.", + self.typeenv.syms[termdata.name.index()], + )?; + writeln!( + code, + "fn {}<'a, C>(ctx: &mut C, {}) -> Option<({})> {{", + func_name, + arg, + ret_tuple_tys.join(", "), + )?; + + let mut body_ctx = Default::default(); + self.generate_extractor_header(code, termdata, &mut body_ctx)?; + self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; + writeln!(code, " }}")?; + writeln!(code, "}}")?; + } + + Ok(()) + } + + fn generate_extractor_header( + &self, + code: &mut dyn Write, + termdata: &Term, + ctx: &mut BodyContext, + ) -> Result<(), Error> { + writeln!(code, " {{")?; + todo!(); + Ok(()) + } + + fn generate_expr_inst( + &self, + code: &mut dyn Write, + id: InstId, + inst: &ExprInst, + indent: &str, + ctx: &mut BodyContext, + ) -> Result<(), Error> { + match inst { + &ExprInst::ConstInt { ty, val } => { + let value = Value::Expr { + inst: id, + output: 0, + }; + let name = self.value_name(&value); + let ty = self.type_name(ty, /* by_ref = */ None); + self.define_val(&value, ctx, /* is_ref = */ false); + writeln!(code, "{}let {}: {} = {};", indent, name, ty, val)?; + } + &ExprInst::CreateVariant { + ref inputs, + ty, + variant, + } => { + let variantinfo = match &self.typeenv.types[ty.index()] { + &Type::Primitive(..) => panic!("CreateVariant with primitive type"), + &Type::Enum { ref variants, .. } => &variants[variant.index()], + }; + let mut input_fields = vec![]; + for ((input_value, _), field) in inputs.iter().zip(variantinfo.fields.iter()) { + let field_name = &self.typeenv.syms[field.name.index()]; + let value_expr = self.value_by_val(input_value, ctx); + input_fields.push(format!("{}: {}", field_name, value_expr)); + } + + let output = Value::Expr { + inst: id, + output: 0, + }; + let outputname = self.value_name(&output); + let full_variant_name = format!( + "{}::{}", + self.type_name(ty, None), + self.typeenv.syms[variantinfo.name.index()] + ); + writeln!( + code, + "{}let {} = {} {{", + indent, outputname, full_variant_name + )?; + for input_field in input_fields { + writeln!(code, "{} {},", indent, input_field)?; + } + writeln!(code, "{}}};", indent)?; + self.define_val(&output, ctx, /* is_ref = */ false); + } + &ExprInst::Construct { + ref inputs, term, .. + } => { + let mut input_exprs = vec![]; + for (input_value, _) in inputs { + let value_expr = self.value_by_val(input_value, ctx); + input_exprs.push(value_expr); + } + + let output = Value::Expr { + inst: id, + output: 0, + }; + let outputname = self.value_name(&output); + let ctor_name = self.constructor_name(term); + writeln!( + code, + "{}let {} = {}(ctx, {});", + indent, + outputname, + ctor_name, + input_exprs.join(", "), + )?; + self.define_val(&output, ctx, /* is_ref = */ false); + } + &ExprInst::Return { ref value, .. } => { + let value_expr = self.value_by_val(value, ctx); + writeln!(code, "{}return Some({});", indent, value_expr)?; + } + } + + Ok(()) + } + + fn generate_pattern_inst( + &self, + code: &mut dyn Write, + id: InstId, + inst: &PatternInst, + indent: &str, + ctx: &mut BodyContext, + ) -> Result<(), Error> { + match inst { + &PatternInst::Arg { index, .. } => { + let output = Value::Expr { + inst: id, + output: 0, + }; + let outputname = self.value_name(&output); + writeln!(code, "{}let {} = arg{};", indent, outputname, index)?; + writeln!(code, "{}{{", indent)?; + } + &PatternInst::MatchEqual { ref a, ref b, .. } => { + let a = self.value_by_ref(a, ctx); + let b = self.value_by_ref(b, ctx); + writeln!(code, "{}if {} == {} {{", indent, a, b)?; + } + &PatternInst::MatchInt { + ref input, int_val, .. + } => { + let input = self.value_by_val(input, ctx); + writeln!(code, "{}if {} == {} {{", indent, input, int_val)?; + } + &PatternInst::MatchVariant { + ref input, + input_ty, + variant, + ref arg_tys, + } => { + let input = self.value_by_ref(input, ctx); + let variants = match &self.typeenv.types[input_ty.index()] { + &Type::Primitive(..) => panic!("primitive type input to MatchVariant"), + &Type::Enum { ref variants, .. } => variants, + }; + let ty_name = self.type_name(input_ty, /* is_ref = */ Some("&")); + let variant = &variants[variant.index()]; + let variantname = &self.typeenv.syms[variant.name.index()]; + let args = arg_tys + .iter() + .enumerate() + .map(|(i, ty)| { + let value = Value::Pattern { + inst: id, + output: i, + }; + let valuename = self.value_name(&value); + match &self.typeenv.types[ty.index()] { + &Type::Primitive(..) => { + self.define_val(&value, ctx, /* is_ref = */ false); + valuename + } + &Type::Enum { .. } => { + self.define_val(&value, ctx, /* is_ref = */ true); + format!("ref {}", valuename) + } + } + }) + .collect::>(); + writeln!( + code, + "{}if let {}::{} {{ {} }} = {} {{", + indent, + ty_name, + variantname, + args.join(", "), + input + )?; + } + &PatternInst::Extract { + ref input, + input_ty, + ref arg_tys, + term, + } => { + let input = self.value_by_ref(input, ctx); + let (etor_name, infallible) = self.extractor_name_and_infallible(term); + + let args = arg_tys + .iter() + .enumerate() + .map(|(i, ty)| { + let value = Value::Pattern { + inst: id, + output: i, + }; + self.define_val(&value, ctx, /* is_ref = */ false); + self.value_name(&value) + }) + .collect::>(); + + if infallible { + writeln!( + code, + "{}let Some(({})) = {}(ctx, {});", + indent, + args.join(", "), + etor_name, + input + )?; + writeln!(code, "{}{{", indent)?; + } else { + writeln!( + code, + "{}if let Some(({})) = {}(ctx, {}) {{", + indent, + args.join(", "), + etor_name, + input + )?; + } + } + } + Ok(()) } fn generate_body( &self, - _code: &mut dyn Write, - _termid: TermId, - _trie: &TrieNode, + code: &mut dyn Write, + depth: usize, + trie: &TrieNode, + indent: &str, + ctx: &mut BodyContext, ) -> Result<(), Error> { + match trie { + &TrieNode::Empty => {} + + &TrieNode::Leaf { ref output, .. } => { + // If this is a leaf node, generate the ExprSequence and return. + for (id, inst) in output.insts.iter().enumerate() { + let id = InstId(id); + self.generate_expr_inst(code, id, inst, indent, ctx)?; + } + } + + &TrieNode::Decision { ref edges } => { + let subindent = format!("{} ", indent); + // if this is a decision node, generate each match op + // in turn (in priority order). + for &TrieEdge { + ref symbol, + ref node, + .. + } in edges + { + match symbol { + &TrieSymbol::EndOfMatch => { + self.generate_body(code, depth + 1, node, &subindent, ctx)?; + } + &TrieSymbol::Match { ref op } => { + let id = InstId(depth); + self.generate_pattern_inst(code, id, op, &subindent, ctx)?; + self.generate_body(code, depth + 1, node, &subindent, ctx)?; + writeln!(code, "{}}}", subindent)?; + } + } + } + } + } + + writeln!(code, "{}return None;", indent)?; Ok(()) } } diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index cde82e748d..b1f3057619 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -17,8 +17,9 @@ pub enum Value { /// A single Pattern instruction. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PatternInst { - /// Get the input root-term value. - Arg { ty: TypeId }, + /// Get the Nth input argument, which corresponds to the Nth field + /// of the root term. + Arg { index: usize, ty: TypeId }, /// Match a value as equal to another value. Produces no values. MatchEqual { a: Value, b: Value, ty: TypeId }, @@ -118,9 +119,9 @@ impl PatternSequence { id } - fn add_arg(&mut self, ty: TypeId) -> Value { + fn add_arg(&mut self, index: usize, ty: TypeId) -> Value { let inst = InstId(self.insts.len()); - self.add_inst(PatternInst::Arg { ty }); + self.add_inst(PatternInst::Arg { index, ty }); Value::Pattern { inst, output: 0 } } @@ -183,7 +184,9 @@ impl PatternSequence { /// this pattern, if any. fn gen_pattern( &mut self, - input: Value, + // If `input` is `None`, then this is the root pattern, and is + // implicitly an extraction with the N args as results. + input: Option, typeenv: &TypeEnv, termenv: &TermEnv, pat: &Pattern, @@ -193,8 +196,9 @@ impl PatternSequence { &Pattern::BindPattern(_ty, var, ref subpat) => { // Bind the appropriate variable and recurse. assert!(!vars.contains_key(&var)); - vars.insert(var, (None, input)); // bind first, so subpat can use it - let root_term = self.gen_pattern(input, typeenv, termenv, &*subpat, vars); + vars.insert(var, (None, input.unwrap())); // bind first, so subpat can use it + let root_term = + self.gen_pattern(input, typeenv, termenv, &*subpat, vars); vars.get_mut(&var).unwrap().0 = root_term; root_term } @@ -204,30 +208,40 @@ impl PatternSequence { .get(&var) .cloned() .expect("Variable should already be bound"); - self.add_match_equal(input, var_val, ty); + self.add_match_equal(input.unwrap(), var_val, ty); var_val_term } &Pattern::ConstInt(ty, value) => { // Assert that the value matches the constant integer. - self.add_match_int(input, ty, value); + self.add_match_int(input.unwrap(), ty, value); None } + &Pattern::Term(_, term, ref args) if input.is_none() => { + let termdata = &termenv.terms[term.index()]; + let arg_tys = &termdata.arg_tys[..]; + for (i, subpat) in args.iter().enumerate() { + let value = self.add_arg(i, arg_tys[i]); + self.gen_pattern(Some(value), typeenv, termenv, subpat, vars); + } + Some(term) + } &Pattern::Term(ty, term, ref args) => { // Determine whether the term has an external extractor or not. let termdata = &termenv.terms[term.index()]; let arg_tys = &termdata.arg_tys[..]; match &termdata.kind { &TermKind::EnumVariant { variant } => { - let arg_values = self.add_match_variant(input, ty, arg_tys, variant); + let arg_values = + self.add_match_variant(input.unwrap(), ty, arg_tys, variant); for (subpat, value) in args.iter().zip(arg_values.into_iter()) { - self.gen_pattern(value, typeenv, termenv, subpat, vars); + self.gen_pattern(Some(value), typeenv, termenv, subpat, vars); } None } &TermKind::Regular { .. } => { - let arg_values = self.add_extract(input, ty, arg_tys, term); + let arg_values = self.add_extract(input.unwrap(), ty, arg_tys, term); for (subpat, value) in args.iter().zip(arg_values.into_iter()) { - self.gen_pattern(value, typeenv, termenv, subpat, vars); + self.gen_pattern(Some(value), typeenv, termenv, subpat, vars); } Some(term) } @@ -341,10 +355,8 @@ pub fn lower_rule( // Lower the pattern, starting from the root input value. let ruledata = &termenv.rules[rule.index()]; - let input_ty = ruledata.lhs.ty(); - let input = pattern_seq.add_arg(input_ty); let mut vars = HashMap::new(); - let lhs_root_term = pattern_seq.gen_pattern(input, tyenv, termenv, &ruledata.lhs, &mut vars); + let lhs_root_term = pattern_seq.gen_pattern(None, tyenv, termenv, &ruledata.lhs, &mut vars); // Lower the expression, making use of the bound variables // from the pattern. diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index 2ff6f3cc1c..b9c3d2eeac 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -772,6 +772,7 @@ impl TermEnv { mod test { use super::*; use crate::ast::Ident; + use crate::lexer::Lexer; use crate::parser::Parser; #[test] @@ -780,14 +781,16 @@ mod test { (type u32 (primitive u32)) (type A extern (enum (B (f1 u32) (f2 u32)) (C (f1 u32)))) "; - let ast = Parser::new("file.isle", text) + let ast = Parser::new(Lexer::from_str(text, "file.isle")) .parse_defs() .expect("should parse"); let tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); let sym_a = tyenv.intern(&Ident("A".to_string())).unwrap(); - let sym_b = tyenv.intern(&Ident("A.B".to_string())).unwrap(); - let sym_c = tyenv.intern(&Ident("A.C".to_string())).unwrap(); + let sym_b = tyenv.intern(&Ident("B".to_string())).unwrap(); + let sym_c = tyenv.intern(&Ident("C".to_string())).unwrap(); + let sym_a_b = tyenv.intern(&Ident("A.B".to_string())).unwrap(); + let sym_a_c = tyenv.intern(&Ident("A.C".to_string())).unwrap(); let sym_u32 = tyenv.intern(&Ident("u32".to_string())).unwrap(); let sym_f1 = tyenv.intern(&Ident("f1".to_string())).unwrap(); let sym_f2 = tyenv.intern(&Ident("f2".to_string())).unwrap(); @@ -806,6 +809,7 @@ mod test { variants: vec![ Variant { name: sym_b, + fullname: sym_a_b, id: VariantId(0), fields: vec![ Field { @@ -822,6 +826,7 @@ mod test { }, Variant { name: sym_c, + fullname: sym_a_c, id: VariantId(1), fields: vec![Field { name: sym_f1, @@ -831,6 +836,7 @@ mod test { }, ], pos: Pos { + file: 0, offset: 58, line: 3, col: 18, @@ -862,7 +868,7 @@ mod test { (rule -1 (T3 _) (A.C 3)) "; - let ast = Parser::new("file.isle", text) + let ast = Parser::new(Lexer::from_str(text, "file.isle")) .parse_defs() .expect("should parse"); let mut tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); From cd55dc956827746b07c803dc986f16637cfacd45 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 17:20:55 -0700 Subject: [PATCH 12/95] WIP. --- cranelift/isle/src/codegen.rs | 55 ++++++++++++++++++++++++++--------- cranelift/isle/src/ir.rs | 7 +++-- cranelift/isle/src/lexer.rs | 2 +- cranelift/isle/src/sema.rs | 3 ++ 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 55f530f9c3..001c1f36b9 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -852,6 +852,8 @@ impl<'a> Codegen<'a> { Ok(()) } + /// Returns a `bool` indicating whether this pattern inst is + /// infallible. fn generate_pattern_inst( &self, code: &mut dyn Write, @@ -859,27 +861,30 @@ impl<'a> Codegen<'a> { inst: &PatternInst, indent: &str, ctx: &mut BodyContext, - ) -> Result<(), Error> { + ) -> Result { match inst { &PatternInst::Arg { index, .. } => { - let output = Value::Expr { + let output = Value::Pattern { inst: id, output: 0, }; let outputname = self.value_name(&output); writeln!(code, "{}let {} = arg{};", indent, outputname, index)?; writeln!(code, "{}{{", indent)?; + Ok(true) } &PatternInst::MatchEqual { ref a, ref b, .. } => { let a = self.value_by_ref(a, ctx); let b = self.value_by_ref(b, ctx); writeln!(code, "{}if {} == {} {{", indent, a, b)?; + Ok(false) } &PatternInst::MatchInt { ref input, int_val, .. } => { let input = self.value_by_val(input, ctx); writeln!(code, "{}if {} == {} {{", indent, input, int_val)?; + Ok(false) } &PatternInst::MatchVariant { ref input, @@ -897,21 +902,23 @@ impl<'a> Codegen<'a> { let variantname = &self.typeenv.syms[variant.name.index()]; let args = arg_tys .iter() + .zip(variant.fields.iter()) .enumerate() - .map(|(i, ty)| { + .map(|(i, (ty, field))| { let value = Value::Pattern { inst: id, output: i, }; let valuename = self.value_name(&value); + let fieldname = &self.typeenv.syms[field.name.index()]; match &self.typeenv.types[ty.index()] { &Type::Primitive(..) => { self.define_val(&value, ctx, /* is_ref = */ false); - valuename + format!("{}: {}", fieldname, valuename) } &Type::Enum { .. } => { self.define_val(&value, ctx, /* is_ref = */ true); - format!("ref {}", valuename) + format!("{}: ref {}", fieldname, valuename) } } }) @@ -925,6 +932,7 @@ impl<'a> Codegen<'a> { args.join(", "), input )?; + Ok(false) } &PatternInst::Extract { ref input, @@ -968,10 +976,9 @@ impl<'a> Codegen<'a> { input )?; } + Ok(infallible) } } - - Ok(()) } fn generate_body( @@ -981,15 +988,27 @@ impl<'a> Codegen<'a> { trie: &TrieNode, indent: &str, ctx: &mut BodyContext, - ) -> Result<(), Error> { + ) -> Result { + log::trace!("generate_body: trie {:?}", trie); + let mut returned = false; match trie { &TrieNode::Empty => {} &TrieNode::Leaf { ref output, .. } => { + writeln!( + code, + "{}// Rule at {}.", + indent, + output.pos.pretty_print_line(&self.typeenv.filenames[..]) + )?; // If this is a leaf node, generate the ExprSequence and return. for (id, inst) in output.insts.iter().enumerate() { let id = InstId(id); self.generate_expr_inst(code, id, inst, indent, ctx)?; + if let &ExprInst::Return { .. } = inst { + returned = true; + break; + } } } @@ -1005,20 +1024,28 @@ impl<'a> Codegen<'a> { { match symbol { &TrieSymbol::EndOfMatch => { - self.generate_body(code, depth + 1, node, &subindent, ctx)?; + returned = self.generate_body(code, depth + 1, node, indent, ctx)?; } &TrieSymbol::Match { ref op } => { let id = InstId(depth); - self.generate_pattern_inst(code, id, op, &subindent, ctx)?; - self.generate_body(code, depth + 1, node, &subindent, ctx)?; - writeln!(code, "{}}}", subindent)?; + let infallible = + self.generate_pattern_inst(code, id, op, indent, ctx)?; + let sub_returned = + self.generate_body(code, depth + 1, node, &subindent, ctx)?; + writeln!(code, "{}}}", indent)?; + if infallible && sub_returned { + returned = true; + break; + } } } } } } - writeln!(code, "{}return None;", indent)?; - Ok(()) + if !returned { + writeln!(code, "{}return None;", indent)?; + } + Ok(returned) } } diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index b1f3057619..33641c4de2 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -1,6 +1,7 @@ //! Lowered matching IR. use crate::declare_id; +use crate::lexer::Pos; use crate::sema::*; use std::collections::HashMap; @@ -110,6 +111,8 @@ pub struct ExprSequence { /// Instruction sequence for expression. InstId indexes into this /// sequence for `Value::Expr` values. pub insts: Vec, + /// Position at which the rule producing this sequence was located. + pub pos: Pos, } impl PatternSequence { @@ -197,8 +200,7 @@ impl PatternSequence { // Bind the appropriate variable and recurse. assert!(!vars.contains_key(&var)); vars.insert(var, (None, input.unwrap())); // bind first, so subpat can use it - let root_term = - self.gen_pattern(input, typeenv, termenv, &*subpat, vars); + let root_term = self.gen_pattern(input, typeenv, termenv, &*subpat, vars); vars.get_mut(&var).unwrap().0 = root_term; root_term } @@ -352,6 +354,7 @@ pub fn lower_rule( ) { let mut pattern_seq: PatternSequence = Default::default(); let mut expr_seq: ExprSequence = Default::default(); + expr_seq.pos = termenv.rules[rule.index()].pos; // Lower the pattern, starting from the root input value. let ruledata = &termenv.rules[rule.index()]; diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/src/lexer.rs index af53d7313e..5c85f5a730 100644 --- a/cranelift/isle/src/lexer.rs +++ b/cranelift/isle/src/lexer.rs @@ -18,7 +18,7 @@ enum LexerInput<'a> { File { content: String, filename: String }, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)] pub struct Pos { pub file: usize, pub offset: usize, diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index b9c3d2eeac..40789ded0d 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -113,6 +113,7 @@ pub struct Rule { pub lhs: Pattern, pub rhs: Expr, pub prio: Option, + pub pos: Pos, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -414,6 +415,7 @@ impl TermEnv { for def in &defs.defs { match def { &ast::Def::Rule(ref rule) => { + let pos = rule.pos; let mut bindings = Bindings { next_var: 0, vars: vec![], @@ -434,6 +436,7 @@ impl TermEnv { lhs, rhs, prio: rule.prio, + pos, }); } &ast::Def::Extern(ast::Extern::Constructor { From be1140e80aa4d76fc7bf553059280088f0be8b97 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 18:45:03 -0700 Subject: [PATCH 13/95] WIP. --- cranelift/isle/examples/test.isle | 9 +++ cranelift/isle/src/codegen.rs | 77 ++++++++++++--------- cranelift/isle/src/ir.rs | 109 +++++++++++++++++++++++++++++- 3 files changed, 159 insertions(+), 36 deletions(-) diff --git a/cranelift/isle/examples/test.isle b/cranelift/isle/examples/test.isle index 1ea1c3ce98..39125a94d1 100644 --- a/cranelift/isle/examples/test.isle +++ b/cranelift/isle/examples/test.isle @@ -10,3 +10,12 @@ (rule (Lower (A.A1 sub @ (Input (A.A2 42)))) (B.B2 sub)) + +(decl Extractor (B) A) +(rule + (A.A2 x) + (Extractor (B.B1 x))) + +(rule + (Lower (Extractor b)) + b) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 001c1f36b9..19c03a4692 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -1,8 +1,8 @@ //! Generate Rust code from a series of Sequences. -use crate::error::Error; use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; -use crate::sema::{RuleId, Term, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; +use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; +use crate::{error::Error, ir::reverse_rule}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; @@ -463,11 +463,14 @@ impl<'a> TermFunctionsBuilder<'a> { .or_insert_with(|| TermFunctionBuilder::new(input_root_term)) .add_rule(prio, pattern.clone(), expr.clone()); } + if let Some(output_root_term) = rhs_root { - self.builders_by_output - .entry(output_root_term) - .or_insert_with(|| TermFunctionBuilder::new(output_root_term)) - .add_rule(prio, pattern, expr); + if let Some((reverse_pattern, reverse_expr)) = reverse_rule(&pattern, &expr) { + self.builders_by_output + .entry(output_root_term) + .or_insert_with(|| TermFunctionBuilder::new(output_root_term)) + .add_rule(prio, reverse_pattern, reverse_expr); + } } } } @@ -498,6 +501,8 @@ pub struct Codegen<'a> { #[derive(Clone, Debug, Default)] struct BodyContext { borrowed_values: HashSet, + expected_return_vals: usize, + tuple_return: bool, } impl<'a> Codegen<'a> { @@ -694,7 +699,8 @@ impl<'a> Codegen<'a> { self.type_name(termdata.ret_ty, /* by_ref = */ None) )?; - let mut body_ctx = Default::default(); + let mut body_ctx: BodyContext = Default::default(); + body_ctx.expected_return_vals = 1; self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; writeln!(code, "}}")?; @@ -717,7 +723,7 @@ impl<'a> Codegen<'a> { // Get the name of the term and build up the signature. let (func_name, _) = self.extractor_name_and_infallible(termid); let arg = format!( - "arg: {}", + "arg0: {}", self.type_name(termdata.ret_ty, /* by_ref = */ Some("&")) ); let ret_tuple_tys = termdata @@ -735,14 +741,15 @@ impl<'a> Codegen<'a> { )?; writeln!( code, - "fn {}<'a, C>(ctx: &mut C, {}) -> Option<({})> {{", + "fn {}(ctx: &mut C, {}) -> Option<({},)> {{", func_name, arg, ret_tuple_tys.join(", "), )?; - let mut body_ctx = Default::default(); - self.generate_extractor_header(code, termdata, &mut body_ctx)?; + let mut body_ctx: BodyContext = Default::default(); + body_ctx.expected_return_vals = ret_tuple_tys.len(); + body_ctx.tuple_return = true; self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; writeln!(code, " }}")?; writeln!(code, "}}")?; @@ -751,17 +758,6 @@ impl<'a> Codegen<'a> { Ok(()) } - fn generate_extractor_header( - &self, - code: &mut dyn Write, - termdata: &Term, - ctx: &mut BodyContext, - ) -> Result<(), Error> { - writeln!(code, " {{")?; - todo!(); - Ok(()) - } - fn generate_expr_inst( &self, code: &mut dyn Write, @@ -769,6 +765,7 @@ impl<'a> Codegen<'a> { inst: &ExprInst, indent: &str, ctx: &mut BodyContext, + returns: &mut Vec<(usize, String)>, ) -> Result<(), Error> { match inst { &ExprInst::ConstInt { ty, val } => { @@ -843,9 +840,11 @@ impl<'a> Codegen<'a> { )?; self.define_val(&output, ctx, /* is_ref = */ false); } - &ExprInst::Return { ref value, .. } => { + &ExprInst::Return { + index, ref value, .. + } => { let value_expr = self.value_by_val(value, ctx); - writeln!(code, "{}return Some({});", indent, value_expr)?; + returns.push((index, value_expr)); } } @@ -936,9 +935,9 @@ impl<'a> Codegen<'a> { } &PatternInst::Extract { ref input, - input_ty, ref arg_tys, term, + .. } => { let input = self.value_by_ref(input, ctx); let (etor_name, infallible) = self.extractor_name_and_infallible(term); @@ -946,7 +945,7 @@ impl<'a> Codegen<'a> { let args = arg_tys .iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, _ty)| { let value = Value::Pattern { inst: id, output: i, @@ -959,7 +958,7 @@ impl<'a> Codegen<'a> { if infallible { writeln!( code, - "{}let Some(({})) = {}(ctx, {});", + "{}let Some(({},)) = {}(ctx, {});", indent, args.join(", "), etor_name, @@ -969,7 +968,7 @@ impl<'a> Codegen<'a> { } else { writeln!( code, - "{}if let Some(({})) = {}(ctx, {}) {{", + "{}if let Some(({},)) = {}(ctx, {}) {{", indent, args.join(", "), etor_name, @@ -1002,14 +1001,26 @@ impl<'a> Codegen<'a> { output.pos.pretty_print_line(&self.typeenv.filenames[..]) )?; // If this is a leaf node, generate the ExprSequence and return. + let mut returns = vec![]; for (id, inst) in output.insts.iter().enumerate() { let id = InstId(id); - self.generate_expr_inst(code, id, inst, indent, ctx)?; - if let &ExprInst::Return { .. } = inst { - returned = true; - break; - } + self.generate_expr_inst(code, id, inst, indent, ctx, &mut returns)?; } + + assert_eq!(returns.len(), ctx.expected_return_vals); + returns.sort_by_key(|(index, _)| *index); + if ctx.tuple_return { + let return_values = returns + .into_iter() + .map(|(_, expr)| expr) + .collect::>() + .join(", "); + writeln!(code, "{}return Some(({},));", indent, return_values)?; + } else { + writeln!(code, "{}return Some({});", indent, returns[0].1)?; + } + + returned = true; } &TrieNode::Decision { ref edges } => { diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 33641c4de2..d12c9bdd2c 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -71,8 +71,8 @@ pub enum ExprInst { term: TermId, }, - /// Set the return value. Produces no values. - Return { ty: TypeId, value: Value }, + /// Set the Nth return value. Produces no values. + Return { index: usize, ty: TypeId, value: Value }, } impl ExprInst { @@ -294,7 +294,11 @@ impl ExprSequence { } fn add_return(&mut self, ty: TypeId, value: Value) { - self.add_inst(ExprInst::Return { ty, value }); + self.add_inst(ExprInst::Return { index: 0, ty, value }); + } + + fn add_multi_return(&mut self, index: usize, ty: TypeId, value: Value) { + self.add_inst(ExprInst::Return { index, ty, value }); } /// Creates a sequence of ExprInsts to generate the given @@ -370,3 +374,102 @@ pub fn lower_rule( (lhs_root_term, pattern_seq, rhs_root_term, expr_seq) } + +/// Reverse a sequence to form an extractor from a constructor. +pub fn reverse_rule( + orig_pat: &PatternSequence, + orig_expr: &ExprSequence, +) -> Option<( + PatternSequence, + ExprSequence, + )> +{ + let mut pattern_seq = PatternSequence::default(); + let mut expr_seq = ExprSequence::default(); + expr_seq.pos = orig_expr.pos; + + let mut value_map = HashMap::new(); + + for (id, inst) in orig_expr.insts.iter().enumerate().rev() { + let id = InstId(id); + match inst { + &ExprInst::Return { index, ty, value } => { + let new_value = pattern_seq.add_arg(index, ty); + value_map.insert(value, new_value); + } + &ExprInst::Construct { ref inputs, ty, term } => { + let arg_tys = inputs.iter().map(|(_, ty)| *ty).collect::>(); + let input_ty = ty; + // Input to the Extract is the output of the Construct. + let input = value_map.get(&Value::Expr { inst: id, output: 0 })?.clone(); + let outputs = pattern_seq.add_extract(input, input_ty, &arg_tys[..], term); + for (input, output) in inputs.iter().map(|(val, _)| val).zip(outputs.into_iter()) { + value_map.insert(*input, output); + } + } + &ExprInst::CreateVariant { ref inputs, ty, variant } => { + let arg_tys = inputs.iter().map(|(_, ty)| *ty).collect::>(); + let input_ty = ty; + // Input to the MatchVariant is the output of the CreateVariant. + let input = value_map.get(&Value::Expr { inst: id, output: 0 })?.clone(); + let outputs = pattern_seq.add_match_variant(input, input_ty, &arg_tys[..], variant); + for (input, output) in inputs.iter().map(|(val, _)| val).zip(outputs.into_iter()) { + value_map.insert(*input, output); + } + } + &ExprInst::ConstInt { ty, val } => { + let input = value_map.get(&Value::Expr { inst: id, output: 0 })?.clone(); + pattern_seq.add_match_int(input, ty, val); + } + } + } + + for (id, inst) in orig_pat.insts.iter().enumerate().rev() { + let id = InstId(id); + match inst { + &PatternInst::Extract { input, input_ty, ref arg_tys, term } => { + let mut inputs = vec![]; + for i in 0..arg_tys.len() { + let value = Value::Pattern { inst: id, output: i }; + let new_value = value_map.get(&value)?.clone(); + inputs.push((new_value, arg_tys[i])); + } + let output = expr_seq.add_construct(&inputs[..], input_ty, term); + value_map.insert(input, output); + + } + &PatternInst::MatchVariant { input, input_ty, ref arg_tys, variant } => { + let mut inputs = vec![]; + for i in 0..arg_tys.len() { + let value = Value::Pattern { inst: id, output: i }; + let new_value = value_map.get(&value)?.clone(); + inputs.push((new_value, arg_tys[i])); + } + let output = expr_seq.add_create_variant(&inputs[..], input_ty, variant); + value_map.insert(input, output); + } + &PatternInst::MatchEqual { a, b, .. } => { + if let Some(new_a) = value_map.get(&a).cloned() { + if !value_map.contains_key(&b) { + value_map.insert(b, new_a); + } + } else if let Some(new_b) = value_map.get(&b).cloned() { + if !value_map.contains_key(&a) { + value_map.insert(a, new_b); + } + } + } + &PatternInst::MatchInt { input, ty, int_val } => { + let output = expr_seq.add_const_int(ty, int_val); + value_map.insert(input, output); + } + &PatternInst::Arg { index, ty } => { + let value = Value::Pattern { inst: id, output: 0 }; + let new_value = value_map.get(&value)?.clone(); + expr_seq.add_multi_return(index, ty, new_value); + } + } + } + + Some((pattern_seq, expr_seq)) +} From d7efd9f219a185c326d175e8dcf3920422ca0434 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 20:03:21 -0700 Subject: [PATCH 14/95] Working extractor and constructor generation from rules! --- cranelift/isle/src/codegen.rs | 51 +++++--- cranelift/isle/src/ir.rs | 227 ++++++++++++++++------------------ 2 files changed, 139 insertions(+), 139 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 19c03a4692..cfc4f5eb77 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -1,8 +1,8 @@ //! Generate Rust code from a series of Sequences. +use crate::error::Error; use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; -use crate::{error::Error, ir::reverse_rule}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; @@ -448,29 +448,40 @@ impl<'a> TermFunctionsBuilder<'a> { let rule = RuleId(rule); let prio = self.termenv.rules[rule.index()].prio.unwrap_or(0); - let (lhs_root, pattern, rhs_root, expr) = lower_rule(self.typeenv, self.termenv, rule); - log::trace!( - "build:\n- rule {:?}\n- lhs_root {:?} rhs_root {:?}\n- pattern {:?}\n- expr {:?}", - self.termenv.rules[rule.index()], - lhs_root, - rhs_root, - pattern, - expr - ); - if let Some(input_root_term) = lhs_root { + if let Some((pattern, expr, lhs_root)) = lower_rule( + self.typeenv, + self.termenv, + rule, + /* forward_dir = */ true, + ) { + log::trace!( + "build:\n- rule {:?}\n- fwd pattern {:?}\n- fwd expr {:?}", + self.termenv.rules[rule.index()], + pattern, + expr + ); self.builders_by_input - .entry(input_root_term) - .or_insert_with(|| TermFunctionBuilder::new(input_root_term)) + .entry(lhs_root) + .or_insert_with(|| TermFunctionBuilder::new(lhs_root)) .add_rule(prio, pattern.clone(), expr.clone()); } - if let Some(output_root_term) = rhs_root { - if let Some((reverse_pattern, reverse_expr)) = reverse_rule(&pattern, &expr) { - self.builders_by_output - .entry(output_root_term) - .or_insert_with(|| TermFunctionBuilder::new(output_root_term)) - .add_rule(prio, reverse_pattern, reverse_expr); - } + if let Some((pattern, expr, rhs_root)) = lower_rule( + self.typeenv, + self.termenv, + rule, + /* forward_dir = */ false, + ) { + log::trace!( + "build:\n- rule {:?}\n- rev pattern {:?}\n- rev expr {:?}", + self.termenv.rules[rule.index()], + pattern, + expr + ); + self.builders_by_output + .entry(rhs_root) + .or_insert_with(|| TermFunctionBuilder::new(rhs_root)) + .add_rule(prio, pattern, expr); } } } diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index d12c9bdd2c..4250f32320 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -72,7 +72,11 @@ pub enum ExprInst { }, /// Set the Nth return value. Produces no values. - Return { index: usize, ty: TypeId, value: Value }, + Return { + index: usize, + ty: TypeId, + value: Value, + }, } impl ExprInst { @@ -294,7 +298,11 @@ impl ExprSequence { } fn add_return(&mut self, ty: TypeId, value: Value) { - self.add_inst(ExprInst::Return { index: 0, ty, value }); + self.add_inst(ExprInst::Return { + index: 0, + ty, + value, + }); } fn add_multi_return(&mut self, index: usize, ty: TypeId, value: Value) { @@ -304,40 +312,55 @@ impl ExprSequence { /// Creates a sequence of ExprInsts to generate the given /// expression value. Returns the value ID as well as the root /// term ID, if any. + /// + /// If `gen_final_construct` is false and the value is a + /// constructor call, this returns the arguments instead. This is + /// used when codegen'ing extractors for internal terms. fn gen_expr( &mut self, typeenv: &TypeEnv, termenv: &TermEnv, expr: &Expr, vars: &HashMap, Value)>, - ) -> (Option, Value) { + gen_final_construct: bool, + ) -> (Option, Vec) { match expr { - &Expr::ConstInt(ty, val) => (None, self.add_const_int(ty, val)), + &Expr::ConstInt(ty, val) => (None, vec![self.add_const_int(ty, val)]), &Expr::Let(_ty, ref bindings, ref subexpr) => { let mut vars = vars.clone(); for &(var, _var_ty, ref var_expr) in bindings { let (var_value_term, var_value) = - self.gen_expr(typeenv, termenv, &*var_expr, &vars); + self.gen_expr(typeenv, termenv, &*var_expr, &vars, false); + let var_value = var_value[0]; vars.insert(var, (var_value_term, var_value)); } - self.gen_expr(typeenv, termenv, &*subexpr, &vars) + self.gen_expr(typeenv, termenv, &*subexpr, &vars, gen_final_construct) + } + &Expr::Var(_ty, var_id) => { + let (root_term, value) = vars.get(&var_id).cloned().unwrap(); + (root_term, vec![value]) } - &Expr::Var(_ty, var_id) => vars.get(&var_id).cloned().unwrap(), &Expr::Term(ty, term, ref arg_exprs) => { let termdata = &termenv.terms[term.index()]; let mut arg_values_tys = vec![]; for (arg_ty, arg_expr) in termdata.arg_tys.iter().cloned().zip(arg_exprs.iter()) { - arg_values_tys - .push((self.gen_expr(typeenv, termenv, &*arg_expr, &vars).1, arg_ty)); + arg_values_tys.push(( + self.gen_expr(typeenv, termenv, &*arg_expr, &vars, false).1[0], + arg_ty, + )); } match &termdata.kind { &TermKind::EnumVariant { variant } => ( None, - self.add_create_variant(&arg_values_tys[..], ty, variant), + vec![self.add_create_variant(&arg_values_tys[..], ty, variant)], + ), + &TermKind::Regular { .. } if !gen_final_construct => ( + Some(termdata.id), + arg_values_tys.into_iter().map(|(val, _ty)| val).collect(), ), &TermKind::Regular { .. } => ( Some(termdata.id), - self.add_construct(&arg_values_tys[..], ty, term), + vec![self.add_construct(&arg_values_tys[..], ty, term)], ), } } @@ -350,12 +373,8 @@ pub fn lower_rule( tyenv: &TypeEnv, termenv: &TermEnv, rule: RuleId, -) -> ( - Option, - PatternSequence, - Option, - ExprSequence, -) { + is_forward_dir: bool, +) -> Option<(PatternSequence, ExprSequence, TermId)> { let mut pattern_seq: PatternSequence = Default::default(); let mut expr_seq: ExprSequence = Default::default(); expr_seq.pos = termenv.rules[rule.index()].pos; @@ -363,113 +382,83 @@ pub fn lower_rule( // Lower the pattern, starting from the root input value. let ruledata = &termenv.rules[rule.index()]; let mut vars = HashMap::new(); - let lhs_root_term = pattern_seq.gen_pattern(None, tyenv, termenv, &ruledata.lhs, &mut vars); - // Lower the expression, making use of the bound variables - // from the pattern. - let (rhs_root_term, rhs_root) = expr_seq.gen_expr(tyenv, termenv, &ruledata.rhs, &vars); - // Return the root RHS value. - let output_ty = ruledata.rhs.ty(); - expr_seq.add_return(output_ty, rhs_root); + if is_forward_dir { + let lhs_root_term = pattern_seq.gen_pattern(None, tyenv, termenv, &ruledata.lhs, &mut vars); + let root_term = match lhs_root_term { + Some(t) => t, + None => { + return None; + } + }; - (lhs_root_term, pattern_seq, rhs_root_term, expr_seq) + // Lower the expression, making use of the bound variables + // from the pattern. + let (_, rhs_root_vals) = expr_seq.gen_expr( + tyenv, + termenv, + &ruledata.rhs, + &vars, + /* final_construct = */ true, + ); + // Return the root RHS value. + let output_ty = ruledata.rhs.ty(); + assert_eq!(rhs_root_vals.len(), 1); + expr_seq.add_return(output_ty, rhs_root_vals[0]); + Some((pattern_seq, expr_seq, root_term)) + } else { + let arg = pattern_seq.add_arg(0, ruledata.lhs.ty()); + let _ = pattern_seq.gen_pattern(Some(arg), tyenv, termenv, &ruledata.lhs, &mut vars); + let (rhs_root_term, rhs_root_vals) = expr_seq.gen_expr( + tyenv, + termenv, + &ruledata.rhs, + &vars, + /* final_construct = */ false, + ); + + let root_term = match rhs_root_term { + Some(t) => t, + None => { + return None; + } + }; + let termdata = &termenv.terms[root_term.index()]; + for (i, (val, ty)) in rhs_root_vals + .into_iter() + .zip(termdata.arg_tys.iter()) + .enumerate() + { + expr_seq.add_multi_return(i, *ty, val); + } + + Some((pattern_seq, expr_seq, root_term)) + } } -/// Reverse a sequence to form an extractor from a constructor. -pub fn reverse_rule( - orig_pat: &PatternSequence, - orig_expr: &ExprSequence, -) -> Option<( - PatternSequence, - ExprSequence, - )> -{ - let mut pattern_seq = PatternSequence::default(); - let mut expr_seq = ExprSequence::default(); - expr_seq.pos = orig_expr.pos; - - let mut value_map = HashMap::new(); - - for (id, inst) in orig_expr.insts.iter().enumerate().rev() { - let id = InstId(id); - match inst { - &ExprInst::Return { index, ty, value } => { - let new_value = pattern_seq.add_arg(index, ty); - value_map.insert(value, new_value); - } - &ExprInst::Construct { ref inputs, ty, term } => { - let arg_tys = inputs.iter().map(|(_, ty)| *ty).collect::>(); - let input_ty = ty; - // Input to the Extract is the output of the Construct. - let input = value_map.get(&Value::Expr { inst: id, output: 0 })?.clone(); - let outputs = pattern_seq.add_extract(input, input_ty, &arg_tys[..], term); - for (input, output) in inputs.iter().map(|(val, _)| val).zip(outputs.into_iter()) { - value_map.insert(*input, output); - } - } - &ExprInst::CreateVariant { ref inputs, ty, variant } => { - let arg_tys = inputs.iter().map(|(_, ty)| *ty).collect::>(); - let input_ty = ty; - // Input to the MatchVariant is the output of the CreateVariant. - let input = value_map.get(&Value::Expr { inst: id, output: 0 })?.clone(); - let outputs = pattern_seq.add_match_variant(input, input_ty, &arg_tys[..], variant); - for (input, output) in inputs.iter().map(|(val, _)| val).zip(outputs.into_iter()) { - value_map.insert(*input, output); - } - } - &ExprInst::ConstInt { ty, val } => { - let input = value_map.get(&Value::Expr { inst: id, output: 0 })?.clone(); - pattern_seq.add_match_int(input, ty, val); - } +/// Trim the final Construct and Return ops in an ExprSequence in +/// order to allow the extractor to be codegen'd. +pub fn trim_expr_for_extractor(mut expr: ExprSequence) -> ExprSequence { + let ret_inst = expr.insts.pop().unwrap(); + let retval = match ret_inst { + ExprInst::Return { value, .. } => value, + _ => panic!("Last instruction is not a return"), + }; + assert_eq!( + retval, + Value::Expr { + inst: InstId(expr.insts.len() - 1), + output: 0 } + ); + let construct_inst = expr.insts.pop().unwrap(); + let inputs = match construct_inst { + ExprInst::Construct { inputs, .. } => inputs, + _ => panic!("Returned value is not a construct call"), + }; + for (i, (value, ty)) in inputs.into_iter().enumerate() { + expr.add_multi_return(i, ty, value); } - for (id, inst) in orig_pat.insts.iter().enumerate().rev() { - let id = InstId(id); - match inst { - &PatternInst::Extract { input, input_ty, ref arg_tys, term } => { - let mut inputs = vec![]; - for i in 0..arg_tys.len() { - let value = Value::Pattern { inst: id, output: i }; - let new_value = value_map.get(&value)?.clone(); - inputs.push((new_value, arg_tys[i])); - } - let output = expr_seq.add_construct(&inputs[..], input_ty, term); - value_map.insert(input, output); - - } - &PatternInst::MatchVariant { input, input_ty, ref arg_tys, variant } => { - let mut inputs = vec![]; - for i in 0..arg_tys.len() { - let value = Value::Pattern { inst: id, output: i }; - let new_value = value_map.get(&value)?.clone(); - inputs.push((new_value, arg_tys[i])); - } - let output = expr_seq.add_create_variant(&inputs[..], input_ty, variant); - value_map.insert(input, output); - } - &PatternInst::MatchEqual { a, b, .. } => { - if let Some(new_a) = value_map.get(&a).cloned() { - if !value_map.contains_key(&b) { - value_map.insert(b, new_a); - } - } else if let Some(new_b) = value_map.get(&b).cloned() { - if !value_map.contains_key(&a) { - value_map.insert(a, new_b); - } - } - } - &PatternInst::MatchInt { input, ty, int_val } => { - let output = expr_seq.add_const_int(ty, int_val); - value_map.insert(input, output); - } - &PatternInst::Arg { index, ty } => { - let value = Value::Pattern { inst: id, output: 0 }; - let new_value = value_map.get(&value)?.clone(); - expr_seq.add_multi_return(index, ty, new_value); - } - } - } - - Some((pattern_seq, expr_seq)) + expr } From 4a2cd7882700caaf3ac42eff6571a99d5e3495e4 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 20:25:10 -0700 Subject: [PATCH 15/95] Working example and README --- cranelift/isle/README.md | 5 +++ .../{examples => isle_examples}/test.isle | 0 cranelift/isle/isle_examples/test_main.rs | 8 ++++ cranelift/isle/src/codegen.rs | 39 ++++++++++++++++--- cranelift/isle/src/parser.rs | 10 +++-- 5 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 cranelift/isle/README.md rename cranelift/isle/{examples => isle_examples}/test.isle (100%) create mode 100644 cranelift/isle/isle_examples/test_main.rs diff --git a/cranelift/isle/README.md b/cranelift/isle/README.md new file mode 100644 index 0000000000..3145c7256e --- /dev/null +++ b/cranelift/isle/README.md @@ -0,0 +1,5 @@ +```plain + $ cargo build --release + $ target/release/isle -i isle_examples/test.isle -o isle_examples/test.rs + $ rustc isle_examples/test_main.rs +``` diff --git a/cranelift/isle/examples/test.isle b/cranelift/isle/isle_examples/test.isle similarity index 100% rename from cranelift/isle/examples/test.isle rename to cranelift/isle/isle_examples/test.isle diff --git a/cranelift/isle/isle_examples/test_main.rs b/cranelift/isle/isle_examples/test_main.rs new file mode 100644 index 0000000000..92cfdf2c0b --- /dev/null +++ b/cranelift/isle/isle_examples/test_main.rs @@ -0,0 +1,8 @@ + +pub fn get_input(ctx: &mut C, x: u32) -> Option<(test::A,)> { + None +} + +fn main() {} + +mod test; diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index cfc4f5eb77..42257f7fee 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -577,7 +577,7 @@ impl<'a> Codegen<'a> { pos.pretty_print_line(&self.typeenv.filenames[..]) )?; writeln!(code, "#[derive(Clone, Debug)]")?; - writeln!(code, "enum {} {{", name)?; + writeln!(code, "pub enum {} {{", name)?; for variant in variants { let name = &self.typeenv.syms[variant.name.index()]; writeln!(code, " {} {{", name)?; @@ -733,9 +733,16 @@ impl<'a> Codegen<'a> { // Get the name of the term and build up the signature. let (func_name, _) = self.extractor_name_and_infallible(termid); + let arg_is_prim = match &self.typeenv.types[termdata.ret_ty.index()] { + &Type::Primitive(..) => true, + _ => false, + }; let arg = format!( "arg0: {}", - self.type_name(termdata.ret_ty, /* by_ref = */ Some("&")) + self.type_name( + termdata.ret_ty, + /* by_ref = */ if arg_is_prim { None } else { Some("&") } + ), ); let ret_tuple_tys = termdata .arg_tys @@ -761,8 +768,7 @@ impl<'a> Codegen<'a> { let mut body_ctx: BodyContext = Default::default(); body_ctx.expected_return_vals = ret_tuple_tys.len(); body_ctx.tuple_return = true; - self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; - writeln!(code, " }}")?; + self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; writeln!(code, "}}")?; } @@ -873,14 +879,26 @@ impl<'a> Codegen<'a> { ctx: &mut BodyContext, ) -> Result { match inst { - &PatternInst::Arg { index, .. } => { + &PatternInst::Arg { index, ty } => { let output = Value::Pattern { inst: id, output: 0, }; let outputname = self.value_name(&output); + let is_ref = match &self.typeenv.types[ty.index()] { + &Type::Primitive(..) => false, + _ => true, + }; writeln!(code, "{}let {} = arg{};", indent, outputname, index)?; writeln!(code, "{}{{", indent)?; + self.define_val( + &Value::Pattern { + inst: id, + output: 0, + }, + ctx, + is_ref, + ); Ok(true) } &PatternInst::MatchEqual { ref a, ref b, .. } => { @@ -946,11 +964,20 @@ impl<'a> Codegen<'a> { } &PatternInst::Extract { ref input, + input_ty, ref arg_tys, term, .. } => { - let input = self.value_by_ref(input, ctx); + let input_ty_prim = match &self.typeenv.types[input_ty.index()] { + &Type::Primitive(..) => true, + _ => false, + }; + let input = if input_ty_prim { + self.value_by_val(input, ctx) + } else { + self.value_by_ref(input, ctx) + }; let (etor_name, infallible) = self.extractor_name_and_infallible(term); let args = arg_tys diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index 8d9591b175..8305e72d44 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -368,13 +368,13 @@ mod test { (Load (a Reg) (dest Reg)))) (type u32 (primitive u32)) "; - let defs = Parser::new("(none)", text) + let defs = Parser::new(Lexer::from_str(text, "(none)")) .parse_defs() .expect("should parse"); assert_eq!( defs, Defs { - filename: "(none)".to_string(), + filenames: vec!["(none)".to_string()], defs: vec![ Def::Type(Type { name: Ident("Inst".to_string()), @@ -412,8 +412,9 @@ mod test { } ]), pos: Pos { + file: 0, offset: 42, - line: 4, + line: 3, col: 18, }, }), @@ -422,8 +423,9 @@ mod test { is_extern: false, ty: TypeValue::Primitive(Ident("u32".to_string())), pos: Pos { + file: 0, offset: 167, - line: 7, + line: 6, col: 18, }, }), From bc91a4e5f6bf863e8f0c5909e826ce90246a93c0 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 20:27:38 -0700 Subject: [PATCH 16/95] Add TODO --- cranelift/isle/TODO | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 cranelift/isle/TODO diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO new file mode 100644 index 0000000000..8fd0e60ad2 --- /dev/null +++ b/cranelift/isle/TODO @@ -0,0 +1,7 @@ +- Convert series of if-lets from MatchVariants into a match { ... }. + - Should be pretty simple; just need to recognize adjacent edges in + priority-order that are all MatchVariants on the same input. + +- Document semantics carefully, especially wrt extractors. + +- Verify that priorities work as expected. From b8e916a0ab251a526beb9eacf4266e9f85925e54 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 4 Sep 2021 20:37:47 -0700 Subject: [PATCH 17/95] Another example, testing rule priorities a bit --- cranelift/isle/isle_examples/test2.isle | 19 +++++++++++++++++++ cranelift/isle/src/codegen.rs | 15 ++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 cranelift/isle/isle_examples/test2.isle diff --git a/cranelift/isle/isle_examples/test2.isle b/cranelift/isle/isle_examples/test2.isle new file mode 100644 index 0000000000..34c5f1c170 --- /dev/null +++ b/cranelift/isle/isle_examples/test2.isle @@ -0,0 +1,19 @@ +(type u32 (primitive u32)) +(type A (enum + (A1 (x B) (y B)))) +(type B (enum + (B1 (x u32)))) + +(decl A2B (A) B) + +(rule 1 + (A2B (A.A1 _ (B.B1 x))) + (B.B1 x)) + +(rule 1 + (A2B (A.A1 (B.B1 x) _)) + (B.B1 x)) + +(rule -1 + (A2B (A.A1 _ _)) + (B.B1 42)) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 42257f7fee..f15781e8b6 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -712,7 +712,11 @@ impl<'a> Codegen<'a> { let mut body_ctx: BodyContext = Default::default(); body_ctx.expected_return_vals = 1; - self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; + let returned = + self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; + if !returned { + writeln!(code, " return None;")?; + } writeln!(code, "}}")?; } @@ -768,7 +772,11 @@ impl<'a> Codegen<'a> { let mut body_ctx: BodyContext = Default::default(); body_ctx.expected_return_vals = ret_tuple_tys.len(); body_ctx.tuple_return = true; - self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; + let returned = + self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; + if !returned { + writeln!(code, " return None;")?; + } writeln!(code, "}}")?; } @@ -1092,9 +1100,6 @@ impl<'a> Codegen<'a> { } } - if !returned { - writeln!(code, "{}return None;", indent)?; - } Ok(returned) } } From 7865191093383b727da54cf89c3bb8f9a1ab5c26 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sun, 5 Sep 2021 10:41:15 -0700 Subject: [PATCH 18/95] Update long block comment describing priority trie in codegen.rs. --- cranelift/isle/src/codegen.rs | 100 ++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index f15781e8b6..65f3d29051 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -8,35 +8,34 @@ use std::fmt::Write; /// One "input symbol" for the decision tree that handles matching on /// a term. Each symbol represents one step: we either run a match op, -/// or we get a result from it. +/// or we finish the match. /// -/// Note that in the original Peepmatic scheme, the problem that this -/// solves was handled slightly differently. The automaton responded -/// to alphabet symbols that corresponded only to match results, and -/// the "extra state" was used at each automaton node to represent the -/// op to run next. This extra state differentiated nodes that would -/// otherwise be merged together by deduplication. That scheme works -/// well enough, but the "extra state" is slightly confusing and -/// diverges slightly from a pure automaton. +/// Note that in the original Peepmatic scheme, the input-symbol to +/// the FSM was specified slightly differently. The automaton +/// responded to alphabet symbols that corresponded only to match +/// results, and the "extra state" was used at each automaton node to +/// represent the op to run next. This extra state differentiated +/// nodes that would otherwise be merged together by +/// deduplication. That scheme works well enough, but the "extra +/// state" is slightly confusing and diverges slightly from a pure +/// automaton. /// -/// Instead, here, we imagine that the user of the automaton can query -/// the possible transition edges out of the current state. Each of -/// these edges corresponds to one possible match op to run. After +/// Instead, here, we imagine that the user of the automaton/trie can +/// query the possible transition edges out of the current state. Each +/// of these edges corresponds to one possible match op to run. After /// running a match op, we reach a new state corresponding to /// successful matches up to that point. /// -/// However, it's a bit more subtle than this; we add one additional -/// dimension to each match op, and an additional alphabet symbol. -/// -/// First, consider the prioritization problem. We want to give the -/// DSL user the ability to change the order in which rules apply, for -/// example to have a tier of "fallback rules" that apply only if more -/// custom rules do not match. +/// However, it's a bit more subtle than this. Consider the +/// prioritization problem. We want to give the DSL user the ability +/// to change the order in which rules apply, for example to have a +/// tier of "fallback rules" that apply only if more custom rules do +/// not match. /// /// A somewhat simplistic answer to this problem is "more specific /// rule wins". However, this implies the existence of a total /// ordering of linearized match sequences that may not fully capture -/// the intuitive meaning of "more specific". Consider four left-hand +/// the intuitive meaning of "more specific". Consider three left-hand /// sides: /// /// - (A _ _) @@ -44,7 +43,7 @@ use std::fmt::Write; /// - (A _ (B _)) /// /// Intuitively, the first is the least specific. Given the input `(A -/// (B 1) (B 2)`, we can say for sure that the first should not be +/// (B 1) (B 2))`, we can say for sure that the first should not be /// chosen, because either the second or third would match "more" of /// the input tree. But which of the second and third should be /// chosen? A "lexicographic ordering" rule would say that we sort @@ -53,29 +52,35 @@ use std::fmt::Write; /// privileging one over the other based on the order of the /// arguments. /// -/// Instead, we need a data structure that can associate matching -/// inputs *with priorities* to outputs, and provide us with a -/// decision tree as output. +/// Instead, we can accept explicit priorities from the user to allow +/// either choice. So we need a data structure that can associate +/// matching inputs *with priorities* to outputs. /// -/// Why a tree and not a fully general FSM? Because we're compiling -/// to a structured language, Rust, and states become *program points* -/// rather than *data*, we cannot easily support a DAG structure. In -/// other words, we are not producing a FSM that we can interpret at -/// runtime; rather we are compiling code in which each state -/// corresponds to a sequence of statements and control-flow that -/// branches to a next state, we naturally need nesting; we cannot -/// codegen arbitrary state transitions in an efficient manner. We -/// could support a limited form of DAG that reifies "diamonds" (two -/// alternate paths that reconverge), but supporting this in a way -/// that lets the output refer to values from either side is very -/// complex (we need to invent phi-nodes), and the cases where we want -/// to do this rather than invoke a sub-term (that is compiled to a -/// separate function) are rare. Finally, note that one reason to -/// deduplicate nodes and turn a tree back into a DAG -- +/// Next, we build a decision tree rather than an FSM. Why? Because +/// we're compiling to a structured language, Rust, and states become +/// *program points* rather than *data*, we cannot easily support a +/// DAG structure. In other words, we are not producing a FSM that we +/// can interpret at runtime; rather we are compiling code in which +/// each state corresponds to a sequence of statements and +/// control-flow that branches to a next state, we naturally need +/// nesting; we cannot codegen arbitrary state transitions in an +/// efficient manner. We could support a limited form of DAG that +/// reifies "diamonds" (two alternate paths that reconverge), but +/// supporting this in a way that lets the output refer to values from +/// either side is very complex (we need to invent phi-nodes), and the +/// cases where we want to do this rather than invoke a sub-term (that +/// is compiled to a separate function) are rare. Finally, note that +/// one reason to deduplicate nodes and turn a tree back into a DAG -- /// "output-suffix sharing" as some other instruction-rewriter -/// engines, such as Peepmatic, do -- is not done. However, -/// "output-prefix sharing" is more important to deduplicate code and -/// we do do this.) +/// engines, such as Peepmatic, do -- is not done, because all +/// "output" occurs at leaf nodes; this is necessary because we do not +/// want to start invoking external constructors until we are sure of +/// the match. Some of the code-sharing advantages of the "suffix +/// sharing" scheme can be obtained in a more flexible and +/// user-controllable way (with less understanding of internal +/// compiler logic needed) by factoring logic into different internal +/// terms, which become different compiled functions. This is likely +/// to happen anyway as part of good software engineering practice. /// /// We prepare for codegen by building a "prioritized trie", where the /// trie associates input strings with priorities to output values. @@ -107,11 +112,12 @@ use std::fmt::Write; /// final match could lie along *either* path, so we have to traverse /// both. /// -/// So, to avoid this, we perform a sort of NFA-to-DFA conversion "on -/// the fly" as we insert nodes by duplicating subtrees. At any node, -/// when inserting with a priority P and when outgoing edges lie in a -/// range [P_lo, P_hi] such that P >= P_lo and P <= P_hi, we -/// "priority-split the edges" at priority P. +/// So, to avoid this, we perform a sort of moral equivalent to the +/// NFA-to-DFA conversion "on the fly" as we insert nodes by +/// duplicating subtrees. At any node, when inserting with a priority +/// P and when outgoing edges lie in a range [P_lo, P_hi] such that P +/// >= P_lo and P <= P_hi, we "priority-split the edges" at priority +/// P. /// /// To priority-split the edges in a node at priority P: /// From ed4c8570823d14ffcc078f03484687c46aa1b385 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sun, 5 Sep 2021 10:52:55 -0700 Subject: [PATCH 19/95] Priority-trie: merge edges with different priorities into ranges when possible. --- cranelift/isle/isle_examples/test2.isle | 2 +- cranelift/isle/src/codegen.rs | 47 +++++++++++++++++-------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/cranelift/isle/isle_examples/test2.isle b/cranelift/isle/isle_examples/test2.isle index 34c5f1c170..62e905a4ba 100644 --- a/cranelift/isle/isle_examples/test2.isle +++ b/cranelift/isle/isle_examples/test2.isle @@ -10,7 +10,7 @@ (A2B (A.A1 _ (B.B1 x))) (B.B1 x)) -(rule 1 +(rule 0 (A2B (A.A1 (B.B1 x) _)) (B.B1 x)) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 65f3d29051..a4b56d5d0c 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -301,12 +301,21 @@ impl TrieNode { // Now find or insert the appropriate edge. let mut edge: Option = None; - for i in 0..edges.len() { - if edges[i].range.contains(prio) && edges[i].symbol == op { - edge = Some(i); - break; - } - if prio > edges[i].range.1 { + let mut last_edge_with_op: Option = None; + let mut last_edge_with_op_prio: Option = None; + for i in 0..(edges.len() + 1) { + if i == edges.len() || prio > edges[i].range.1 { + // We've passed all edges with overlapping priority + // ranges. Maybe the last edge we saw with the op + // we're inserting can have its range expanded, + // however. + if last_edge_with_op.is_some() && edges[last_edge_with_op.unwrap()].symbol == op { + // Move it to the end of the run of equal-unit-range ops. + edges.swap(last_edge_with_op.unwrap(), i - 1); + edge = Some(i - 1); + edges[i - 1].range.1 = prio; + break; + } edges.insert( i, TrieEdge { @@ -318,15 +327,25 @@ impl TrieNode { edge = Some(i); break; } + if i == edges.len() { + break; + } + if edges[i].symbol == op { + last_edge_with_op = Some(i); + last_edge_with_op_prio = Some(edges[i].range.1); + } + if last_edge_with_op_prio.is_some() + && last_edge_with_op_prio.unwrap() < edges[i].range.1 + { + last_edge_with_op = None; + last_edge_with_op_prio = None; + } + if edges[i].range.contains(prio) && edges[i].symbol == op { + edge = Some(i); + break; + } } - let edge = edge.unwrap_or_else(|| { - edges.push(TrieEdge { - range: PrioRange(prio, prio), - symbol: op.clone(), - node: TrieNode::Empty, - }); - edges.len() - 1 - }); + let edge = edge.expect("Must have found an edge at least at last iter"); let edge = &mut edges[edge]; if is_last { From 3ccbaf0f6949b5e644242dce4cba636e7438ea17 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sun, 5 Sep 2021 17:21:15 -0700 Subject: [PATCH 20/95] Generate match {} statements by merging adjacent MatchVariant trie edges. --- cranelift/isle/TODO | 8 +- cranelift/isle/isle_examples/test2.isle | 7 +- cranelift/isle/src/codegen.rs | 228 +++++++++++++++++++----- 3 files changed, 191 insertions(+), 52 deletions(-) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 8fd0e60ad2..d588cd0037 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -1,7 +1,3 @@ -- Convert series of if-lets from MatchVariants into a match { ... }. - - Should be pretty simple; just need to recognize adjacent edges in - priority-order that are all MatchVariants on the same input. - - Document semantics carefully, especially wrt extractors. - -- Verify that priorities work as expected. +- Build out an initial set of bindings for Cranelift LowerCtx with extractors + for instruction info. diff --git a/cranelift/isle/isle_examples/test2.isle b/cranelift/isle/isle_examples/test2.isle index 62e905a4ba..5c0977a702 100644 --- a/cranelift/isle/isle_examples/test2.isle +++ b/cranelift/isle/isle_examples/test2.isle @@ -2,7 +2,8 @@ (type A (enum (A1 (x B) (y B)))) (type B (enum - (B1 (x u32)))) + (B1 (x u32)) + (B2 (x u32)))) (decl A2B (A) B) @@ -14,6 +15,10 @@ (A2B (A.A1 (B.B1 x) _)) (B.B1 x)) +(rule 0 + (A2B (A.A1 (B.B2 x) _)) + (B.B1 x)) + (rule -1 (A2B (A.A1 _ _)) (B.B1 42)) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index a4b56d5d0c..12839d819d 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -2,7 +2,7 @@ use crate::error::Error; use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; -use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId}; +use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId, Variant}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; @@ -309,7 +309,7 @@ impl TrieNode { // ranges. Maybe the last edge we saw with the op // we're inserting can have its range expanded, // however. - if last_edge_with_op.is_some() && edges[last_edge_with_op.unwrap()].symbol == op { + if last_edge_with_op.is_some() { // Move it to the end of the run of equal-unit-range ops. edges.swap(last_edge_with_op.unwrap(), i - 1); edge = Some(i - 1); @@ -901,6 +901,38 @@ impl<'a> Codegen<'a> { Ok(()) } + fn match_variant_binders( + &self, + variant: &Variant, + arg_tys: &[TypeId], + id: InstId, + ctx: &mut BodyContext, + ) -> Vec { + arg_tys + .iter() + .zip(variant.fields.iter()) + .enumerate() + .map(|(i, (ty, field))| { + let value = Value::Pattern { + inst: id, + output: i, + }; + let valuename = self.value_name(&value); + let fieldname = &self.typeenv.syms[field.name.index()]; + match &self.typeenv.types[ty.index()] { + &Type::Primitive(..) => { + self.define_val(&value, ctx, /* is_ref = */ false); + format!("{}: {}", fieldname, valuename) + } + &Type::Enum { .. } => { + self.define_val(&value, ctx, /* is_ref = */ true); + format!("{}: ref {}", fieldname, valuename) + } + } + }) + .collect::>() + } + /// Returns a `bool` indicating whether this pattern inst is /// infallible. fn generate_pattern_inst( @@ -961,29 +993,7 @@ impl<'a> Codegen<'a> { let ty_name = self.type_name(input_ty, /* is_ref = */ Some("&")); let variant = &variants[variant.index()]; let variantname = &self.typeenv.syms[variant.name.index()]; - let args = arg_tys - .iter() - .zip(variant.fields.iter()) - .enumerate() - .map(|(i, (ty, field))| { - let value = Value::Pattern { - inst: id, - output: i, - }; - let valuename = self.value_name(&value); - let fieldname = &self.typeenv.syms[field.name.index()]; - match &self.typeenv.types[ty.index()] { - &Type::Primitive(..) => { - self.define_val(&value, ctx, /* is_ref = */ false); - format!("{}: {}", fieldname, valuename) - } - &Type::Enum { .. } => { - self.define_val(&value, ctx, /* is_ref = */ true); - format!("{}: ref {}", fieldname, valuename) - } - } - }) - .collect::>(); + let args = self.match_variant_binders(variant, &arg_tys[..], id, ctx); writeln!( code, "{}if let {}::{} {{ {} }} = {} {{", @@ -1097,34 +1107,162 @@ impl<'a> Codegen<'a> { &TrieNode::Decision { ref edges } => { let subindent = format!("{} ", indent); // if this is a decision node, generate each match op - // in turn (in priority order). - for &TrieEdge { - ref symbol, - ref node, - .. - } in edges - { - match symbol { - &TrieSymbol::EndOfMatch => { - returned = self.generate_body(code, depth + 1, node, indent, ctx)?; - } - &TrieSymbol::Match { ref op } => { - let id = InstId(depth); - let infallible = - self.generate_pattern_inst(code, id, op, indent, ctx)?; - let sub_returned = - self.generate_body(code, depth + 1, node, &subindent, ctx)?; - writeln!(code, "{}}}", indent)?; - if infallible && sub_returned { - returned = true; + // in turn (in priority order). Sort the ops within + // each priority, and gather together adjacent + // MatchVariant ops with the same input and disjoint + // variants in order to create a `match` rather than a + // chain of if-lets. + let mut edges = edges.clone(); + edges.sort_by(|e1, e2| (-e1.range.0, &e1.symbol).cmp(&(-e2.range.0, &e2.symbol))); + + let mut i = 0; + while i < edges.len() { + let mut last = i; + let mut adjacent_variants = HashSet::new(); + let mut adjacent_variant_input = None; + log::trace!("edge: {:?}", edges[i]); + while last < edges.len() { + match &edges[last].symbol { + &TrieSymbol::Match { + op: PatternInst::MatchVariant { input, variant, .. }, + } => { + if adjacent_variant_input.is_none() { + adjacent_variant_input = Some(input); + } + if adjacent_variant_input == Some(input) + && !adjacent_variants.contains(&variant) + { + adjacent_variants.insert(variant); + last += 1; + } else { + break; + } + } + _ => { break; } } } + + // edges[i..last] is a run of adjacent + // MatchVariants (possibly an empty one). Only use + // a `match` form if there are at least two + // adjacent options. + if last - i > 1 { + self.generate_body_matches(code, depth, &edges[i..last], indent, ctx)?; + i = last; + continue; + } else { + let &TrieEdge { + ref symbol, + ref node, + .. + } = &edges[i]; + i += 1; + + match symbol { + &TrieSymbol::EndOfMatch => { + returned = + self.generate_body(code, depth + 1, node, indent, ctx)?; + } + &TrieSymbol::Match { ref op } => { + let id = InstId(depth); + let infallible = + self.generate_pattern_inst(code, id, op, indent, ctx)?; + let sub_returned = + self.generate_body(code, depth + 1, node, &subindent, ctx)?; + writeln!(code, "{}}}", indent)?; + if infallible && sub_returned { + returned = true; + break; + } + } + } + } } } } Ok(returned) } + + fn generate_body_matches( + &self, + code: &mut dyn Write, + depth: usize, + edges: &[TrieEdge], + indent: &str, + ctx: &mut BodyContext, + ) -> Result<(), Error> { + let (input, input_ty) = match &edges[0].symbol { + &TrieSymbol::Match { + op: + PatternInst::MatchVariant { + input, input_ty, .. + }, + } => (input, input_ty), + _ => unreachable!(), + }; + let (input_ty_sym, variants) = match &self.typeenv.types[input_ty.index()] { + &Type::Enum { + ref name, + ref variants, + .. + } => (name, variants), + _ => unreachable!(), + }; + let input_ty_name = &self.typeenv.syms[input_ty_sym.index()]; + + // Emit the `match`. + writeln!( + code, + "{}match {} {{", + indent, + self.value_by_ref(&input, ctx) + )?; + + // Emit each case. + for &TrieEdge { + ref symbol, + ref node, + .. + } in edges + { + let id = InstId(depth); + let (variant, arg_tys) = match symbol { + &TrieSymbol::Match { + op: + PatternInst::MatchVariant { + variant, + ref arg_tys, + .. + }, + } => (variant, arg_tys), + _ => unreachable!(), + }; + + let variantinfo = &variants[variant.index()]; + let variantname = &self.typeenv.syms[variantinfo.name.index()]; + let fields = self.match_variant_binders(variantinfo, arg_tys, id, ctx); + writeln!( + code, + "{} &{}::{} {{ {} }} => {{", + indent, + input_ty_name, + variantname, + fields.join(", ") + )?; + let subindent = format!("{} ", indent); + self.generate_body(code, depth + 1, node, &subindent, ctx)?; + writeln!(code, "{} }}", indent)?; + } + + // Always add a catchall, because we don't do exhaustiveness + // checking on the MatcHVariants. + writeln!(code, "{} _ => {{}}", indent)?; + + writeln!(code, "{}}}", indent)?; + + Ok(()) + } } From d725ac13b2d1c51fbe234e223ba1f8bfa5c04da7 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sun, 5 Sep 2021 17:53:55 -0700 Subject: [PATCH 21/95] Codegen: parameterize on a generated Context trait that contains external ctors/etors. --- cranelift/isle/isle_examples/test_main.rs | 14 ++- cranelift/isle/src/codegen.rs | 107 ++++++++++++++++++++-- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/cranelift/isle/isle_examples/test_main.rs b/cranelift/isle/isle_examples/test_main.rs index 92cfdf2c0b..5bec55a798 100644 --- a/cranelift/isle/isle_examples/test_main.rs +++ b/cranelift/isle/isle_examples/test_main.rs @@ -1,8 +1,12 @@ +mod test; -pub fn get_input(ctx: &mut C, x: u32) -> Option<(test::A,)> { - None +struct Context; +impl test::Context for Context { + fn get_input(&mut self, x: u32) -> Option<(test::A,)> { + Some((test::A::A1 { x: x + 1 },)) + } } -fn main() {} - -mod test; +fn main() { + test::constructor_Lower(&mut Context, &test::A::A1 { x: 42 }); +} diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 12839d819d..4c7fc04a93 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -559,6 +559,7 @@ impl<'a> Codegen<'a> { let mut code = String::new(); self.generate_header(&mut code)?; + self.generate_ctx_trait(&mut code)?; self.generate_internal_types(&mut code)?; self.generate_internal_term_constructors(&mut code)?; self.generate_internal_term_extractors(&mut code)?; @@ -576,11 +577,105 @@ impl<'a> Codegen<'a> { for file in &self.typeenv.filenames { writeln!(code, "// - {}", file)?; } + writeln!( code, - "\nuse super::*; // Pulls in all external types and ctors/etors" + "\n#![allow(dead_code, unreachable_code, unused_imports, unused_variables, non_snake_case)]" )?; + writeln!(code, "\nuse super::*; // Pulls in all external types.")?; + + Ok(()) + } + + fn generate_ctx_trait(&self, code: &mut dyn Write) -> Result<(), Error> { + writeln!(code, "")?; + writeln!( + code, + "/// Context during lowering: an implementation of this trait" + )?; + writeln!( + code, + "/// must be provided with all external constructors and extractors." + )?; + writeln!( + code, + "/// A mutable borrow is passed along through all lowering logic." + )?; + writeln!(code, "pub trait Context {{")?; + for term in &self.termenv.terms { + if let &TermKind::Regular { + extractor, + constructor, + .. + } = &term.kind + { + if let Some((etor_name, infallible)) = extractor { + let etor_name = &self.typeenv.syms[etor_name.index()]; + let arg_is_prim = match &self.typeenv.types[term.ret_ty.index()] { + &Type::Primitive(..) => true, + _ => false, + }; + let arg = format!( + "arg0: {}", + self.type_name( + term.ret_ty, + /* by_ref = */ if arg_is_prim { None } else { Some("&") } + ), + ); + let ret_tuple_tys = term + .arg_tys + .iter() + .map(|ty| { + self.type_name(*ty, /* by_ref = */ None) + }) + .collect::>(); + if infallible { + writeln!( + code, + " fn {}(&mut self, {}) -> ({},);", + etor_name, + arg, + ret_tuple_tys.join(", ") + )?; + } else { + writeln!( + code, + " fn {}(&mut self, {}) -> Option<({},)>;", + etor_name, + arg, + ret_tuple_tys.join(", ") + )?; + } + } + + if let Some(ctor_name) = constructor { + let ctor_name = &self.typeenv.syms[ctor_name.index()]; + let args = term + .arg_tys + .iter() + .enumerate() + .map(|(i, &arg_ty)| { + format!( + "arg{}: {}", + i, + self.type_name(arg_ty, /* by_ref = */ Some("&")) + ) + }) + .collect::>(); + let ret = self.type_name(term.ret_ty, /* by_ref = */ None); + writeln!( + code, + "fn {}(&mut self, {}) -> Option<{}>;", + ctor_name, + args.join(", "), + ret, + )?; + } + } + } + writeln!(code, "}}")?; + Ok(()) } @@ -628,7 +723,7 @@ impl<'a> Codegen<'a> { &TermKind::Regular { constructor: Some(sym), .. - } => self.typeenv.syms[sym.index()].clone(), + } => format!("C::{}", self.typeenv.syms[sym.index()]), &TermKind::Regular { constructor: None, .. } => { @@ -644,7 +739,7 @@ impl<'a> Codegen<'a> { &TermKind::Regular { extractor: Some((sym, infallible)), .. - } => (self.typeenv.syms[sym.index()].clone(), infallible), + } => (format!("C::{}", self.typeenv.syms[sym.index()]), infallible), &TermKind::Regular { extractor: None, .. } => ( @@ -729,7 +824,7 @@ impl<'a> Codegen<'a> { )?; writeln!( code, - "fn {}(ctx: &mut C, {}) -> Option<{}> {{", + "pub fn {}(ctx: &mut C, {}) -> Option<{}> {{", func_name, args.join(", "), self.type_name(termdata.ret_ty, /* by_ref = */ None) @@ -788,7 +883,7 @@ impl<'a> Codegen<'a> { )?; writeln!( code, - "fn {}(ctx: &mut C, {}) -> Option<({},)> {{", + "pub fn {}(ctx: &mut C, {}) -> Option<({},)> {{", func_name, arg, ret_tuple_tys.join(", "), @@ -1114,7 +1209,7 @@ impl<'a> Codegen<'a> { // chain of if-lets. let mut edges = edges.clone(); edges.sort_by(|e1, e2| (-e1.range.0, &e1.symbol).cmp(&(-e2.range.0, &e2.symbol))); - + let mut i = 0; while i < edges.len() { let mut last = i; From 602b8308ce72898ca6ee86c7af639a1d08a6ec04 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 7 Sep 2021 00:27:45 -0700 Subject: [PATCH 22/95] More work on sketch for isel and some TODO items derived from it. --- cranelift/isle/TODO | 3 + cranelift/isle/isle_examples/test3.isle | 57 +++++++++++++++++ cranelift/isle/src/codegen.rs | 83 ++++++++++++++++--------- cranelift/isle/src/ir.rs | 33 +++++++++- cranelift/isle/src/parser.rs | 22 ++++--- 5 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 cranelift/isle/isle_examples/test3.isle diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index d588cd0037..fc799f256c 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -1,3 +1,6 @@ +- `and` combinator in input. +- inputs to external extractors? "polarity" of args? +- "extractor macros" rather than full rule reversal? (rule ...) and (pattern ...)? - Document semantics carefully, especially wrt extractors. - Build out an initial set of bindings for Cranelift LowerCtx with extractors for instruction info. diff --git a/cranelift/isle/isle_examples/test3.isle b/cranelift/isle/isle_examples/test3.isle new file mode 100644 index 0000000000..99d537b0e2 --- /dev/null +++ b/cranelift/isle/isle_examples/test3.isle @@ -0,0 +1,57 @@ +(type Opcode extern (enum + Iadd + Isub + Load + Store)) + +(type Inst (primitive Inst)) +(type Reg (primitive Reg)) +(type u32 (primitive u32)) + +(decl Op (Opcode) Inst) +(extractor Op get_opcode) + +(decl InputToReg (Inst u32) Reg) +(constructor InputToReg put_input_in_reg) + +(type MachInst (enum + (Add (a Reg) (b Reg)) + (Sub (a Reg) (b Reg)))) + +(decl Lower (Inst) MachInst) + +;; These can be made nicer by defining some extractors -- see below. +(rule + (Lower inst @ (Op (Opcode.Iadd))) + (MachInst.Add (InputToReg inst 0) (InputToReg inst 1))) +(rule + (Lower inst @ (Op (Opcode.Isub))) + (MachInst.Sub (InputToReg inst 0) (InputToReg inst 1))) + +;; Extractors that give syntax sugar for (Iadd ra rb), etc. +;; +;; Note that this is somewhat simplistic: it directly connects inputs to +;; MachInst regs; really we'd want to return a VReg or InstInput that we can use +;; another extractor to connect to another (producer) inst. +;; +;; Also, note that while it looks a little indirect, a verification effort could +;; define equivalences across the `rule` LHS/RHS pairs, and the types ensure that +;; we are dealing (at the semantic level) with pure value equivalences of +;; "terms", not arbitrary side-effecting calls. + +(decl Iadd (Reg Reg) Inst) +(decl Isub (Reg Reg) Inst) +(rule + inst @ (Op Opcode.Iadd) + (Iadd (InputToReg inst 0) (InputToReg inst 1))) +(rule + inst @ (Op Opcode.Isub) + (Isub (InputToReg inst 0) (InputToReg inst 1))) + +;; Now the nice syntax-sugar that "end-user" backend authors can write: +(rule + (Lower (Iadd ra rb)) + (MachInst.Add ra rb)) +(rule + (Lower (Isub ra rb)) + (MachInst.Sub ra rb)) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 4c7fc04a93..77a8ba0895 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -666,7 +666,7 @@ impl<'a> Codegen<'a> { let ret = self.type_name(term.ret_ty, /* by_ref = */ None); writeln!( code, - "fn {}(&mut self, {}) -> Option<{}>;", + " fn {}(&mut self, {}) -> Option<{}>;", ctor_name, args.join(", "), ret, @@ -700,13 +700,18 @@ impl<'a> Codegen<'a> { writeln!(code, "pub enum {} {{", name)?; for variant in variants { let name = &self.typeenv.syms[variant.name.index()]; - writeln!(code, " {} {{", name)?; - for field in &variant.fields { - let name = &self.typeenv.syms[field.name.index()]; - let ty_name = self.typeenv.types[field.ty.index()].name(&self.typeenv); - writeln!(code, " {}: {},", name, ty_name)?; + if variant.fields.is_empty() { + writeln!(code, " {},", name)?; + } else { + writeln!(code, " {} {{", name)?; + for field in &variant.fields { + let name = &self.typeenv.syms[field.name.index()]; + let ty_name = + self.typeenv.types[field.ty.index()].name(&self.typeenv); + writeln!(code, " {}: {},", name, ty_name)?; + } + writeln!(code, " }},")?; } - writeln!(code, " }},")?; } writeln!(code, "}}")?; } @@ -796,10 +801,15 @@ impl<'a> Codegen<'a> { for (&termid, trie) in &self.functions_by_input { let termdata = &self.termenv.terms[termid.index()]; - // Skip terms that are enum variants or that have external constructors. + // Skip terms that are enum variants or that have external + // constructors/extractors. match &termdata.kind { &TermKind::EnumVariant { .. } => continue, - &TermKind::Regular { constructor, .. } if constructor.is_some() => continue, + &TermKind::Regular { + constructor, + extractor, + .. + } if constructor.is_some() || extractor.is_some() => continue, _ => {} } @@ -851,7 +861,11 @@ impl<'a> Codegen<'a> { // Skip terms that are enum variants or that have external extractors. match &termdata.kind { &TermKind::EnumVariant { .. } => continue, - &TermKind::Regular { extractor, .. } if extractor.is_some() => continue, + &TermKind::Regular { + constructor, + extractor, + .. + } if constructor.is_some() || extractor.is_some() => continue, _ => {} } @@ -949,15 +963,23 @@ impl<'a> Codegen<'a> { self.type_name(ty, None), self.typeenv.syms[variantinfo.name.index()] ); - writeln!( - code, - "{}let {} = {} {{", - indent, outputname, full_variant_name - )?; - for input_field in input_fields { - writeln!(code, "{} {},", indent, input_field)?; + if input_fields.is_empty() { + writeln!( + code, + "{}let {} = {};", + indent, outputname, full_variant_name + )?; + } else { + writeln!( + code, + "{}let {} = {} {{", + indent, outputname, full_variant_name + )?; + for input_field in input_fields { + writeln!(code, "{} {},", indent, input_field)?; + } + writeln!(code, "{}}};", indent)?; } - writeln!(code, "{}}};", indent)?; self.define_val(&output, ctx, /* is_ref = */ false); } &ExprInst::Construct { @@ -1089,14 +1111,15 @@ impl<'a> Codegen<'a> { let variant = &variants[variant.index()]; let variantname = &self.typeenv.syms[variant.name.index()]; let args = self.match_variant_binders(variant, &arg_tys[..], id, ctx); + let args = if args.is_empty() { + "".to_string() + } else { + format!("{{ {} }}", args.join(", ")) + }; writeln!( code, - "{}if let {}::{} {{ {} }} = {} {{", - indent, - ty_name, - variantname, - args.join(", "), - input + "{}if let {}::{} {} = {} {{", + indent, ty_name, variantname, args, input )?; Ok(false) } @@ -1339,13 +1362,15 @@ impl<'a> Codegen<'a> { let variantinfo = &variants[variant.index()]; let variantname = &self.typeenv.syms[variantinfo.name.index()]; let fields = self.match_variant_binders(variantinfo, arg_tys, id, ctx); + let fields = if fields.is_empty() { + "".to_string() + } else { + format!("{{ {} }}", fields.join(", ")) + }; writeln!( code, - "{} &{}::{} {{ {} }} => {{", - indent, - input_ty_name, - variantname, - fields.join(", ") + "{} &{}::{} {} => {{", + indent, input_ty_name, variantname, fields, )?; let subindent = format!("{} ", indent); self.generate_body(code, depth + 1, node, &subindent, ctx)?; diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 4250f32320..9f7a7210b8 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -324,13 +324,18 @@ impl ExprSequence { vars: &HashMap, Value)>, gen_final_construct: bool, ) -> (Option, Vec) { + log::trace!( + "gen_expr: expr {:?} gen_final_construct {}", + expr, + gen_final_construct + ); match expr { &Expr::ConstInt(ty, val) => (None, vec![self.add_const_int(ty, val)]), &Expr::Let(_ty, ref bindings, ref subexpr) => { let mut vars = vars.clone(); for &(var, _var_ty, ref var_expr) in bindings { let (var_value_term, var_value) = - self.gen_expr(typeenv, termenv, &*var_expr, &vars, false); + self.gen_expr(typeenv, termenv, &*var_expr, &vars, true); let var_value = var_value[0]; vars.insert(var, (var_value_term, var_value)); } @@ -343,9 +348,11 @@ impl ExprSequence { &Expr::Term(ty, term, ref arg_exprs) => { let termdata = &termenv.terms[term.index()]; let mut arg_values_tys = vec![]; + log::trace!("Term gen_expr term {}", term.index()); for (arg_ty, arg_expr) in termdata.arg_tys.iter().cloned().zip(arg_exprs.iter()) { + log::trace!("generating for arg_expr {:?}", arg_expr); arg_values_tys.push(( - self.gen_expr(typeenv, termenv, &*arg_expr, &vars, false).1[0], + self.gen_expr(typeenv, termenv, &*arg_expr, &vars, true).1[0], arg_ty, )); } @@ -383,7 +390,21 @@ pub fn lower_rule( let ruledata = &termenv.rules[rule.index()]; let mut vars = HashMap::new(); + log::trace!( + "lower_rule: ruledata {:?} forward {}", + ruledata, + is_forward_dir + ); + if is_forward_dir { + let can_do_forward = match &ruledata.lhs { + &Pattern::Term(..) => true, + _ => false, + }; + if !can_do_forward { + return None; + } + let lhs_root_term = pattern_seq.gen_pattern(None, tyenv, termenv, &ruledata.lhs, &mut vars); let root_term = match lhs_root_term { Some(t) => t, @@ -407,6 +428,14 @@ pub fn lower_rule( expr_seq.add_return(output_ty, rhs_root_vals[0]); Some((pattern_seq, expr_seq, root_term)) } else { + let can_reverse = match &ruledata.rhs { + &Expr::Term(..) => true, + _ => false, + }; + if !can_reverse { + return None; + } + let arg = pattern_seq.add_arg(0, ruledata.lhs.ty()); let _ = pattern_seq.gen_pattern(Some(arg), tyenv, termenv, &ruledata.lhs, &mut vars); let (rhs_root_term, rhs_root_vals) = expr_seq.gen_expr( diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index 8305e72d44..b02f833068 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -184,14 +184,22 @@ impl<'a> Parser<'a> { } fn parse_type_variant(&mut self) -> ParseResult { - self.lparen()?; - let name = self.parse_ident()?; - let mut fields = vec![]; - while !self.is_rparen() { - fields.push(self.parse_type_field()?); + if self.is_sym() { + let name = self.parse_ident()?; + Ok(Variant { + name, + fields: vec![], + }) + } else { + self.lparen()?; + let name = self.parse_ident()?; + let mut fields = vec![]; + while !self.is_rparen() { + fields.push(self.parse_type_field()?); + } + self.rparen()?; + Ok(Variant { name, fields }) } - self.rparen()?; - Ok(Variant { name, fields }) } fn parse_type_field(&mut self) -> ParseResult { From 1ceef04680cf3f3baceaab6a61e735792758f838 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 7 Sep 2021 00:47:03 -0700 Subject: [PATCH 23/95] (and ...) combinator in patterns --- cranelift/isle/TODO | 1 - cranelift/isle/isle_examples/test4.isle | 17 +++++++++++++++++ cranelift/isle/src/ast.rs | 2 ++ cranelift/isle/src/ir.rs | 7 +++++++ cranelift/isle/src/parser.rs | 22 ++++++++++++++++------ cranelift/isle/src/sema.rs | 19 +++++++++++++++++++ 6 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 cranelift/isle/isle_examples/test4.isle diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index fc799f256c..86bf62c88a 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -1,4 +1,3 @@ -- `and` combinator in input. - inputs to external extractors? "polarity" of args? - "extractor macros" rather than full rule reversal? (rule ...) and (pattern ...)? - Document semantics carefully, especially wrt extractors. diff --git a/cranelift/isle/isle_examples/test4.isle b/cranelift/isle/isle_examples/test4.isle new file mode 100644 index 0000000000..085d2bee3b --- /dev/null +++ b/cranelift/isle/isle_examples/test4.isle @@ -0,0 +1,17 @@ +(type u32 (primitive u32)) +(type A (enum (A1 (x u32)))) + +(decl Ext1 (u32) A) +(decl Ext2 (u32) A) +(extractor Ext1 ext1) +(extractor Ext2 ext2) + +(decl Lower (A) A) + +(rule + (Lower + (and + a + (Ext1 x) + (Ext2 =x))) + a) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index b0c06b68ae..e216ca9b82 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -83,6 +83,8 @@ pub enum Pattern { Term { sym: Ident, args: Vec }, /// An operator that matches anything. Wildcard, + /// N sub-patterns that must all match. + And { subpats: Vec }, } /// An expression: the right-hand side of a rule. diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 9f7a7210b8..7951c1175e 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -253,6 +253,13 @@ impl PatternSequence { } } } + &Pattern::And(_ty, ref children) => { + let input = input.unwrap(); + for child in children { + self.gen_pattern(Some(input), typeenv, termenv, child, vars); + } + None + } &Pattern::Wildcard(_ty) => { // Nothing! None diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index b02f833068..e5265adc76 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -305,13 +305,23 @@ impl<'a> Parser<'a> { } } else if self.is_lparen() { self.lparen()?; - let sym = self.parse_ident()?; - let mut args = vec![]; - while !self.is_rparen() { - args.push(self.parse_pattern()?); + if self.is_sym_str("and") { + self.symbol()?; + let mut subpats = vec![]; + while !self.is_rparen() { + subpats.push(self.parse_pattern()?); + } + self.rparen()?; + Ok(Pattern::And { subpats }) + } else { + let sym = self.parse_ident()?; + let mut args = vec![]; + while !self.is_rparen() { + args.push(self.parse_pattern()?); + } + self.rparen()?; + Ok(Pattern::Term { sym, args }) } - self.rparen()?; - Ok(Pattern::Term { sym, args }) } else { Err(self.error(pos.unwrap(), "Unexpected pattern".into())) } diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index 40789ded0d..f3d9049180 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -123,6 +123,7 @@ pub enum Pattern { ConstInt(TypeId, i64), Term(TypeId, TermId, Vec), Wildcard(TypeId), + And(TypeId, Vec), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -141,6 +142,7 @@ impl Pattern { &Self::ConstInt(t, ..) => t, &Self::Term(t, ..) => t, &Self::Wildcard(t, ..) => t, + &Self::And(t, ..) => t, } } } @@ -293,11 +295,13 @@ impl TypeEnv { } } +#[derive(Clone)] struct Bindings { next_var: usize, vars: Vec, } +#[derive(Clone)] struct BoundVar { name: Sym, id: VarId, @@ -548,6 +552,21 @@ impl TermEnv { })?; Ok((Pattern::Wildcard(ty), ty)) } + &ast::Pattern::And { ref subpats } => { + let mut expected_ty = expected_ty; + let mut children = vec![]; + for subpat in subpats { + let (subpat, ty) = + self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; + expected_ty = expected_ty.or(Some(ty)); + children.push(subpat); + } + if expected_ty.is_none() { + return Err(tyenv.error(pos, "No type for (and ...) form.".to_string())); + } + let ty = expected_ty.unwrap(); + Ok((Pattern::And(ty, children), ty)) + } &ast::Pattern::BindPattern { ref var, ref subpat, From 6daa55af8216b5469a8e03adcec438dd81ca27bd Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 00:48:36 -0700 Subject: [PATCH 24/95] Initial draft of DSL semantics complete. This latest refactor adds "extractor macros" in place of the very-confusing-even-to-the-DSL-author reverse-rules-as-extractors concept. It was beautifully symmetric but also just too mind-bending to be practical. It also adds argument polarity to external extractors. This is inspired by Prolog's similar notion (see e.g. the "+x" vs. "-x" argument notation in library documentation) where the unification-based semantics allow for bidirectional flow through arguments. We don't want polymorphism or dynamism w.r.t. directions/polarities here; the polarities are static; but it is useful to be able to feed values *into* an extractor (aside from the one value being extracted). Semantically this still correlates to a term-rewriting/value-equivalence world since we can still translate all of this to a list of equality constraints. To make that work, this change also adds expressions into patterns, specifically only for extractor "input" args. This required quite a bit of internal refactoring but is only a small addition to the language semantics. I plan to build out the little instruction-selector sketch further but the one that is here (in `test3.isle`) is starting to get interesting already with the current DSL semantics. --- cranelift/isle/TODO | 7 +- cranelift/isle/isle_examples/test.isle | 10 +- cranelift/isle/isle_examples/test3.isle | 55 +-- cranelift/isle/isle_examples/test4.isle | 4 +- cranelift/isle/src/ast.rs | 61 ++- cranelift/isle/src/codegen.rs | 491 ++++++++---------------- cranelift/isle/src/ir.rs | 411 ++++++++++---------- cranelift/isle/src/lexer.rs | 14 +- cranelift/isle/src/parser.rs | 111 ++++-- cranelift/isle/src/sema.rs | 478 ++++++++++++++++++----- 10 files changed, 970 insertions(+), 672 deletions(-) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 86bf62c88a..b6e0acff40 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -1,5 +1,8 @@ -- inputs to external extractors? "polarity" of args? -- "extractor macros" rather than full rule reversal? (rule ...) and (pattern ...)? +- Optimizations + - Infallible patterns; optimize away control flow when possible. + - Don't do the closure-wrapping thing for expressions inside of patterns. + - Document semantics carefully, especially wrt extractors. + - Build out an initial set of bindings for Cranelift LowerCtx with extractors for instruction info. diff --git a/cranelift/isle/isle_examples/test.isle b/cranelift/isle/isle_examples/test.isle index 39125a94d1..09d5ea92af 100644 --- a/cranelift/isle/isle_examples/test.isle +++ b/cranelift/isle/isle_examples/test.isle @@ -3,7 +3,7 @@ (type B (enum (B1 (x u32)) (B2 (x u32)))) (decl Input (A) u32) -(extractor Input get_input) ;; fn get_input(ctx: &mut C, ret: u32) -> Option<(A,)> +(extern extractor Input get_input) ;; fn get_input(ctx: &mut C, ret: u32) -> Option<(A,)> (decl Lower (A) B) @@ -12,10 +12,10 @@ (B.B2 sub)) (decl Extractor (B) A) -(rule - (A.A2 x) - (Extractor (B.B1 x))) +(extractor + (Extractor x) + (A.A2 x)) (rule (Lower (Extractor b)) - b) + (B.B1 b)) diff --git a/cranelift/isle/isle_examples/test3.isle b/cranelift/isle/isle_examples/test3.isle index 99d537b0e2..df4c7337cd 100644 --- a/cranelift/isle/isle_examples/test3.isle +++ b/cranelift/isle/isle_examples/test3.isle @@ -5,29 +5,29 @@ Store)) (type Inst (primitive Inst)) +(type InstInput (primitive InstInput)) (type Reg (primitive Reg)) (type u32 (primitive u32)) (decl Op (Opcode) Inst) -(extractor Op get_opcode) +(extern extractor Op get_opcode) -(decl InputToReg (Inst u32) Reg) -(constructor InputToReg put_input_in_reg) +(decl InstInput (InstInput u32) Inst) +(extern extractor InstInput get_inst_input (out in)) + +(decl Producer (Inst) InstInput) +(extern extractor Producer get_input_producer) + +(decl UseInput (InstInput) Reg) +(extern constructor UseInput put_input_in_reg) (type MachInst (enum (Add (a Reg) (b Reg)) + (Add3 (a Reg) (b Reg) (c Reg)) (Sub (a Reg) (b Reg)))) (decl Lower (Inst) MachInst) -;; These can be made nicer by defining some extractors -- see below. -(rule - (Lower inst @ (Op (Opcode.Iadd))) - (MachInst.Add (InputToReg inst 0) (InputToReg inst 1))) -(rule - (Lower inst @ (Op (Opcode.Isub))) - (MachInst.Sub (InputToReg inst 0) (InputToReg inst 1))) - ;; Extractors that give syntax sugar for (Iadd ra rb), etc. ;; ;; Note that this is somewhat simplistic: it directly connects inputs to @@ -39,19 +39,28 @@ ;; we are dealing (at the semantic level) with pure value equivalences of ;; "terms", not arbitrary side-effecting calls. -(decl Iadd (Reg Reg) Inst) -(decl Isub (Reg Reg) Inst) -(rule - inst @ (Op Opcode.Iadd) - (Iadd (InputToReg inst 0) (InputToReg inst 1))) -(rule - inst @ (Op Opcode.Isub) - (Isub (InputToReg inst 0) (InputToReg inst 1))) +(decl Iadd (InstInput InstInput) Inst) +(decl Isub (InstInput InstInput) Inst) +(extractor + (Iadd a b) + (and + (Op (Opcode.Iadd)) + (InstInput a <0) + (InstInput b <1))) +(extractor + (Isub a b) + (and + (Op (Opcode.Isub)) + (InstInput a <0) + (InstInput b <1))) ;; Now the nice syntax-sugar that "end-user" backend authors can write: (rule - (Lower (Iadd ra rb)) - (MachInst.Add ra rb)) + (Lower (Iadd ra rb)) + (MachInst.Add (UseInput ra) (UseInput rb))) (rule - (Lower (Isub ra rb)) - (MachInst.Sub ra rb)) + (Lower (Iadd (Producer (Iadd ra rb)) rc)) + (MachInst.Add3 (UseInput ra) (UseInput rb) (UseInput rc))) +(rule + (Lower (Isub ra rb)) + (MachInst.Sub (UseInput ra) (UseInput rb))) \ No newline at end of file diff --git a/cranelift/isle/isle_examples/test4.isle b/cranelift/isle/isle_examples/test4.isle index 085d2bee3b..a899122bbb 100644 --- a/cranelift/isle/isle_examples/test4.isle +++ b/cranelift/isle/isle_examples/test4.isle @@ -3,8 +3,8 @@ (decl Ext1 (u32) A) (decl Ext2 (u32) A) -(extractor Ext1 ext1) -(extractor Ext2 ext2) +(extern extractor Ext1 ext1) +(extern extractor Ext2 ext2) (decl Lower (A) A) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index e216ca9b82..8742aaa166 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -12,6 +12,7 @@ pub struct Defs { pub enum Def { Type(Type), Rule(Rule), + Extractor(Extractor), Decl(Decl), Extern(Extern), } @@ -69,6 +70,16 @@ pub struct Rule { pub prio: Option, } +/// An extractor macro: (A x y) becomes (B x _ y ...). Expanded during +/// ast-to-sema pass. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Extractor { + pub term: Ident, + pub args: Vec, + pub template: Pattern, + pub pos: Pos, +} + /// A pattern: the left-hand side of a rule. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Pattern { @@ -80,13 +91,40 @@ pub enum Pattern { /// An operator that matches a constant integer value. ConstInt { val: i64 }, /// An application of a type variant or term. - Term { sym: Ident, args: Vec }, + Term { + sym: Ident, + args: Vec, + }, /// An operator that matches anything. Wildcard, /// N sub-patterns that must all match. And { subpats: Vec }, } +impl Pattern { + pub fn root_term(&self) -> Option<&Ident> { + match self { + &Pattern::BindPattern { ref subpat, .. } => subpat.root_term(), + &Pattern::Term { ref sym, .. } => Some(sym), + _ => None, + } + } +} + +/// A pattern in a term argument. Adds "evaluated expression" to kinds +/// of patterns in addition to all options in `Pattern`. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TermArgPattern { + /// A regular pattern that must match the existing value in the term's argument. + Pattern(Pattern), + /// An expression that is evaluated during the match phase and can + /// be given into an extractor. This is essentially a limited form + /// of unification or bidirectional argument flow (a la Prolog): + /// we can pass an arg *into* an extractor rather than getting the + /// arg *out of* it. + Expr(Expr), +} + /// An expression: the right-hand side of a rule. /// /// Note that this *almost* looks like a core Lisp or lambda calculus, @@ -124,8 +162,15 @@ pub enum Extern { func: Ident, /// The position of this decl. pos: Pos, - /// Whether this extractor is infallible (always matches). - infallible: bool, + /// Poliarity of args: whether values are inputs or outputs to + /// the external extractor function. This is a sort of + /// statically-defined approximation to Prolog-style + /// unification; we allow for the same flexible directionality + /// but fix it at DSL-definition time. By default, every arg + /// is an *output* from the extractor (and the 'retval", or + /// more precisely the term value that we are extracting, is + /// an "input"). + arg_polarity: Option>, }, /// An external constructor: `(constructor Term rustfunc)` form. Constructor { @@ -137,3 +182,13 @@ pub enum Extern { pos: Pos, }, } + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ArgPolarity { + /// An arg that must be given an Expr in the pattern and passes + /// data *to* the extractor op. + Input, + /// An arg that must be given a regular pattern (not Expr) and + /// receives data *from* the extractor op. + Output, +} diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 77a8ba0895..3e29005926 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -1,8 +1,8 @@ //! Generate Rust code from a series of Sequences. -use crate::error::Error; use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; -use crate::sema::{RuleId, TermEnv, TermId, TermKind, Type, TypeEnv, TypeId, Variant}; +use crate::sema::{RuleId, TermEnv, TermId, Type, TypeEnv, TypeId, Variant}; +use crate::{error::Error, sema::ExternalSig}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; @@ -452,8 +452,7 @@ impl TermFunctionBuilder { struct TermFunctionsBuilder<'a> { typeenv: &'a TypeEnv, termenv: &'a TermEnv, - builders_by_input: HashMap, - builders_by_output: HashMap, + builders_by_term: HashMap, } impl<'a> TermFunctionsBuilder<'a> { @@ -461,8 +460,7 @@ impl<'a> TermFunctionsBuilder<'a> { log::trace!("typeenv: {:?}", typeenv); log::trace!("termenv: {:?}", termenv); Self { - builders_by_input: HashMap::new(), - builders_by_output: HashMap::new(), + builders_by_term: HashMap::new(), typeenv, termenv, } @@ -473,56 +471,29 @@ impl<'a> TermFunctionsBuilder<'a> { let rule = RuleId(rule); let prio = self.termenv.rules[rule.index()].prio.unwrap_or(0); - if let Some((pattern, expr, lhs_root)) = lower_rule( - self.typeenv, - self.termenv, - rule, - /* forward_dir = */ true, - ) { - log::trace!( - "build:\n- rule {:?}\n- fwd pattern {:?}\n- fwd expr {:?}", - self.termenv.rules[rule.index()], - pattern, - expr - ); - self.builders_by_input - .entry(lhs_root) - .or_insert_with(|| TermFunctionBuilder::new(lhs_root)) - .add_rule(prio, pattern.clone(), expr.clone()); - } + let (pattern, expr) = lower_rule(self.typeenv, self.termenv, rule); + let root_term = self.termenv.rules[rule.index()].lhs.root_term().unwrap(); - if let Some((pattern, expr, rhs_root)) = lower_rule( - self.typeenv, - self.termenv, - rule, - /* forward_dir = */ false, - ) { - log::trace!( - "build:\n- rule {:?}\n- rev pattern {:?}\n- rev expr {:?}", - self.termenv.rules[rule.index()], - pattern, - expr - ); - self.builders_by_output - .entry(rhs_root) - .or_insert_with(|| TermFunctionBuilder::new(rhs_root)) - .add_rule(prio, pattern, expr); - } + log::trace!( + "build:\n- rule {:?}\n- pattern {:?}\n- expr {:?}", + self.termenv.rules[rule.index()], + pattern, + expr + ); + self.builders_by_term + .entry(root_term) + .or_insert_with(|| TermFunctionBuilder::new(root_term)) + .add_rule(prio, pattern.clone(), expr.clone()); } } - fn finalize(self) -> (HashMap, HashMap) { - let functions_by_input = self - .builders_by_input + fn finalize(self) -> HashMap { + let functions_by_term = self + .builders_by_term .into_iter() .map(|(term, builder)| (term, builder.trie)) .collect::>(); - let functions_by_output = self - .builders_by_output - .into_iter() - .map(|(term, builder)| (term, builder.trie)) - .collect::>(); - (functions_by_input, functions_by_output) + functions_by_term } } @@ -530,15 +501,13 @@ impl<'a> TermFunctionsBuilder<'a> { pub struct Codegen<'a> { typeenv: &'a TypeEnv, termenv: &'a TermEnv, - functions_by_input: HashMap, - functions_by_output: HashMap, + functions_by_term: HashMap, } #[derive(Clone, Debug, Default)] struct BodyContext { - borrowed_values: HashSet, - expected_return_vals: usize, - tuple_return: bool, + /// For each value: (is_ref, ty). + values: HashMap, } impl<'a> Codegen<'a> { @@ -546,12 +515,11 @@ impl<'a> Codegen<'a> { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); log::trace!("builder: {:?}", builder); - let (functions_by_input, functions_by_output) = builder.finalize(); + let functions_by_term = builder.finalize(); Ok(Codegen { typeenv, termenv, - functions_by_input, - functions_by_output, + functions_by_term, }) } @@ -562,7 +530,6 @@ impl<'a> Codegen<'a> { self.generate_ctx_trait(&mut code)?; self.generate_internal_types(&mut code)?; self.generate_internal_term_constructors(&mut code)?; - self.generate_internal_term_extractors(&mut code)?; Ok(code) } @@ -580,7 +547,11 @@ impl<'a> Codegen<'a> { writeln!( code, - "\n#![allow(dead_code, unreachable_code, unused_imports, unused_variables, non_snake_case)]" + "\n#![allow(dead_code, unreachable_code, unreachable_patterns)]" + )?; + writeln!( + code, + "#![allow(unused_imports, unused_variables, non_snake_case)]" )?; writeln!(code, "\nuse super::*; // Pulls in all external types.")?; @@ -588,6 +559,32 @@ impl<'a> Codegen<'a> { Ok(()) } + fn generate_trait_sig( + &self, + code: &mut dyn Write, + indent: &str, + sig: &ExternalSig, + ) -> Result<(), Error> { + writeln!( + code, + "{}fn {}(&mut self, {}) -> Option<({},)>;", + indent, + sig.func_name, + sig.arg_tys + .iter() + .enumerate() + .map(|(i, &ty)| format!("arg{}: {}", i, self.type_name(ty, /* by_ref = */ true))) + .collect::>() + .join(", "), + sig.ret_tys + .iter() + .map(|&ty| self.type_name(ty, /* by_ref = */ false)) + .collect::>() + .join(", ") + )?; + Ok(()) + } + fn generate_ctx_trait(&self, code: &mut dyn Write) -> Result<(), Error> { writeln!(code, "")?; writeln!( @@ -604,74 +601,9 @@ impl<'a> Codegen<'a> { )?; writeln!(code, "pub trait Context {{")?; for term in &self.termenv.terms { - if let &TermKind::Regular { - extractor, - constructor, - .. - } = &term.kind - { - if let Some((etor_name, infallible)) = extractor { - let etor_name = &self.typeenv.syms[etor_name.index()]; - let arg_is_prim = match &self.typeenv.types[term.ret_ty.index()] { - &Type::Primitive(..) => true, - _ => false, - }; - let arg = format!( - "arg0: {}", - self.type_name( - term.ret_ty, - /* by_ref = */ if arg_is_prim { None } else { Some("&") } - ), - ); - let ret_tuple_tys = term - .arg_tys - .iter() - .map(|ty| { - self.type_name(*ty, /* by_ref = */ None) - }) - .collect::>(); - if infallible { - writeln!( - code, - " fn {}(&mut self, {}) -> ({},);", - etor_name, - arg, - ret_tuple_tys.join(", ") - )?; - } else { - writeln!( - code, - " fn {}(&mut self, {}) -> Option<({},)>;", - etor_name, - arg, - ret_tuple_tys.join(", ") - )?; - } - } - - if let Some(ctor_name) = constructor { - let ctor_name = &self.typeenv.syms[ctor_name.index()]; - let args = term - .arg_tys - .iter() - .enumerate() - .map(|(i, &arg_ty)| { - format!( - "arg{}: {}", - i, - self.type_name(arg_ty, /* by_ref = */ Some("&")) - ) - }) - .collect::>(); - let ret = self.type_name(term.ret_ty, /* by_ref = */ None); - writeln!( - code, - " fn {}(&mut self, {}) -> Option<{}>;", - ctor_name, - args.join(", "), - ret, - )?; - } + if term.is_external() { + let ext_sig = term.to_sig(self.typeenv).unwrap(); + self.generate_trait_sig(code, " ", &ext_sig)?; } } writeln!(code, "}}")?; @@ -721,44 +653,11 @@ impl<'a> Codegen<'a> { Ok(()) } - fn constructor_name(&self, term: TermId) -> String { - let termdata = &self.termenv.terms[term.index()]; - match &termdata.kind { - &TermKind::EnumVariant { .. } => panic!("using enum variant as constructor"), - &TermKind::Regular { - constructor: Some(sym), - .. - } => format!("C::{}", self.typeenv.syms[sym.index()]), - &TermKind::Regular { - constructor: None, .. - } => { - format!("constructor_{}", self.typeenv.syms[termdata.name.index()]) - } - } - } - - fn extractor_name_and_infallible(&self, term: TermId) -> (String, bool) { - let termdata = &self.termenv.terms[term.index()]; - match &termdata.kind { - &TermKind::EnumVariant { .. } => panic!("using enum variant as extractor"), - &TermKind::Regular { - extractor: Some((sym, infallible)), - .. - } => (format!("C::{}", self.typeenv.syms[sym.index()]), infallible), - &TermKind::Regular { - extractor: None, .. - } => ( - format!("extractor_{}", self.typeenv.syms[termdata.name.index()]), - false, - ), - } - } - - fn type_name(&self, typeid: TypeId, by_ref: Option<&str>) -> String { + fn type_name(&self, typeid: TypeId, by_ref: bool) -> String { match &self.typeenv.types[typeid.index()] { &Type::Primitive(_, sym) => self.typeenv.syms[sym.index()].clone(), &Type::Enum { name, .. } => { - let r = by_ref.unwrap_or(""); + let r = if by_ref { "&" } else { "" }; format!("{}{}", r, self.typeenv.syms[name.index()]) } } @@ -771,10 +670,24 @@ impl<'a> Codegen<'a> { } } + fn ty_prim(&self, ty: TypeId) -> bool { + self.typeenv.types[ty.index()].is_prim() + } + + fn value_binder(&self, value: &Value, is_ref: bool, ty: TypeId) -> String { + let prim = self.ty_prim(ty); + if prim || !is_ref { + format!("{}", self.value_name(value)) + } else { + format!("ref {}", self.value_name(value)) + } + } + fn value_by_ref(&self, value: &Value, ctx: &BodyContext) -> String { let raw_name = self.value_name(value); - let name_is_ref = ctx.borrowed_values.contains(value); - if name_is_ref { + let &(is_ref, ty) = ctx.values.get(value).unwrap(); + let prim = self.ty_prim(ty); + if is_ref || prim { raw_name } else { format!("&{}", raw_name) @@ -783,50 +696,41 @@ impl<'a> Codegen<'a> { fn value_by_val(&self, value: &Value, ctx: &BodyContext) -> String { let raw_name = self.value_name(value); - let name_is_ref = ctx.borrowed_values.contains(value); - if name_is_ref { + let &(is_ref, _) = ctx.values.get(value).unwrap(); + if is_ref { format!("{}.clone()", raw_name) } else { raw_name } } - fn define_val(&self, value: &Value, ctx: &mut BodyContext, is_ref: bool) { - if is_ref { - ctx.borrowed_values.insert(value.clone()); - } + fn define_val(&self, value: &Value, ctx: &mut BodyContext, is_ref: bool, ty: TypeId) { + let is_ref = !self.ty_prim(ty) && is_ref; + ctx.values.insert(value.clone(), (is_ref, ty)); } fn generate_internal_term_constructors(&self, code: &mut dyn Write) -> Result<(), Error> { - for (&termid, trie) in &self.functions_by_input { + for (&termid, trie) in &self.functions_by_term { let termdata = &self.termenv.terms[termid.index()]; // Skip terms that are enum variants or that have external // constructors/extractors. - match &termdata.kind { - &TermKind::EnumVariant { .. } => continue, - &TermKind::Regular { - constructor, - extractor, - .. - } if constructor.is_some() || extractor.is_some() => continue, - _ => {} + if !termdata.is_constructor() || termdata.is_external() { + continue; } - // Get the name of the term and build up the signature. - let func_name = self.constructor_name(termid); - let args = termdata + let sig = termdata.to_sig(self.typeenv).unwrap(); + + let args = sig .arg_tys .iter() .enumerate() - .map(|(i, &arg_ty)| { - format!( - "arg{}: {}", - i, - self.type_name(arg_ty, /* by_ref = */ Some("&")) - ) - }) - .collect::>(); + .map(|(i, &ty)| format!("arg{}: {}", i, self.type_name(ty, true))) + .collect::>() + .join(", "); + assert_eq!(sig.ret_tys.len(), 1); + let ret = self.type_name(sig.ret_tys[0], false); + writeln!( code, "\n// Generated as internal constructor for term {}.", @@ -835,13 +739,10 @@ impl<'a> Codegen<'a> { writeln!( code, "pub fn {}(ctx: &mut C, {}) -> Option<{}> {{", - func_name, - args.join(", "), - self.type_name(termdata.ret_ty, /* by_ref = */ None) + sig.func_name, args, ret, )?; let mut body_ctx: BodyContext = Default::default(); - body_ctx.expected_return_vals = 1; let returned = self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; if !returned { @@ -854,69 +755,6 @@ impl<'a> Codegen<'a> { Ok(()) } - fn generate_internal_term_extractors(&self, code: &mut dyn Write) -> Result<(), Error> { - for (&termid, trie) in &self.functions_by_output { - let termdata = &self.termenv.terms[termid.index()]; - - // Skip terms that are enum variants or that have external extractors. - match &termdata.kind { - &TermKind::EnumVariant { .. } => continue, - &TermKind::Regular { - constructor, - extractor, - .. - } if constructor.is_some() || extractor.is_some() => continue, - _ => {} - } - - // Get the name of the term and build up the signature. - let (func_name, _) = self.extractor_name_and_infallible(termid); - let arg_is_prim = match &self.typeenv.types[termdata.ret_ty.index()] { - &Type::Primitive(..) => true, - _ => false, - }; - let arg = format!( - "arg0: {}", - self.type_name( - termdata.ret_ty, - /* by_ref = */ if arg_is_prim { None } else { Some("&") } - ), - ); - let ret_tuple_tys = termdata - .arg_tys - .iter() - .map(|ty| { - self.type_name(*ty, /* by_ref = */ None) - }) - .collect::>(); - - writeln!( - code, - "\n// Generated as internal extractor for term {}.", - self.typeenv.syms[termdata.name.index()], - )?; - writeln!( - code, - "pub fn {}(ctx: &mut C, {}) -> Option<({},)> {{", - func_name, - arg, - ret_tuple_tys.join(", "), - )?; - - let mut body_ctx: BodyContext = Default::default(); - body_ctx.expected_return_vals = ret_tuple_tys.len(); - body_ctx.tuple_return = true; - let returned = - self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; - if !returned { - writeln!(code, " return None;")?; - } - writeln!(code, "}}")?; - } - - Ok(()) - } - fn generate_expr_inst( &self, code: &mut dyn Write, @@ -926,15 +764,16 @@ impl<'a> Codegen<'a> { ctx: &mut BodyContext, returns: &mut Vec<(usize, String)>, ) -> Result<(), Error> { + log::trace!("generate_expr_inst: {:?}", inst); match inst { &ExprInst::ConstInt { ty, val } => { let value = Value::Expr { inst: id, output: 0, }; + self.define_val(&value, ctx, /* is_ref = */ false, ty); let name = self.value_name(&value); - let ty = self.type_name(ty, /* by_ref = */ None); - self.define_val(&value, ctx, /* is_ref = */ false); + let ty = self.type_name(ty, /* by_ref = */ false); writeln!(code, "{}let {}: {} = {};", indent, name, ty, val)?; } &ExprInst::CreateVariant { @@ -960,7 +799,7 @@ impl<'a> Codegen<'a> { let outputname = self.value_name(&output); let full_variant_name = format!( "{}::{}", - self.type_name(ty, None), + self.type_name(ty, false), self.typeenv.syms[variantinfo.name.index()] ); if input_fields.is_empty() { @@ -980,7 +819,7 @@ impl<'a> Codegen<'a> { } writeln!(code, "{}}};", indent)?; } - self.define_val(&output, ctx, /* is_ref = */ false); + self.define_val(&output, ctx, /* is_ref = */ false, ty); } &ExprInst::Construct { ref inputs, term, .. @@ -996,16 +835,18 @@ impl<'a> Codegen<'a> { output: 0, }; let outputname = self.value_name(&output); - let ctor_name = self.constructor_name(term); + let termdata = &self.termenv.terms[term.index()]; + let sig = termdata.to_sig(self.typeenv).unwrap(); + assert_eq!(input_exprs.len(), sig.arg_tys.len()); writeln!( code, "{}let {} = {}(ctx, {});", indent, outputname, - ctor_name, + sig.full_name, input_exprs.join(", "), )?; - self.define_val(&output, ctx, /* is_ref = */ false); + self.define_val(&output, ctx, /* is_ref = */ false, termdata.ret_ty); } &ExprInst::Return { index, ref value, .. @@ -1029,23 +870,15 @@ impl<'a> Codegen<'a> { .iter() .zip(variant.fields.iter()) .enumerate() - .map(|(i, (ty, field))| { + .map(|(i, (&ty, field))| { let value = Value::Pattern { inst: id, output: i, }; - let valuename = self.value_name(&value); + let valuename = self.value_binder(&value, /* is_ref = */ true, ty); let fieldname = &self.typeenv.syms[field.name.index()]; - match &self.typeenv.types[ty.index()] { - &Type::Primitive(..) => { - self.define_val(&value, ctx, /* is_ref = */ false); - format!("{}: {}", fieldname, valuename) - } - &Type::Enum { .. } => { - self.define_val(&value, ctx, /* is_ref = */ true); - format!("{}: ref {}", fieldname, valuename) - } - } + self.define_val(&value, ctx, /* is_ref = */ false, field.ty); + format!("{}: {}", fieldname, valuename) }) .collect::>() } @@ -1080,6 +913,7 @@ impl<'a> Codegen<'a> { }, ctx, is_ref, + ty, ); Ok(true) } @@ -1107,7 +941,7 @@ impl<'a> Codegen<'a> { &Type::Primitive(..) => panic!("primitive type input to MatchVariant"), &Type::Enum { ref variants, .. } => variants, }; - let ty_name = self.type_name(input_ty, /* is_ref = */ Some("&")); + let ty_name = self.type_name(input_ty, /* is_ref = */ true); let variant = &variants[variant.index()]; let variantname = &self.typeenv.syms[variant.name.index()]; let args = self.match_variant_binders(variant, &arg_tys[..], id, ctx); @@ -1124,57 +958,70 @@ impl<'a> Codegen<'a> { Ok(false) } &PatternInst::Extract { - ref input, - input_ty, - ref arg_tys, + ref inputs, + ref output_tys, term, .. } => { - let input_ty_prim = match &self.typeenv.types[input_ty.index()] { - &Type::Primitive(..) => true, - _ => false, - }; - let input = if input_ty_prim { - self.value_by_val(input, ctx) - } else { - self.value_by_ref(input, ctx) - }; - let (etor_name, infallible) = self.extractor_name_and_infallible(term); + let termdata = &self.termenv.terms[term.index()]; + let sig = termdata.to_sig(self.typeenv).unwrap(); - let args = arg_tys + let input_values = inputs + .iter() + .map(|input| self.value_by_ref(input, ctx)) + .collect::>(); + let output_binders = output_tys .iter() .enumerate() - .map(|(i, _ty)| { - let value = Value::Pattern { + .map(|(i, &ty)| { + let output_val = Value::Pattern { inst: id, output: i, }; - self.define_val(&value, ctx, /* is_ref = */ false); - self.value_name(&value) + self.define_val(&output_val, ctx, /* is_ref = */ false, ty); + self.value_binder(&output_val, /* is_ref = */ false, ty) }) .collect::>(); - if infallible { - writeln!( - code, - "{}let Some(({},)) = {}(ctx, {});", - indent, - args.join(", "), - etor_name, - input - )?; - writeln!(code, "{}{{", indent)?; - } else { - writeln!( - code, - "{}if let Some(({},)) = {}(ctx, {}) {{", - indent, - args.join(", "), - etor_name, - input - )?; + writeln!( + code, + "{}if let Some(({},)) = {}(ctx, {}) {{", + indent, + output_binders.join(", "), + sig.full_name, + input_values.join(", "), + )?; + + Ok(false) + } + &PatternInst::Expr { ref seq, output_ty, .. } => { + let closure_name = format!("closure{}", id.index()); + writeln!(code, "{}let {} = || {{", indent, closure_name)?; + let subindent = format!("{} ", indent); + let mut subctx = ctx.clone(); + let mut returns = vec![]; + for (id, inst) in seq.insts.iter().enumerate() { + let id = InstId(id); + self.generate_expr_inst(code, id, inst, &subindent, &mut subctx, &mut returns)?; } - Ok(infallible) + assert_eq!(returns.len(), 1); + writeln!(code, "{}return Some({});", subindent, returns[0].1)?; + writeln!(code, "{}}};", indent)?; + + let output = Value::Pattern { + inst: id, + output: 0, + }; + writeln!( + code, + "{}if let Some({}) = {}() {{", + indent, + self.value_binder(&output, /* is_ref = */ false, output_ty), + closure_name + )?; + self.define_val(&output, ctx, /* is_ref = */ false, output_ty); + + Ok(false) } } } @@ -1206,18 +1053,8 @@ impl<'a> Codegen<'a> { self.generate_expr_inst(code, id, inst, indent, ctx, &mut returns)?; } - assert_eq!(returns.len(), ctx.expected_return_vals); - returns.sort_by_key(|(index, _)| *index); - if ctx.tuple_return { - let return_values = returns - .into_iter() - .map(|(_, expr)| expr) - .collect::>() - .join(", "); - writeln!(code, "{}return Some(({},));", indent, return_values)?; - } else { - writeln!(code, "{}return Some({});", indent, returns[0].1)?; - } + assert_eq!(returns.len(), 1); + writeln!(code, "{}return Some({});", indent, returns[0].1)?; returned = true; } diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 7951c1175e..19802b4f1e 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -41,14 +41,25 @@ pub enum PatternInst { variant: VariantId, }, - /// Invoke an extractor, taking the given value as input and - /// producing `|arg_tys|` values as output. + /// Invoke an extractor, taking the given values as input (the + /// first is the value to extract, the other are the + /// `Input`-polarity extractor args) and producing an output valu + /// efor each `Output`-polarity extractor arg. Extract { - input: Value, - input_ty: TypeId, - arg_tys: Vec, + inputs: Vec, + input_tys: Vec, + output_tys: Vec, term: TermId, }, + + /// Evaluate an expression and provide the given value as the + /// result of this match instruction. The expression has access to + /// the pattern-values up to this point in the sequence. + Expr { + seq: ExprSequence, + output: Value, + output_ty: TypeId, + }, } /// A single Expr instruction. @@ -110,7 +121,7 @@ pub struct PatternSequence { /// A linear sequence of instructions that produce a new value from /// the right-hand side of a rule, given bindings that come from a /// `Pattern` derived from the left-hand side. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct ExprSequence { /// Instruction sequence for expression. InstId indexes into this /// sequence for `Value::Expr` values. @@ -119,6 +130,21 @@ pub struct ExprSequence { pub pos: Pos, } +#[derive(Clone, Copy, Debug)] +enum ValueOrArgs { + Value(Value), + ImplicitTermFromArgs(TermId), +} + +impl ValueOrArgs { + fn to_value(&self) -> Option { + match self { + &ValueOrArgs::Value(v) => Some(v), + _ => None, + } + } +} + impl PatternSequence { fn add_inst(&mut self, inst: PatternInst) -> InstId { let id = InstId(self.insts.len()); @@ -165,104 +191,198 @@ impl PatternSequence { fn add_extract( &mut self, - input: Value, - input_ty: TypeId, - arg_tys: &[TypeId], + inputs: Vec, + input_tys: Vec, + output_tys: Vec, term: TermId, ) -> Vec { let inst = InstId(self.insts.len()); let mut outs = vec![]; - for (i, _arg_ty) in arg_tys.iter().enumerate() { + for i in 0..output_tys.len() { let val = Value::Pattern { inst, output: i }; outs.push(val); } - let arg_tys = arg_tys.iter().cloned().collect(); + let output_tys = output_tys.iter().cloned().collect(); self.add_inst(PatternInst::Extract { - input, - input_ty, - arg_tys, + inputs, + input_tys, + output_tys, term, }); outs } + fn add_expr_seq(&mut self, seq: ExprSequence, output: Value, output_ty: TypeId) -> Value { + let inst = self.add_inst(PatternInst::Expr { + seq, + output, + output_ty, + }); + + // Create values for all outputs. + Value::Pattern { inst, output: 0 } + } + /// Generate PatternInsts to match the given (sub)pattern. Works - /// recursively down the AST. Returns the root term matched by - /// this pattern, if any. + /// recursively down the AST. fn gen_pattern( &mut self, - // If `input` is `None`, then this is the root pattern, and is - // implicitly an extraction with the N args as results. - input: Option, + input: ValueOrArgs, typeenv: &TypeEnv, termenv: &TermEnv, pat: &Pattern, - vars: &mut HashMap, Value)>, - ) -> Option { + vars: &mut HashMap, + ) { match pat { &Pattern::BindPattern(_ty, var, ref subpat) => { // Bind the appropriate variable and recurse. assert!(!vars.contains_key(&var)); - vars.insert(var, (None, input.unwrap())); // bind first, so subpat can use it + if let Some(v) = input.to_value() { + vars.insert(var, v); + } let root_term = self.gen_pattern(input, typeenv, termenv, &*subpat, vars); - vars.get_mut(&var).unwrap().0 = root_term; root_term } &Pattern::Var(ty, var) => { // Assert that the value matches the existing bound var. - let (var_val_term, var_val) = vars + let var_val = vars .get(&var) .cloned() .expect("Variable should already be bound"); - self.add_match_equal(input.unwrap(), var_val, ty); - var_val_term + let input_val = input + .to_value() + .expect("Cannot match an =var pattern against root term"); + self.add_match_equal(input_val, var_val, ty); } &Pattern::ConstInt(ty, value) => { // Assert that the value matches the constant integer. - self.add_match_int(input.unwrap(), ty, value); - None - } - &Pattern::Term(_, term, ref args) if input.is_none() => { - let termdata = &termenv.terms[term.index()]; - let arg_tys = &termdata.arg_tys[..]; - for (i, subpat) in args.iter().enumerate() { - let value = self.add_arg(i, arg_tys[i]); - self.gen_pattern(Some(value), typeenv, termenv, subpat, vars); - } - Some(term) + let input_val = input + .to_value() + .expect("Cannot match an =var pattern against root term"); + self.add_match_int(input_val, ty, value); } &Pattern::Term(ty, term, ref args) => { - // Determine whether the term has an external extractor or not. - let termdata = &termenv.terms[term.index()]; - let arg_tys = &termdata.arg_tys[..]; - match &termdata.kind { - &TermKind::EnumVariant { variant } => { - let arg_values = - self.add_match_variant(input.unwrap(), ty, arg_tys, variant); - for (subpat, value) in args.iter().zip(arg_values.into_iter()) { - self.gen_pattern(Some(value), typeenv, termenv, subpat, vars); + match input { + ValueOrArgs::ImplicitTermFromArgs(termid) => { + assert_eq!( + termid, term, + "Cannot match a different term against root pattern" + ); + let termdata = &termenv.terms[term.index()]; + let arg_tys = &termdata.arg_tys[..]; + for (i, subpat) in args.iter().enumerate() { + let value = self.add_arg(i, arg_tys[i]); + let subpat = match subpat { + &TermArgPattern::Expr(..) => { + panic!("Should have been caught in typechecking") + } + &TermArgPattern::Pattern(ref pat) => pat, + }; + self.gen_pattern( + ValueOrArgs::Value(value), + typeenv, + termenv, + subpat, + vars, + ); } - None } - &TermKind::Regular { .. } => { - let arg_values = self.add_extract(input.unwrap(), ty, arg_tys, term); - for (subpat, value) in args.iter().zip(arg_values.into_iter()) { - self.gen_pattern(Some(value), typeenv, termenv, subpat, vars); + ValueOrArgs::Value(input) => { + // Determine whether the term has an external extractor or not. + let termdata = &termenv.terms[term.index()]; + let arg_tys = &termdata.arg_tys[..]; + match &termdata.kind { + &TermKind::Declared => { + panic!("Pattern invocation of undefined term body"); + } + &TermKind::EnumVariant { variant } => { + let arg_values = + self.add_match_variant(input, ty, arg_tys, variant); + for (subpat, value) in args.iter().zip(arg_values.into_iter()) { + let subpat = match subpat { + &TermArgPattern::Pattern(ref pat) => pat, + _ => unreachable!("Should have been caught by sema"), + }; + self.gen_pattern( + ValueOrArgs::Value(value), + typeenv, + termenv, + subpat, + vars, + ); + } + } + &TermKind::InternalConstructor + | &TermKind::ExternalConstructor { .. } => { + panic!("Should not invoke constructor in pattern"); + } + &TermKind::InternalExtractor { .. } => { + panic!("Should have been expanded away"); + } + &TermKind::ExternalExtractor { + ref arg_polarity, .. + } => { + // Evaluate all `input` args. + let mut inputs = vec![]; + let mut input_tys = vec![]; + let mut output_tys = vec![]; + let mut output_pats = vec![]; + inputs.push(input); + input_tys.push(termdata.ret_ty); + for (arg, pol) in args.iter().zip(arg_polarity.iter()) { + match pol { + &ArgPolarity::Input => { + let expr = match arg { + &TermArgPattern::Expr(ref expr) => expr, + _ => panic!( + "Should have been caught by typechecking" + ), + }; + let mut seq = ExprSequence::default(); + let value = seq.gen_expr(typeenv, termenv, expr, vars); + seq.add_return(expr.ty(), value); + let value = self.add_expr_seq(seq, value, expr.ty()); + inputs.push(value); + input_tys.push(expr.ty()); + } + &ArgPolarity::Output => { + let pat = match arg { + &TermArgPattern::Pattern(ref pat) => pat, + _ => panic!( + "Should have been caught by typechecking" + ), + }; + output_tys.push(pat.ty()); + output_pats.push(pat); + } + } + } + + // Invoke the extractor. + let arg_values = + self.add_extract(inputs, input_tys, output_tys, term); + + for (pat, &val) in output_pats.iter().zip(arg_values.iter()) { + self.gen_pattern( + ValueOrArgs::Value(val), + typeenv, + termenv, + pat, + vars, + ); + } + } } - Some(term) } } } &Pattern::And(_ty, ref children) => { - let input = input.unwrap(); for child in children { - self.gen_pattern(Some(input), typeenv, termenv, child, vars); + self.gen_pattern(input, typeenv, termenv, child, vars); } - None } &Pattern::Wildcard(_ty) => { // Nothing! - None } } } @@ -319,63 +439,40 @@ impl ExprSequence { /// Creates a sequence of ExprInsts to generate the given /// expression value. Returns the value ID as well as the root /// term ID, if any. - /// - /// If `gen_final_construct` is false and the value is a - /// constructor call, this returns the arguments instead. This is - /// used when codegen'ing extractors for internal terms. fn gen_expr( &mut self, typeenv: &TypeEnv, termenv: &TermEnv, expr: &Expr, - vars: &HashMap, Value)>, - gen_final_construct: bool, - ) -> (Option, Vec) { - log::trace!( - "gen_expr: expr {:?} gen_final_construct {}", - expr, - gen_final_construct - ); + vars: &HashMap, + ) -> Value { + log::trace!("gen_expr: expr {:?}", expr); match expr { - &Expr::ConstInt(ty, val) => (None, vec![self.add_const_int(ty, val)]), + &Expr::ConstInt(ty, val) => self.add_const_int(ty, val), &Expr::Let(_ty, ref bindings, ref subexpr) => { let mut vars = vars.clone(); for &(var, _var_ty, ref var_expr) in bindings { - let (var_value_term, var_value) = - self.gen_expr(typeenv, termenv, &*var_expr, &vars, true); - let var_value = var_value[0]; - vars.insert(var, (var_value_term, var_value)); + let var_value = self.gen_expr(typeenv, termenv, &*var_expr, &vars); + vars.insert(var, var_value); } - self.gen_expr(typeenv, termenv, &*subexpr, &vars, gen_final_construct) - } - &Expr::Var(_ty, var_id) => { - let (root_term, value) = vars.get(&var_id).cloned().unwrap(); - (root_term, vec![value]) + self.gen_expr(typeenv, termenv, &*subexpr, &vars) } + &Expr::Var(_ty, var_id) => vars.get(&var_id).cloned().unwrap(), &Expr::Term(ty, term, ref arg_exprs) => { let termdata = &termenv.terms[term.index()]; let mut arg_values_tys = vec![]; - log::trace!("Term gen_expr term {}", term.index()); for (arg_ty, arg_expr) in termdata.arg_tys.iter().cloned().zip(arg_exprs.iter()) { - log::trace!("generating for arg_expr {:?}", arg_expr); - arg_values_tys.push(( - self.gen_expr(typeenv, termenv, &*arg_expr, &vars, true).1[0], - arg_ty, - )); + arg_values_tys + .push((self.gen_expr(typeenv, termenv, &*arg_expr, &vars), arg_ty)); } match &termdata.kind { - &TermKind::EnumVariant { variant } => ( - None, - vec![self.add_create_variant(&arg_values_tys[..], ty, variant)], - ), - &TermKind::Regular { .. } if !gen_final_construct => ( - Some(termdata.id), - arg_values_tys.into_iter().map(|(val, _ty)| val).collect(), - ), - &TermKind::Regular { .. } => ( - Some(termdata.id), - vec![self.add_construct(&arg_values_tys[..], ty, term)], - ), + &TermKind::EnumVariant { variant } => { + self.add_create_variant(&arg_values_tys[..], ty, variant) + } + &TermKind::InternalConstructor | &TermKind::ExternalConstructor { .. } => { + self.add_construct(&arg_values_tys[..], ty, term) + } + _ => panic!("Should have been caught by typechecking"), } } } @@ -387,114 +484,34 @@ pub fn lower_rule( tyenv: &TypeEnv, termenv: &TermEnv, rule: RuleId, - is_forward_dir: bool, -) -> Option<(PatternSequence, ExprSequence, TermId)> { +) -> (PatternSequence, ExprSequence) { let mut pattern_seq: PatternSequence = Default::default(); let mut expr_seq: ExprSequence = Default::default(); expr_seq.pos = termenv.rules[rule.index()].pos; - // Lower the pattern, starting from the root input value. let ruledata = &termenv.rules[rule.index()]; let mut vars = HashMap::new(); + let root_term = ruledata + .lhs + .root_term() + .expect("Pattern must have a term at the root"); - log::trace!( - "lower_rule: ruledata {:?} forward {}", - ruledata, - is_forward_dir + log::trace!("lower_rule: ruledata {:?}", ruledata,); + + // Lower the pattern, starting from the root input value. + pattern_seq.gen_pattern( + ValueOrArgs::ImplicitTermFromArgs(root_term), + tyenv, + termenv, + &ruledata.lhs, + &mut vars, ); - if is_forward_dir { - let can_do_forward = match &ruledata.lhs { - &Pattern::Term(..) => true, - _ => false, - }; - if !can_do_forward { - return None; - } - - let lhs_root_term = pattern_seq.gen_pattern(None, tyenv, termenv, &ruledata.lhs, &mut vars); - let root_term = match lhs_root_term { - Some(t) => t, - None => { - return None; - } - }; - - // Lower the expression, making use of the bound variables - // from the pattern. - let (_, rhs_root_vals) = expr_seq.gen_expr( - tyenv, - termenv, - &ruledata.rhs, - &vars, - /* final_construct = */ true, - ); - // Return the root RHS value. - let output_ty = ruledata.rhs.ty(); - assert_eq!(rhs_root_vals.len(), 1); - expr_seq.add_return(output_ty, rhs_root_vals[0]); - Some((pattern_seq, expr_seq, root_term)) - } else { - let can_reverse = match &ruledata.rhs { - &Expr::Term(..) => true, - _ => false, - }; - if !can_reverse { - return None; - } - - let arg = pattern_seq.add_arg(0, ruledata.lhs.ty()); - let _ = pattern_seq.gen_pattern(Some(arg), tyenv, termenv, &ruledata.lhs, &mut vars); - let (rhs_root_term, rhs_root_vals) = expr_seq.gen_expr( - tyenv, - termenv, - &ruledata.rhs, - &vars, - /* final_construct = */ false, - ); - - let root_term = match rhs_root_term { - Some(t) => t, - None => { - return None; - } - }; - let termdata = &termenv.terms[root_term.index()]; - for (i, (val, ty)) in rhs_root_vals - .into_iter() - .zip(termdata.arg_tys.iter()) - .enumerate() - { - expr_seq.add_multi_return(i, *ty, val); - } - - Some((pattern_seq, expr_seq, root_term)) - } -} - -/// Trim the final Construct and Return ops in an ExprSequence in -/// order to allow the extractor to be codegen'd. -pub fn trim_expr_for_extractor(mut expr: ExprSequence) -> ExprSequence { - let ret_inst = expr.insts.pop().unwrap(); - let retval = match ret_inst { - ExprInst::Return { value, .. } => value, - _ => panic!("Last instruction is not a return"), - }; - assert_eq!( - retval, - Value::Expr { - inst: InstId(expr.insts.len() - 1), - output: 0 - } - ); - let construct_inst = expr.insts.pop().unwrap(); - let inputs = match construct_inst { - ExprInst::Construct { inputs, .. } => inputs, - _ => panic!("Returned value is not a construct call"), - }; - for (i, (value, ty)) in inputs.into_iter().enumerate() { - expr.add_multi_return(i, ty, value); - } - - expr + // 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); + // Return the root RHS value. + let output_ty = ruledata.rhs.ty(); + expr_seq.add_return(output_ty, rhs_root_val); + (pattern_seq, expr_seq) } diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/src/lexer.rs index 5c85f5a730..261dc9c910 100644 --- a/cranelift/isle/src/lexer.rs +++ b/cranelift/isle/src/lexer.rs @@ -18,7 +18,7 @@ enum LexerInput<'a> { File { content: String, filename: String }, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] pub struct Pos { pub file: usize, pub offset: usize, @@ -41,6 +41,8 @@ pub enum Token { RParen, Symbol(String), Int(i64), + At, + Lt, } impl<'a> Lexer<'a> { @@ -133,7 +135,7 @@ impl<'a> Lexer<'a> { } fn is_sym_other_char(c: u8) -> bool { match c { - b'(' | b')' | b';' => false, + b'(' | b')' | b';' | b'@' | b'<' => false, c if c.is_ascii_whitespace() => false, _ => true, } @@ -168,6 +170,14 @@ impl<'a> Lexer<'a> { self.advance_pos(); Some((char_pos, Token::RParen)) } + b'@' => { + self.advance_pos(); + Some((char_pos, Token::At)) + } + b'<' => { + self.advance_pos(); + Some((char_pos, Token::Lt)) + } c if is_sym_first_char(c) => { let start = self.pos.offset; let start_pos = self.pos; diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index e5265adc76..7ec4fdc0ea 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -53,6 +53,12 @@ impl<'a> Parser<'a> { fn is_rparen(&self) -> bool { self.is(|tok| *tok == Token::RParen) } + fn is_at(&self) -> bool { + self.is(|tok| *tok == Token::At) + } + fn is_lt(&self) -> bool { + self.is(|tok| *tok == Token::Lt) + } fn is_sym(&self) -> bool { self.is(|tok| tok.is_sym()) } @@ -72,6 +78,12 @@ impl<'a> Parser<'a> { fn rparen(&mut self) -> ParseResult<()> { self.take(|tok| *tok == Token::RParen).map(|_| ()) } + fn at(&mut self) -> ParseResult<()> { + self.take(|tok| *tok == Token::At).map(|_| ()) + } + fn lt(&mut self) -> ParseResult<()> { + self.take(|tok| *tok == Token::Lt).map(|_| ()) + } fn symbol(&mut self) -> ParseResult { match self.take(|tok| tok.is_sym())? { @@ -103,10 +115,10 @@ impl<'a> Parser<'a> { let pos = self.pos(); let def = match &self.symbol()?[..] { "type" => Def::Type(self.parse_type()?), - "rule" => Def::Rule(self.parse_rule()?), "decl" => Def::Decl(self.parse_decl()?), - "constructor" => Def::Extern(self.parse_ctor()?), - "extractor" => Def::Extern(self.parse_etor()?), + "rule" => Def::Rule(self.parse_rule()?), + "extractor" => Def::Extractor(self.parse_etor()?), + "extern" => Def::Extern(self.parse_extern()?), s => { return Err(self.error(pos.unwrap(), format!("Unexpected identifier: {}", s))); } @@ -231,32 +243,72 @@ impl<'a> Parser<'a> { }) } - fn parse_ctor(&mut self) -> ParseResult { + fn parse_extern(&mut self) -> ParseResult { let pos = self.pos(); - let term = self.parse_ident()?; - let func = self.parse_ident()?; - Ok(Extern::Constructor { - term, - func, - pos: pos.unwrap(), - }) + if self.is_sym_str("constructor") { + self.symbol()?; + let term = self.parse_ident()?; + let func = self.parse_ident()?; + Ok(Extern::Constructor { + term, + func, + pos: pos.unwrap(), + }) + } else if self.is_sym_str("extractor") { + self.symbol()?; + let term = self.parse_ident()?; + let func = self.parse_ident()?; + let arg_polarity = if self.is_lparen() { + let mut pol = vec![]; + self.lparen()?; + while !self.is_rparen() { + if self.is_sym_str("in") { + self.symbol()?; + pol.push(ArgPolarity::Input); + } else if self.is_sym_str("out") { + self.symbol()?; + pol.push(ArgPolarity::Output); + } else { + return Err( + self.error(pos.unwrap(), "Invalid argument polarity".to_string()) + ); + } + } + self.rparen()?; + Some(pol) + } else { + None + }; + Ok(Extern::Extractor { + term, + func, + pos: pos.unwrap(), + arg_polarity, + }) + } else { + Err(self.error( + pos.unwrap(), + "Invalid extern: must be (extern constructor ...) or (extern extractor ...)" + .to_string(), + )) + } } - fn parse_etor(&mut self) -> ParseResult { + fn parse_etor(&mut self) -> ParseResult { let pos = self.pos(); - let infallible = if self.is_sym_str("infallible") { - self.symbol()?; - true - } else { - false - }; + self.lparen()?; let term = self.parse_ident()?; - let func = self.parse_ident()?; - Ok(Extern::Extractor { + let mut args = vec![]; + while !self.is_rparen() { + args.push(self.parse_ident()?); + } + self.rparen()?; + let template = self.parse_pattern()?; + Ok(Extractor { term, - func, + args, + template, pos: pos.unwrap(), - infallible, }) } @@ -292,8 +344,8 @@ impl<'a> Parser<'a> { Ok(Pattern::Var { var }) } else { let var = self.str_to_ident(pos.unwrap(), &s)?; - if self.is_sym_str("@") { - self.symbol()?; + if self.is_at() { + self.at()?; let subpat = Box::new(self.parse_pattern()?); Ok(Pattern::BindPattern { var, subpat }) } else { @@ -317,7 +369,7 @@ impl<'a> Parser<'a> { let sym = self.parse_ident()?; let mut args = vec![]; while !self.is_rparen() { - args.push(self.parse_pattern()?); + args.push(self.parse_pattern_term_arg()?); } self.rparen()?; Ok(Pattern::Term { sym, args }) @@ -327,6 +379,15 @@ impl<'a> Parser<'a> { } } + fn parse_pattern_term_arg(&mut self) -> ParseResult { + if self.is_lt() { + self.lt()?; + Ok(TermArgPattern::Expr(self.parse_expr()?)) + } else { + Ok(TermArgPattern::Pattern(self.parse_pattern()?)) + } + } + fn parse_expr(&mut self) -> ParseResult { let pos = self.pos(); if self.is_lparen() { diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index f3d9049180..72aa779c69 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -55,6 +55,13 @@ impl Type { Self::Primitive(_, name) | Self::Enum { name, .. } => &tyenv.syms[name.index()], } } + + pub fn is_prim(&self) -> bool { + match self { + &Type::Primitive(..) => true, + _ => false, + } + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -96,15 +103,120 @@ pub enum TermKind { /// `A1`. variant: VariantId, }, - Regular { - // Producer and consumer rules are catalogued separately after - // building Sequences. Here we just record whether an - // extractor and/or constructor is known. - /// Extractor func and `infallible` flag. - extractor: Option<(Sym, bool)>, - /// Constructor func. - constructor: Option, + /// A term with "internal" rules that work in the forward + /// direction. Becomes a compiled Rust function in the generated + /// code. + InternalConstructor, + /// A term that defines an "extractor macro" in the LHS of a + /// pattern. Its arguments take patterns and are simply + /// substituted with the given patterns when used. + InternalExtractor { + args: Vec, + template: ast::Pattern, }, + /// A term defined solely by an external extractor function. + ExternalExtractor { + /// Extractor func. + name: Sym, + /// Which arguments of the extractor are inputs and which are outputs? + arg_polarity: Vec, + }, + /// A term defined solely by an external constructor function. + ExternalConstructor { + /// Constructor func. + name: Sym, + }, + /// Declared but no body or externs associated (yet). + Declared, +} + +pub use crate::ast::ArgPolarity; + +#[derive(Clone, Debug)] +pub struct ExternalSig { + pub func_name: String, + pub full_name: String, + pub arg_tys: Vec, + pub ret_tys: Vec, +} + +impl Term { + pub fn ty(&self) -> TypeId { + self.ret_ty + } + + pub fn to_variant(&self) -> Option { + match &self.kind { + &TermKind::EnumVariant { variant } => Some(variant), + _ => None, + } + } + + pub fn is_constructor(&self) -> bool { + match &self.kind { + &TermKind::InternalConstructor { .. } | &TermKind::ExternalConstructor { .. } => true, + _ => false, + } + } + + pub fn is_extractor(&self) -> bool { + match &self.kind { + &TermKind::InternalExtractor { .. } | &TermKind::ExternalExtractor { .. } => true, + _ => false, + } + } + + pub fn is_external(&self) -> bool { + match &self.kind { + &TermKind::ExternalExtractor { .. } | &TermKind::ExternalConstructor { .. } => true, + _ => false, + } + } + + pub fn to_sig(&self, tyenv: &TypeEnv) -> Option { + match &self.kind { + &TermKind::ExternalConstructor { name } => Some(ExternalSig { + func_name: tyenv.syms[name.index()].clone(), + full_name: format!("C::{}", tyenv.syms[name.index()]), + arg_tys: self.arg_tys.clone(), + ret_tys: vec![self.ret_ty], + }), + &TermKind::ExternalExtractor { + name, + ref arg_polarity, + } => { + let mut arg_tys = vec![]; + let mut ret_tys = vec![]; + arg_tys.push(self.ret_ty); + for (&arg, polarity) in self.arg_tys.iter().zip(arg_polarity.iter()) { + match polarity { + &ArgPolarity::Input => { + arg_tys.push(arg); + } + &ArgPolarity::Output => { + ret_tys.push(arg); + } + } + } + Some(ExternalSig { + func_name: tyenv.syms[name.index()].clone(), + full_name: format!("C::{}", tyenv.syms[name.index()]), + arg_tys, + ret_tys, + }) + } + &TermKind::InternalConstructor { .. } => { + let name = format!("constructor_{}", tyenv.syms[self.name.index()]); + Some(ExternalSig { + func_name: name.clone(), + full_name: name, + arg_tys: self.arg_tys.clone(), + ret_tys: vec![self.ret_ty], + }) + } + _ => None, + } + } } #[derive(Clone, Debug)] @@ -121,11 +233,17 @@ pub enum Pattern { BindPattern(TypeId, VarId, Box), Var(TypeId, VarId), ConstInt(TypeId, i64), - Term(TypeId, TermId, Vec), + Term(TypeId, TermId, Vec), Wildcard(TypeId), And(TypeId, Vec), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TermArgPattern { + Pattern(Pattern), + Expr(Expr), +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Expr { Term(TypeId, TermId, Vec), @@ -145,6 +263,14 @@ impl Pattern { &Self::And(t, ..) => t, } } + + pub fn root_term(&self) -> Option { + match self { + &Pattern::Term(_, term, _) => Some(term), + &Pattern::BindPattern(_, _, ref subpat) => subpat.root_term(), + _ => None, + } + } } impl Expr { @@ -295,13 +421,13 @@ impl TypeEnv { } } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Bindings { next_var: usize, vars: Vec, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct BoundVar { name: Sym, id: VarId, @@ -318,6 +444,8 @@ impl TermEnv { env.collect_term_sigs(tyenv, defs)?; env.collect_enum_variant_terms(tyenv)?; + env.collect_constructors(tyenv, defs)?; + env.collect_extractor_templates(tyenv, defs)?; env.collect_rules(tyenv, defs)?; Ok(env) @@ -361,10 +489,7 @@ impl TermEnv { name, arg_tys, ret_ty, - kind: TermKind::Regular { - extractor: None, - constructor: None, - }, + kind: TermKind::Declared, }); } _ => {} @@ -415,6 +540,87 @@ impl TermEnv { Ok(()) } + fn collect_constructors(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { + for def in &defs.defs { + match def { + &ast::Def::Rule(ref rule) => { + let pos = rule.pos; + let term = match rule.pattern.root_term() { + Some(t) => t, + None => { + return Err(tyenv.error( + pos, + "Rule does not have a term at the LHS root".to_string(), + )); + } + }; + let sym = tyenv.intern_mut(&term); + let term = match self.term_map.get(&sym) { + Some(&tid) => tid, + None => { + return Err( + tyenv.error(pos, "Rule LHS root term is not defined".to_string()) + ); + } + }; + let termdata = &mut self.terms[term.index()]; + match &termdata.kind { + &TermKind::Declared => { + termdata.kind = TermKind::InternalConstructor; + } + &TermKind::InternalConstructor => { + // OK, no error; multiple rules can apply to one internal constructor term. + } + _ => { + return Err(tyenv.error(pos, "Rule LHS root term is incorrect kind; cannot be internal constructor".to_string())); + } + } + } + _ => {} + } + } + Ok(()) + } + + fn collect_extractor_templates( + &mut self, + tyenv: &mut TypeEnv, + defs: &ast::Defs, + ) -> SemaResult<()> { + for def in &defs.defs { + match def { + &ast::Def::Extractor(ref ext) => { + let sym = tyenv.intern_mut(&ext.term); + let term = self.term_map.get(&sym).ok_or_else(|| { + tyenv.error( + ext.pos, + "Extractor macro body definition on a non-existent term".to_string(), + ) + })?; + let termdata = &mut self.terms[term.index()]; + match &termdata.kind { + &TermKind::Declared => { + termdata.kind = TermKind::InternalExtractor { + args: ext.args.clone(), + template: ext.template.clone(), + }; + } + _ => { + return Err(tyenv.error( + ext.pos, + "Extractor macro body defined on term of incorrect kind" + .to_string(), + )); + } + } + } + _ => {} + } + } + + Ok(()) + } + fn collect_rules(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { for def in &defs.defs { match def { @@ -431,9 +637,11 @@ impl TermEnv { &rule.pattern, None, &mut bindings, + None, )?; let rhs = self.translate_expr(tyenv, rule.pos, &rule.expr, ty, &mut bindings)?; + let rid = RuleId(self.rules.len()); self.rules.push(Rule { id: rid, @@ -459,35 +667,27 @@ impl TermEnv { )) } }; - match &mut self.terms[term_id.index()].kind { - &mut TermKind::EnumVariant { .. } => { + let termdata = &mut self.terms[term_id.index()]; + match &termdata.kind { + &TermKind::Declared => { + termdata.kind = TermKind::ExternalConstructor { name: func_sym }; + } + _ => { return Err(tyenv.error( pos, - format!("Constructor defined on enum type '{}'", term.0), + format!( + "Constructor defined on term of improper type '{}'", + term.0 + ), )); } - &mut TermKind::Regular { - ref mut constructor, - .. - } => { - if constructor.is_some() { - return Err(tyenv.error( - pos, - format!( - "Constructor defined more than once on term '{}'", - term.0 - ), - )); - } - *constructor = Some(func_sym); - } } } &ast::Def::Extern(ast::Extern::Extractor { ref term, ref func, pos, - infallible, + ref arg_polarity, }) => { let term_sym = tyenv.intern_mut(term); let func_sym = tyenv.intern_mut(func); @@ -500,27 +700,31 @@ impl TermEnv { )) } }; - match &mut self.terms[term_id.index()].kind { - &mut TermKind::EnumVariant { .. } => { + + let termdata = &mut self.terms[term_id.index()]; + + let arg_polarity = if let Some(pol) = arg_polarity.as_ref() { + if pol.len() != termdata.arg_tys.len() { + return Err(tyenv.error(pos, "Incorrect number of argument-polarity directions in extractor definition".to_string())); + } + pol.clone() + } else { + vec![ArgPolarity::Output; termdata.arg_tys.len()] + }; + + match &termdata.kind { + &TermKind::Declared => { + termdata.kind = TermKind::ExternalExtractor { + name: func_sym, + arg_polarity, + }; + } + _ => { return Err(tyenv.error( pos, - format!("Extractor defined on enum type '{}'", term.0), + format!("Extractor defined on term of improper type '{}'", term.0), )); } - &mut TermKind::Regular { - ref mut extractor, .. - } => { - if extractor.is_some() { - return Err(tyenv.error( - pos, - format!( - "Extractor defined more than once on term '{}'", - term.0 - ), - )); - } - *extractor = Some((func_sym, infallible)); - } } } _ => {} @@ -537,7 +741,10 @@ impl TermEnv { pat: &ast::Pattern, expected_ty: Option, bindings: &mut Bindings, + macro_args: Option<&HashMap>, ) -> SemaResult<(Pattern, TypeId)> { + log::trace!("translate_pattern: {:?}", pat); + log::trace!("translate_pattern: bindings = {:?}", bindings); match pat { // TODO: flag on primitive type decl indicating it's an integer type? &ast::Pattern::ConstInt { val } => { @@ -556,8 +763,14 @@ impl TermEnv { let mut expected_ty = expected_ty; let mut children = vec![]; for subpat in subpats { - let (subpat, ty) = - self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; + let (subpat, ty) = self.translate_pattern( + tyenv, + pos, + &*subpat, + expected_ty, + bindings, + macro_args, + )?; expected_ty = expected_ty.or(Some(ty)); children.push(subpat); } @@ -571,9 +784,29 @@ impl TermEnv { ref var, ref subpat, } => { + // Handle macro-arg substitution. + if macro_args.is_some() && &**subpat == &ast::Pattern::Wildcard { + if let Some(macro_ast) = macro_args.as_ref().unwrap().get(var) { + return self.translate_pattern( + tyenv, + pos, + macro_ast, + expected_ty, + bindings, + macro_args, + ); + } + } + // Do the subpattern first so we can resolve the type for sure. - let (subpat, ty) = - self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; + let (subpat, ty) = self.translate_pattern( + tyenv, + pos, + &*subpat, + expected_ty, + bindings, + macro_args, + )?; let name = tyenv.intern_mut(var); if bindings.vars.iter().any(|bv| bv.name == name) { @@ -644,12 +877,85 @@ impl TermEnv { )); } + let termdata = &self.terms[tid.index()]; + + match &termdata.kind { + &TermKind::EnumVariant { .. } => { + for arg in args { + if let &ast::TermArgPattern::Expr(..) = arg { + return Err(tyenv.error(pos, format!("Term in pattern '{}' cannot have an injected expr, because it is an enum variant", sym.0))); + } + } + } + &TermKind::ExternalExtractor { + ref arg_polarity, .. + } => { + for (arg, pol) in args.iter().zip(arg_polarity.iter()) { + match (arg, pol) { + (&ast::TermArgPattern::Expr(..), &ArgPolarity::Input) => {} + (&ast::TermArgPattern::Expr(..), &ArgPolarity::Output) => { + return Err(tyenv.error( + pos, + "Expression used for output-polarity extractor arg" + .to_string(), + )); + } + (_, &ArgPolarity::Output) => {} + (_, &ArgPolarity::Input) => { + return Err(tyenv.error(pos, "Non-expression used in pattern but expression required for input-polarity extractor arg".to_string())); + } + } + } + } + &TermKind::InternalExtractor { + args: ref template_args, + ref template, + } => { + // Expand the extractor macro! We create a map + // from macro args to AST pattern trees and + // then evaluate the template with these + // substitutions. + let mut arg_map = HashMap::new(); + for (template_arg, sub_ast) in template_args.iter().zip(args.iter()) { + let sub_ast = match sub_ast { + &ast::TermArgPattern::Pattern(ref pat) => pat.clone(), + &ast::TermArgPattern::Expr(_) => { + return Err(tyenv.error(pos, "Cannot expand an extractor macro with an expression in a macro argument".to_string())); + } + }; + arg_map.insert(template_arg.clone(), sub_ast.clone()); + } + log::trace!("internal extractor map = {:?}", arg_map); + return self.translate_pattern( + tyenv, + pos, + template, + expected_ty, + bindings, + Some(&arg_map), + ); + } + &TermKind::ExternalConstructor { .. } | &TermKind::InternalConstructor => { + // OK. + } + &TermKind::Declared => { + return Err(tyenv + .error(pos, format!("Declared but undefined term '{}' used", sym.0))); + } + } + // Resolve subpatterns. let mut subpats = vec![]; for (i, arg) in args.iter().enumerate() { let arg_ty = self.terms[tid.index()].arg_tys[i]; - let (subpat, _) = - self.translate_pattern(tyenv, pos, arg, Some(arg_ty), bindings)?; + let (subpat, _) = self.translate_pattern_term_arg( + tyenv, + pos, + arg, + Some(arg_ty), + bindings, + macro_args, + )?; subpats.push(subpat); } @@ -658,6 +964,35 @@ impl TermEnv { } } + fn translate_pattern_term_arg( + &self, + tyenv: &mut TypeEnv, + pos: Pos, + pat: &ast::TermArgPattern, + expected_ty: Option, + bindings: &mut Bindings, + macro_args: Option<&HashMap>, + ) -> SemaResult<(TermArgPattern, TypeId)> { + match pat { + &ast::TermArgPattern::Pattern(ref pat) => { + let (subpat, ty) = + self.translate_pattern(tyenv, pos, pat, expected_ty, bindings, macro_args)?; + Ok((TermArgPattern::Pattern(subpat), ty)) + } + &ast::TermArgPattern::Expr(ref expr) => { + if expected_ty.is_none() { + return Err(tyenv.error( + pos, + "Expression in pattern must have expected type".to_string(), + )); + } + let ty = expected_ty.unwrap(); + let expr = self.translate_expr(tyenv, pos, expr, expected_ty.unwrap(), bindings)?; + Ok((TermArgPattern::Expr(expr), ty)) + } + } + } + fn translate_expr( &self, tyenv: &mut TypeEnv, @@ -867,33 +1202,4 @@ mod test { ] ); } - - #[test] - fn build_rules() { - let text = r" - (type u32 (primitive u32)) - (type A extern (enum (B (f1 u32) (f2 u32)) (C (f1 u32)))) - - (decl T1 (A) u32) - (decl T2 (A A) A) - (decl T3 (u32) A) - - (constructor T1 t1_ctor) - (extractor T2 t2_etor) - - (rule - (T1 _) 1) - (rule - (T2 x =x) (T3 42)) - (rule - (T3 1) (A.C 2)) - (rule -1 - (T3 _) (A.C 3)) - "; - let ast = Parser::new(Lexer::from_str(text, "file.isle")) - .parse_defs() - .expect("should parse"); - let mut tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); - let _ = TermEnv::from_ast(&mut tyenv, &ast).expect("could not typecheck rules"); - } } From a412cce615de538086f6b14fd565d2be614a566e Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 15:33:15 -0700 Subject: [PATCH 25/95] Infallible extractors, and some fixes to fallibility in return types (Option vs T). --- cranelift/isle/TODO | 14 ++--- cranelift/isle/isle_examples/test3.isle | 4 +- cranelift/isle/src/ast.rs | 5 ++ cranelift/isle/src/codegen.rs | 71 +++++++++++++++++++----- cranelift/isle/src/ir.rs | 72 ++++++++++++++++++++++--- cranelift/isle/src/parser.rs | 10 ++++ cranelift/isle/src/sema.rs | 9 ++++ 7 files changed, 156 insertions(+), 29 deletions(-) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index b6e0acff40..0e7f3f3759 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -1,8 +1,10 @@ -- Optimizations - - Infallible patterns; optimize away control flow when possible. - - Don't do the closure-wrapping thing for expressions inside of patterns. +- Document the semantics of the DSL! -- Document semantics carefully, especially wrt extractors. +- Clean up and factor the codegen properly. -- Build out an initial set of bindings for Cranelift LowerCtx with extractors - for instruction info. +- Look into whether optimizations are possible: + - More in-depth fallibility analysis (avoid failure edges where possible) + +- Slightly nicer human-readable generated code + - Include full rule body (S-expression) in comment, not just line number + - Inline some expressions (no more `let val23 = 1234; ... f(val23);`) \ No newline at end of file diff --git a/cranelift/isle/isle_examples/test3.isle b/cranelift/isle/isle_examples/test3.isle index df4c7337cd..e13c21d0f8 100644 --- a/cranelift/isle/isle_examples/test3.isle +++ b/cranelift/isle/isle_examples/test3.isle @@ -10,10 +10,10 @@ (type u32 (primitive u32)) (decl Op (Opcode) Inst) -(extern extractor Op get_opcode) +(extern extractor infallible Op get_opcode) (decl InstInput (InstInput u32) Inst) -(extern extractor InstInput get_inst_input (out in)) +(extern extractor infallible InstInput get_inst_input (out in)) (decl Producer (Inst) InstInput) (extern extractor Producer get_input_producer) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index 8742aaa166..a0d6258485 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -171,6 +171,11 @@ pub enum Extern { /// more precisely the term value that we are extracting, is /// an "input"). arg_polarity: Option>, + /// Infallibility: if an external extractor returns `(T1, T2, + /// ...)` rather than `Option<(T1, T2, ...)>`, and hence can + /// never fail, it is declared as such and allows for slightly + /// better code to be generated. + infallible: bool, }, /// An external constructor: `(constructor Term rustfunc)` form. Constructor { diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index 3e29005926..bb334e4f47 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -567,7 +567,7 @@ impl<'a> Codegen<'a> { ) -> Result<(), Error> { writeln!( code, - "{}fn {}(&mut self, {}) -> Option<({},)>;", + "{}fn {}(&mut self, {}) -> {}({},){};", indent, sig.func_name, sig.arg_tys @@ -576,11 +576,13 @@ impl<'a> Codegen<'a> { .map(|(i, &ty)| format!("arg{}: {}", i, self.type_name(ty, /* by_ref = */ true))) .collect::>() .join(", "), + if sig.infallible { "" } else { "Option<" }, sig.ret_tys .iter() .map(|&ty| self.type_name(ty, /* by_ref = */ false)) .collect::>() - .join(", ") + .join(", "), + if sig.infallible { "" } else { ">" }, )?; Ok(()) } @@ -822,7 +824,10 @@ impl<'a> Codegen<'a> { self.define_val(&output, ctx, /* is_ref = */ false, ty); } &ExprInst::Construct { - ref inputs, term, .. + ref inputs, + term, + infallible, + .. } => { let mut input_exprs = vec![]; for (input_value, _) in inputs { @@ -838,13 +843,15 @@ impl<'a> Codegen<'a> { let termdata = &self.termenv.terms[term.index()]; let sig = termdata.to_sig(self.typeenv).unwrap(); assert_eq!(input_exprs.len(), sig.arg_tys.len()); + let fallible_try = if infallible { "" } else { "?" }; writeln!( code, - "{}let {} = {}(ctx, {});", + "{}let {} = {}(ctx, {}){};", indent, outputname, sig.full_name, input_exprs.join(", "), + fallible_try, )?; self.define_val(&output, ctx, /* is_ref = */ false, termdata.ret_ty); } @@ -905,7 +912,6 @@ impl<'a> Codegen<'a> { _ => true, }; writeln!(code, "{}let {} = arg{};", indent, outputname, index)?; - writeln!(code, "{}{{", indent)?; self.define_val( &Value::Pattern { inst: id, @@ -961,6 +967,7 @@ impl<'a> Codegen<'a> { ref inputs, ref output_tys, term, + infallible, .. } => { let termdata = &self.termenv.terms[term.index()]; @@ -983,18 +990,51 @@ impl<'a> Codegen<'a> { }) .collect::>(); + if infallible { + writeln!( + code, + "{}let ({},) = {}(ctx, {});", + indent, + output_binders.join(", "), + sig.full_name, + input_values.join(", "), + )?; + Ok(true) + } else { + writeln!( + code, + "{}if let Some(({},)) = {}(ctx, {}) {{", + indent, + output_binders.join(", "), + sig.full_name, + input_values.join(", "), + )?; + Ok(false) + } + } + &PatternInst::Expr { + ref seq, output_ty, .. + } if seq.is_const_int().is_some() => { + let (ty, val) = seq.is_const_int().unwrap(); + assert_eq!(ty, output_ty); + + let output = Value::Pattern { + inst: id, + output: 0, + }; writeln!( code, - "{}if let Some(({},)) = {}(ctx, {}) {{", + "{}let {} = {};", indent, - output_binders.join(", "), - sig.full_name, - input_values.join(", "), + self.value_name(&output), + val )?; - - Ok(false) + self.define_val(&output, ctx, /* is_ref = */ false, ty); + Ok(true) } - &PatternInst::Expr { ref seq, output_ty, .. } => { + &PatternInst::Expr { + ref seq, output_ty, .. + } => { let closure_name = format!("closure{}", id.index()); writeln!(code, "{}let {} = || {{", indent, closure_name)?; let subindent = format!("{} ", indent); @@ -1124,9 +1164,12 @@ impl<'a> Codegen<'a> { let id = InstId(depth); let infallible = self.generate_pattern_inst(code, id, op, indent, ctx)?; + let i = if infallible { indent } else { &subindent[..] }; let sub_returned = - self.generate_body(code, depth + 1, node, &subindent, ctx)?; - writeln!(code, "{}}}", indent)?; + self.generate_body(code, depth + 1, node, i, ctx)?; + if !infallible { + writeln!(code, "{}}}", indent)?; + } if infallible && sub_returned { returned = true; break; diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index 19802b4f1e..a8f88dff6f 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -50,6 +50,7 @@ pub enum PatternInst { input_tys: Vec, output_tys: Vec, term: TermId, + infallible: bool, }, /// Evaluate an expression and provide the given value as the @@ -80,6 +81,7 @@ pub enum ExprInst { inputs: Vec<(Value, TypeId)>, ty: TypeId, term: TermId, + infallible: bool, }, /// Set the Nth return value. Produces no values. @@ -130,6 +132,34 @@ pub struct ExprSequence { pub pos: Pos, } +impl ExprSequence { + pub fn is_const_int(&self) -> Option<(TypeId, i64)> { + if self.insts.len() == 2 && matches!(&self.insts[1], &ExprInst::Return { .. }) { + match &self.insts[0] { + &ExprInst::ConstInt { ty, val } => Some((ty, val)), + _ => None, + } + } else { + None + } + } + + pub fn is_const_variant(&self) -> Option<(TypeId, VariantId)> { + if self.insts.len() == 2 && matches!(&self.insts[1], &ExprInst::Return { .. }) { + match &self.insts[0] { + &ExprInst::CreateVariant { + ref inputs, + ty, + variant, + } if inputs.len() == 0 => Some((ty, variant)), + _ => None, + } + } else { + None + } + } +} + #[derive(Clone, Copy, Debug)] enum ValueOrArgs { Value(Value), @@ -195,6 +225,7 @@ impl PatternSequence { input_tys: Vec, output_tys: Vec, term: TermId, + infallible: bool, ) -> Vec { let inst = InstId(self.insts.len()); let mut outs = vec![]; @@ -208,6 +239,7 @@ impl PatternSequence { input_tys, output_tys, term, + infallible, }); outs } @@ -320,7 +352,9 @@ impl PatternSequence { panic!("Should have been expanded away"); } &TermKind::ExternalExtractor { - ref arg_polarity, .. + ref arg_polarity, + infallible, + .. } => { // Evaluate all `input` args. let mut inputs = vec![]; @@ -359,8 +393,8 @@ impl PatternSequence { } // Invoke the extractor. - let arg_values = - self.add_extract(inputs, input_tys, output_tys, term); + let arg_values = self + .add_extract(inputs, input_tys, output_tys, term, infallible); for (pat, &val) in output_pats.iter().zip(arg_values.iter()) { self.gen_pattern( @@ -417,10 +451,21 @@ impl ExprSequence { Value::Expr { inst, output: 0 } } - fn add_construct(&mut self, inputs: &[(Value, TypeId)], ty: TypeId, term: TermId) -> Value { + fn add_construct( + &mut self, + inputs: &[(Value, TypeId)], + ty: TypeId, + term: TermId, + infallible: bool, + ) -> Value { let inst = InstId(self.insts.len()); let inputs = inputs.iter().cloned().collect(); - self.add_inst(ExprInst::Construct { inputs, ty, term }); + self.add_inst(ExprInst::Construct { + inputs, + ty, + term, + infallible, + }); Value::Expr { inst, output: 0 } } @@ -469,8 +514,21 @@ impl ExprSequence { &TermKind::EnumVariant { variant } => { self.add_create_variant(&arg_values_tys[..], ty, variant) } - &TermKind::InternalConstructor | &TermKind::ExternalConstructor { .. } => { - self.add_construct(&arg_values_tys[..], ty, term) + &TermKind::InternalConstructor => { + self.add_construct( + &arg_values_tys[..], + ty, + term, + /* infallible = */ true, + ) + } + &TermKind::ExternalConstructor { .. } => { + self.add_construct( + &arg_values_tys[..], + ty, + term, + /* infallible = */ false, + ) } _ => panic!("Should have been caught by typechecking"), } diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index 7ec4fdc0ea..c872dd28d5 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -256,8 +256,17 @@ impl<'a> Parser<'a> { }) } else if self.is_sym_str("extractor") { self.symbol()?; + + let infallible = if self.is_sym_str("infallible") { + self.symbol()?; + true + } else { + false + }; + let term = self.parse_ident()?; let func = self.parse_ident()?; + let arg_polarity = if self.is_lparen() { let mut pol = vec![]; self.lparen()?; @@ -284,6 +293,7 @@ impl<'a> Parser<'a> { func, pos: pos.unwrap(), arg_polarity, + infallible, }) } else { Err(self.error( diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index 72aa779c69..fe55369b70 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -120,6 +120,8 @@ pub enum TermKind { name: Sym, /// Which arguments of the extractor are inputs and which are outputs? arg_polarity: Vec, + /// Is the external extractor infallible? + infallible: bool, }, /// A term defined solely by an external constructor function. ExternalConstructor { @@ -138,6 +140,7 @@ pub struct ExternalSig { pub full_name: String, pub arg_tys: Vec, pub ret_tys: Vec, + pub infallible: bool, } impl Term { @@ -180,10 +183,12 @@ impl Term { full_name: format!("C::{}", tyenv.syms[name.index()]), arg_tys: self.arg_tys.clone(), ret_tys: vec![self.ret_ty], + infallible: true, }), &TermKind::ExternalExtractor { name, ref arg_polarity, + infallible, } => { let mut arg_tys = vec![]; let mut ret_tys = vec![]; @@ -203,6 +208,7 @@ impl Term { full_name: format!("C::{}", tyenv.syms[name.index()]), arg_tys, ret_tys, + infallible, }) } &TermKind::InternalConstructor { .. } => { @@ -212,6 +218,7 @@ impl Term { full_name: name, arg_tys: self.arg_tys.clone(), ret_tys: vec![self.ret_ty], + infallible: false, }) } _ => None, @@ -688,6 +695,7 @@ impl TermEnv { ref func, pos, ref arg_polarity, + infallible, }) => { let term_sym = tyenv.intern_mut(term); let func_sym = tyenv.intern_mut(func); @@ -717,6 +725,7 @@ impl TermEnv { termdata.kind = TermKind::ExternalExtractor { name: func_sym, arg_polarity, + infallible, }; } _ => { From edc95c51a012ce81414bf000d13d1f01e43dc17c Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 16:26:35 -0700 Subject: [PATCH 26/95] Support for bools. Also fix fallible/infallible mixup for ctors. --- cranelift/isle/isle_examples/test4.isle | 6 +++++- cranelift/isle/src/codegen.rs | 25 ++++++++++++++++++++++--- cranelift/isle/src/ir.rs | 4 ++-- cranelift/isle/src/parser.rs | 6 ++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/cranelift/isle/isle_examples/test4.isle b/cranelift/isle/isle_examples/test4.isle index a899122bbb..4a8457e886 100644 --- a/cranelift/isle/isle_examples/test4.isle +++ b/cranelift/isle/isle_examples/test4.isle @@ -1,4 +1,5 @@ (type u32 (primitive u32)) +(type bool (primitive bool)) (type A (enum (A1 (x u32)))) (decl Ext1 (u32) A) @@ -6,6 +7,9 @@ (extern extractor Ext1 ext1) (extern extractor Ext2 ext2) +(decl C (bool) A) +(extern constructor C c) + (decl Lower (A) A) (rule @@ -14,4 +18,4 @@ a (Ext1 x) (Ext2 =x))) - a) + (C #t)) diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/src/codegen.rs index bb334e4f47..5eed4b7cb4 100644 --- a/cranelift/isle/src/codegen.rs +++ b/cranelift/isle/src/codegen.rs @@ -711,6 +711,18 @@ impl<'a> Codegen<'a> { ctx.values.insert(value.clone(), (is_ref, ty)); } + fn const_int(&self, val: i64, ty: TypeId) -> String { + let is_bool = match &self.typeenv.types[ty.index()] { + &Type::Primitive(_, name) => &self.typeenv.syms[name.index()] == "bool", + _ => unreachable!(), + }; + if is_bool { + format!("{}", val != 0) + } else { + format!("{}", val) + } + } + fn generate_internal_term_constructors(&self, code: &mut dyn Write) -> Result<(), Error> { for (&termid, trie) in &self.functions_by_term { let termdata = &self.termenv.terms[termid.index()]; @@ -775,8 +787,15 @@ impl<'a> Codegen<'a> { }; self.define_val(&value, ctx, /* is_ref = */ false, ty); let name = self.value_name(&value); - let ty = self.type_name(ty, /* by_ref = */ false); - writeln!(code, "{}let {}: {} = {};", indent, name, ty, val)?; + let ty_name = self.type_name(ty, /* by_ref = */ false); + writeln!( + code, + "{}let {}: {} = {};", + indent, + name, + ty_name, + self.const_int(val, ty) + )?; } &ExprInst::CreateVariant { ref inputs, @@ -1027,7 +1046,7 @@ impl<'a> Codegen<'a> { "{}let {} = {};", indent, self.value_name(&output), - val + self.const_int(val, ty), )?; self.define_val(&output, ctx, /* is_ref = */ false, ty); Ok(true) diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index a8f88dff6f..d9657297ea 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -519,7 +519,7 @@ impl ExprSequence { &arg_values_tys[..], ty, term, - /* infallible = */ true, + /* infallible = */ false, ) } &TermKind::ExternalConstructor { .. } => { @@ -527,7 +527,7 @@ impl ExprSequence { &arg_values_tys[..], ty, term, - /* infallible = */ false, + /* infallible = */ true, ) } _ => panic!("Should have been caught by typechecking"), diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index c872dd28d5..fc21f728de 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -423,6 +423,12 @@ impl<'a> Parser<'a> { self.rparen()?; Ok(Expr::Term { sym, args }) } + } else if self.is_sym_str("#t") { + self.symbol()?; + Ok(Expr::ConstInt { val: 1 }) + } else if self.is_sym_str("#f") { + self.symbol()?; + Ok(Expr::ConstInt { val: 0 }) } else if self.is_sym() { let name = self.parse_ident()?; Ok(Expr::Var { name }) From 3f96068f948d6e073f68b6af16e32a442c032c6a Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 16:32:28 -0700 Subject: [PATCH 27/95] Some test cases for arg-less enums. --- cranelift/isle/isle_examples/test4.isle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cranelift/isle/isle_examples/test4.isle b/cranelift/isle/isle_examples/test4.isle index 4a8457e886..0e1f45901f 100644 --- a/cranelift/isle/isle_examples/test4.isle +++ b/cranelift/isle/isle_examples/test4.isle @@ -19,3 +19,16 @@ (Ext1 x) (Ext2 =x))) (C #t)) + +(type Opcode (enum A B C)) +(type MachInst (enum D E F)) +(decl Lower2 (Opcode) MachInst) +(rule + (Lower2 (Opcode.A)) + (MachInst.D)) +(rule + (Lower2 (Opcode.B)) + (MachInst.E)) +(rule + (Lower2 (Opcode.C)) + (MachInst.F)) From 5a8e35b253d41f11be4e6562642d954769f5b276 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 18:12:46 -0700 Subject: [PATCH 28/95] Some fixes to the internal-extractor macro substitution --- cranelift/isle/src/ast.rs | 98 ++++++++++++++++++++++++++++++++++++++ cranelift/isle/src/sema.rs | 89 +++++++++------------------------- 2 files changed, 120 insertions(+), 67 deletions(-) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index a0d6258485..8f1cd820c4 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -99,6 +99,8 @@ pub enum Pattern { Wildcard, /// N sub-patterns that must all match. And { subpats: Vec }, + /// Internal use only: macro argument in a template. + MacroArg { index: usize }, } impl Pattern { @@ -109,6 +111,81 @@ impl Pattern { _ => None, } } + + pub fn make_macro_template(&self, macro_args: &[Ident]) -> Pattern { + log::trace!("repplace_macro_args: {:?} with {:?}", self, macro_args); + match self { + &Pattern::BindPattern { + ref var, + ref subpat, + } if matches!(&**subpat, &Pattern::Wildcard) => { + if let Some(i) = macro_args.iter().position(|arg| arg == var) { + Pattern::MacroArg { index: i } + } else { + self.clone() + } + } + &Pattern::BindPattern { + ref var, + ref subpat, + } => Pattern::BindPattern { + var: var.clone(), + subpat: Box::new(subpat.make_macro_template(macro_args)), + }, + &Pattern::And { ref subpats } => { + let subpats = subpats + .iter() + .map(|subpat| subpat.make_macro_template(macro_args)) + .collect::>(); + Pattern::And { subpats } + } + &Pattern::Term { ref sym, ref args } => { + let args = args + .iter() + .map(|arg| arg.make_macro_template(macro_args)) + .collect::>(); + Pattern::Term { + sym: sym.clone(), + args, + } + } + + &Pattern::Var { .. } | &Pattern::Wildcard | &Pattern::ConstInt { .. } => self.clone(), + &Pattern::MacroArg { .. } => unreachable!(), + } + } + + pub fn subst_macro_args(&self, macro_args: &[Pattern]) -> Pattern { + match self { + &Pattern::BindPattern { + ref var, + ref subpat, + } => Pattern::BindPattern { + var: var.clone(), + subpat: Box::new(subpat.subst_macro_args(macro_args)), + }, + &Pattern::And { ref subpats } => { + let subpats = subpats + .iter() + .map(|subpat| subpat.subst_macro_args(macro_args)) + .collect::>(); + Pattern::And { subpats } + } + &Pattern::Term { ref sym, ref args } => { + let args = args + .iter() + .map(|arg| arg.subst_macro_args(macro_args)) + .collect::>(); + Pattern::Term { + sym: sym.clone(), + args, + } + } + + &Pattern::Var { .. } | &Pattern::Wildcard | &Pattern::ConstInt { .. } => self.clone(), + &Pattern::MacroArg { index } => macro_args[index].clone(), + } + } } /// A pattern in a term argument. Adds "evaluated expression" to kinds @@ -125,6 +202,27 @@ pub enum TermArgPattern { Expr(Expr), } +impl TermArgPattern { + fn make_macro_template(&self, args: &[Ident]) -> TermArgPattern { + log::trace!("repplace_macro_args: {:?} with {:?}", self, args); + match self { + &TermArgPattern::Pattern(ref pat) => { + TermArgPattern::Pattern(pat.make_macro_template(args)) + } + &TermArgPattern::Expr(_) => self.clone(), + } + } + + fn subst_macro_args(&self, args: &[Pattern]) -> TermArgPattern { + match self { + &TermArgPattern::Pattern(ref pat) => { + TermArgPattern::Pattern(pat.subst_macro_args(args)) + } + &TermArgPattern::Expr(_) => self.clone(), + } + } +} + /// An expression: the right-hand side of a rule. /// /// Note that this *almost* looks like a core Lisp or lambda calculus, diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index fe55369b70..df5854c2c4 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -110,10 +110,7 @@ pub enum TermKind { /// A term that defines an "extractor macro" in the LHS of a /// pattern. Its arguments take patterns and are simply /// substituted with the given patterns when used. - InternalExtractor { - args: Vec, - template: ast::Pattern, - }, + InternalExtractor { template: ast::Pattern }, /// A term defined solely by an external extractor function. ExternalExtractor { /// Extractor func. @@ -605,12 +602,11 @@ impl TermEnv { ) })?; let termdata = &mut self.terms[term.index()]; + let template = ext.template.make_macro_template(&ext.args[..]); + log::trace!("extractor def: {:?} becomes template {:?}", def, template); match &termdata.kind { &TermKind::Declared => { - termdata.kind = TermKind::InternalExtractor { - args: ext.args.clone(), - template: ext.template.clone(), - }; + termdata.kind = TermKind::InternalExtractor { template }; } _ => { return Err(tyenv.error( @@ -644,7 +640,6 @@ impl TermEnv { &rule.pattern, None, &mut bindings, - None, )?; let rhs = self.translate_expr(tyenv, rule.pos, &rule.expr, ty, &mut bindings)?; @@ -750,7 +745,6 @@ impl TermEnv { pat: &ast::Pattern, expected_ty: Option, bindings: &mut Bindings, - macro_args: Option<&HashMap>, ) -> SemaResult<(Pattern, TypeId)> { log::trace!("translate_pattern: {:?}", pat); log::trace!("translate_pattern: bindings = {:?}", bindings); @@ -772,14 +766,8 @@ impl TermEnv { let mut expected_ty = expected_ty; let mut children = vec![]; for subpat in subpats { - let (subpat, ty) = self.translate_pattern( - tyenv, - pos, - &*subpat, - expected_ty, - bindings, - macro_args, - )?; + let (subpat, ty) = + self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; expected_ty = expected_ty.or(Some(ty)); children.push(subpat); } @@ -793,29 +781,9 @@ impl TermEnv { ref var, ref subpat, } => { - // Handle macro-arg substitution. - if macro_args.is_some() && &**subpat == &ast::Pattern::Wildcard { - if let Some(macro_ast) = macro_args.as_ref().unwrap().get(var) { - return self.translate_pattern( - tyenv, - pos, - macro_ast, - expected_ty, - bindings, - macro_args, - ); - } - } - // Do the subpattern first so we can resolve the type for sure. - let (subpat, ty) = self.translate_pattern( - tyenv, - pos, - &*subpat, - expected_ty, - bindings, - macro_args, - )?; + let (subpat, ty) = + self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; let name = tyenv.intern_mut(var); if bindings.vars.iter().any(|bv| bv.name == name) { @@ -826,6 +794,7 @@ impl TermEnv { } let id = VarId(bindings.next_var); bindings.next_var += 1; + log::trace!("binding var {:?}", var.0); bindings.vars.push(BoundVar { name, id, ty }); Ok((Pattern::BindPattern(ty, id, Box::new(subpat)), ty)) @@ -916,33 +885,24 @@ impl TermEnv { } } } - &TermKind::InternalExtractor { - args: ref template_args, - ref template, - } => { + &TermKind::InternalExtractor { ref template } => { // Expand the extractor macro! We create a map // from macro args to AST pattern trees and // then evaluate the template with these // substitutions. - let mut arg_map = HashMap::new(); - for (template_arg, sub_ast) in template_args.iter().zip(args.iter()) { - let sub_ast = match sub_ast { + let mut macro_args: Vec = vec![]; + for template_arg in args { + let sub_ast = match template_arg { &ast::TermArgPattern::Pattern(ref pat) => pat.clone(), &ast::TermArgPattern::Expr(_) => { return Err(tyenv.error(pos, "Cannot expand an extractor macro with an expression in a macro argument".to_string())); } }; - arg_map.insert(template_arg.clone(), sub_ast.clone()); + macro_args.push(sub_ast.clone()); } - log::trace!("internal extractor map = {:?}", arg_map); - return self.translate_pattern( - tyenv, - pos, - template, - expected_ty, - bindings, - Some(&arg_map), - ); + log::trace!("internal extractor macro args = {:?}", args); + let pat = template.subst_macro_args(¯o_args[..]); + return self.translate_pattern(tyenv, pos, &pat, expected_ty, bindings); } &TermKind::ExternalConstructor { .. } | &TermKind::InternalConstructor => { // OK. @@ -957,19 +917,14 @@ impl TermEnv { let mut subpats = vec![]; for (i, arg) in args.iter().enumerate() { let arg_ty = self.terms[tid.index()].arg_tys[i]; - let (subpat, _) = self.translate_pattern_term_arg( - tyenv, - pos, - arg, - Some(arg_ty), - bindings, - macro_args, - )?; + let (subpat, _) = + self.translate_pattern_term_arg(tyenv, pos, arg, Some(arg_ty), bindings)?; subpats.push(subpat); } Ok((Pattern::Term(ty, *tid, subpats), ty)) } + &ast::Pattern::MacroArg { .. } => unreachable!(), } } @@ -980,12 +935,11 @@ impl TermEnv { pat: &ast::TermArgPattern, expected_ty: Option, bindings: &mut Bindings, - macro_args: Option<&HashMap>, ) -> SemaResult<(TermArgPattern, TypeId)> { match pat { &ast::TermArgPattern::Pattern(ref pat) => { let (subpat, ty) = - self.translate_pattern(tyenv, pos, pat, expected_ty, bindings, macro_args)?; + self.translate_pattern(tyenv, pos, pat, expected_ty, bindings)?; Ok((TermArgPattern::Pattern(subpat), ty)) } &ast::TermArgPattern::Expr(ref expr) => { @@ -1010,6 +964,7 @@ impl TermEnv { ty: TypeId, bindings: &mut Bindings, ) -> SemaResult { + log::trace!("translate_expr: {:?}", expr); match expr { &ast::Expr::Term { ref sym, ref args } => { // Look up the term. From af9e01a4569f34b2eb41e0876d25c4a1cdc06c94 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 18:33:18 -0700 Subject: [PATCH 29/95] Remove old .gitmodules -- no longer using wasmtime as a submodule --- cranelift/isle/.gitmodules | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 cranelift/isle/.gitmodules diff --git a/cranelift/isle/.gitmodules b/cranelift/isle/.gitmodules deleted file mode 100644 index dd607ad0d9..0000000000 --- a/cranelift/isle/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "wasmtime"] - path = wasmtime - url = https://github.com/cfallin/wasmtime From b46fa6acb0a1928e850c71442e95c06a93403b8e Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 21:51:12 -0700 Subject: [PATCH 30/95] Remove old/no longer used `lower.rs` --- cranelift/isle/src/lower.rs | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 cranelift/isle/src/lower.rs diff --git a/cranelift/isle/src/lower.rs b/cranelift/isle/src/lower.rs deleted file mode 100644 index 52736d4f34..0000000000 --- a/cranelift/isle/src/lower.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::ir::*; -use crate::sema; - -struct LowerState<'a> { - tyenv: &'a sema::TypeEnv, - func: &'a sema::Func, - builder: FuncBuilder, - control_flow: ControlInput, -} - -pub fn lower(tyenv: &sema::TypeEnv, func: &sema::Func) -> Func { - let mut builder = FuncBuilder::default(); - let entry = builder.intern(Node::Entry); - - let mut state = LowerState { - tyenv, - func, - builder, - control_flow: ControlInput(entry, 0), - }; - - if !func.is_extern && !func.is_inline { - for case in &func.cases { - state.lower_case(case); - } - } - - state.builder.build() -} - -impl<'a> LowerState<'a> { - fn lower_case(&mut self) {} -} From 7d38b3b6d86d302a352673e16102279e8fd129c6 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 23:27:25 -0700 Subject: [PATCH 31/95] Revamped error handling: keep going beyond some errors to accumulate a list of build errors, like most conventional compilers. --- cranelift/isle/Cargo.lock | 116 ------- cranelift/isle/Cargo.toml | 3 +- cranelift/isle/isle_examples/error1.isle | 36 ++ cranelift/isle/src/compile.rs | 6 +- cranelift/isle/src/error.rs | 76 ++--- cranelift/isle/src/main.rs | 11 +- cranelift/isle/src/parser.rs | 6 +- cranelift/isle/src/sema.rs | 413 +++++++++++++++-------- 8 files changed, 359 insertions(+), 308 deletions(-) create mode 100644 cranelift/isle/isle_examples/error1.isle diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index f37ad00454..26a438a64e 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - [[package]] name = "ansi_term" version = "0.11.0" @@ -64,11 +55,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ - "atty", - "humantime", "log", - "regex", - "termcolor", ] [[package]] @@ -80,12 +67,6 @@ dependencies = [ "libc", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "isle" version = "0.1.0" @@ -93,7 +74,6 @@ dependencies = [ "clap", "env_logger", "log", - "thiserror", ] [[package]] @@ -111,73 +91,12 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "memchr" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" - -[[package]] -name = "proc-macro2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "syn" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - [[package]] name = "textwrap" version = "0.11.0" @@ -187,38 +106,12 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thiserror" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "vec_map" version = "0.8.2" @@ -241,15 +134,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml index d3f43cb1c5..2d7da0ae6f 100644 --- a/cranelift/isle/Cargo.toml +++ b/cranelift/isle/Cargo.toml @@ -7,6 +7,5 @@ license = "Apache-2.0 WITH LLVM-exception" [dependencies] log = "0.4" -env_logger = "0.8" -thiserror = "1.0" +env_logger = { version = "0.8", default-features = false } clap = "2.33" diff --git a/cranelift/isle/isle_examples/error1.isle b/cranelift/isle/isle_examples/error1.isle new file mode 100644 index 0000000000..5d304668f6 --- /dev/null +++ b/cranelift/isle/isle_examples/error1.isle @@ -0,0 +1,36 @@ +(type u32 (primitive u32)) +(type bool (primitive bool)) +(type A (enum (A1 (x u32)))) + +(decl Ext1 (u32) A) +(decl Ext2 (u32) A) +(extern extractor Ext1 ext1) +(extern extractor Ext2 ext2) + +(decl C (bool) A) +(extern constructor C c) + +(decl Lower (A) A) + +(rule + (Lower + (and + a + (Ext1 x) + (Ext2 =q))) + (C y)) + +(type R (enum (A (x u32)))) + +(type Opcode (enum A B C)) +(type MachInst (enum D E F)) +(decl Lower2 (Opcode) MachInst) +(rule + (Lower2 (Opcode.A)) + (R.A (Opcode.A))) +(rule + (Lower2 (Opcode.B)) + (MachInst.E)) +(rule + (Lower2 (Opcode.C)) + (MachInst.F)) diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/src/compile.rs index 618998980c..8f98b8c1c6 100644 --- a/cranelift/isle/src/compile.rs +++ b/cranelift/isle/src/compile.rs @@ -3,9 +3,9 @@ use crate::error::Error; use crate::{ast, codegen, sema}; -pub fn compile(defs: &ast::Defs) -> Result { +pub fn compile(defs: &ast::Defs) -> Result> { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; - let codegen = codegen::Codegen::compile(&typeenv, &termenv)?; - codegen.generate_rust() + let codegen = codegen::Codegen::compile(&typeenv, &termenv).map_err(|e| vec![e])?; + codegen.generate_rust().map_err(|e| vec![e]) } diff --git a/cranelift/isle/src/error.rs b/cranelift/isle/src/error.rs index 7fce2df669..a3afd1fce7 100644 --- a/cranelift/isle/src/error.rs +++ b/cranelift/isle/src/error.rs @@ -1,50 +1,50 @@ //! Error types. use crate::lexer::Pos; -use thiserror::Error; +use std::fmt; -#[derive(Debug, Error)] +#[derive(Clone, Debug)] pub enum Error { - #[error("Parse error")] - ParseError(#[from] ParseError), - #[error("Semantic error")] - SemaError(#[from] SemaError), - #[error("IO error")] - IoError(#[from] std::io::Error), - #[error("Formatting error")] - FmtError(#[from] std::fmt::Error), + CompileError { + msg: String, + filename: String, + pos: Pos, + }, + SystemError { + msg: String, + }, } -#[derive(Clone, Debug, Error)] -pub struct ParseError { - pub msg: String, - pub filename: String, - pub pos: Pos, -} - -#[derive(Clone, Debug, Error)] -pub struct SemaError { - pub msg: String, - pub filename: String, - pub pos: Pos, -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}:{}:{}: {}", - self.filename, self.pos.line, self.pos.col, self.msg - ) +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Error::CompileError { + ref msg, + ref filename, + pos, + } => { + write!(f, "{}:{}: {}", filename, pos.line, msg) + } + &Error::SystemError { ref msg } => { + write!(f, "{}", msg) + } + } } } -impl std::fmt::Display for SemaError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}:{}:{}: {}", - self.filename, self.pos.line, self.pos.col, self.msg - ) +impl std::error::Error for Error {} + +impl std::convert::From for Error { + fn from(e: std::fmt::Error) -> Error { + Error::SystemError { + msg: format!("{}", e), + } + } +} +impl std::convert::From for Error { + fn from(e: std::io::Error) -> Error { + Error::SystemError { + msg: format!("{}", e), + } } } diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs index a09e21096a..5f2e168a19 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/main.rs @@ -47,7 +47,16 @@ fn main() -> Result<(), error::Error> { let lexer = lexer::Lexer::from_files(input_files)?; let mut parser = parser::Parser::new(lexer); let defs = parser.parse_defs()?; - let code = compile::compile(&defs)?; + let code = match compile::compile(&defs) { + Ok(code) => code, + Err(errors) => { + for error in errors { + eprintln!("{}", error); + } + eprintln!("Failed to compile."); + std::process::exit(1); + } + }; { use std::io::Write; diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index fc21f728de..8c6b080b10 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -9,15 +9,15 @@ pub struct Parser<'a> { lexer: Lexer<'a>, } -pub type ParseResult = std::result::Result; +pub type ParseResult = std::result::Result; impl<'a> Parser<'a> { pub fn new(lexer: Lexer<'a>) -> Parser<'a> { Parser { lexer } } - pub fn error(&self, pos: Pos, msg: String) -> ParseError { - ParseError { + pub fn error(&self, pos: Pos, msg: String) -> Error { + Error::CompileError { filename: self.lexer.filenames[pos.file].clone(), pos, msg, diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index df5854c2c4..19f136acf0 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -5,7 +5,7 @@ use crate::error::*; use crate::lexer::Pos; use std::collections::HashMap; -pub type SemaResult = std::result::Result; +pub type SemaResult = std::result::Result>; #[macro_export] macro_rules! declare_id { @@ -35,6 +35,7 @@ pub struct TypeEnv { pub sym_map: HashMap, pub types: Vec, pub type_map: HashMap, + pub errors: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -296,6 +297,7 @@ impl TypeEnv { sym_map: HashMap::new(), types: vec![], type_map: HashMap::new(), + errors: vec![], }; // Traverse defs, assigning type IDs to type names. We'll fill @@ -306,10 +308,11 @@ impl TypeEnv { let tid = TypeId(tyenv.type_map.len()); let name = tyenv.intern_mut(&td.name); if tyenv.type_map.contains_key(&name) { - return Err(tyenv.error( + tyenv.report_error( td.pos, format!("Type name defined more than once: '{}'", td.name.0), - )); + ); + continue; } tyenv.type_map.insert(name, tid); } @@ -324,7 +327,12 @@ impl TypeEnv { for def in &defs.defs { match def { &ast::Def::Type(ref td) => { - let ty = tyenv.type_from_ast(TypeId(tid), td)?; + let ty = match tyenv.type_from_ast(TypeId(tid), td) { + Some(ty) => ty, + None => { + continue; + } + }; tyenv.types.push(ty); tid += 1; } @@ -332,13 +340,23 @@ impl TypeEnv { } } + tyenv.return_errors()?; + Ok(tyenv) } - fn type_from_ast(&mut self, tid: TypeId, ty: &ast::Type) -> SemaResult { + fn return_errors(&mut self) -> SemaResult<()> { + if self.errors.len() > 0 { + Err(std::mem::take(&mut self.errors)) + } else { + Ok(()) + } + } + + fn type_from_ast(&mut self, tid: TypeId, ty: &ast::Type) -> Option { let name = self.intern(&ty.name).unwrap(); match &ty.ty { - &ast::TypeValue::Primitive(ref id) => Ok(Type::Primitive(tid, self.intern_mut(id))), + &ast::TypeValue::Primitive(ref id) => Some(Type::Primitive(tid, self.intern_mut(id))), &ast::TypeValue::Enum(ref ty_variants) => { let mut variants = vec![]; for variant in ty_variants { @@ -347,34 +365,37 @@ impl TypeEnv { let name = self.intern_mut(&variant.name); let id = VariantId(variants.len()); if variants.iter().any(|v: &Variant| v.name == name) { - return Err(self.error( + self.report_error( ty.pos, format!("Duplicate variant name in type: '{}'", variant.name.0), - )); + ); + return None; } let mut fields = vec![]; for field in &variant.fields { let field_name = self.intern_mut(&field.name); if fields.iter().any(|f: &Field| f.name == field_name) { - return Err(self.error( + self.report_error( ty.pos, format!( "Duplicate field name '{}' in variant '{}' of type", field.name.0, variant.name.0 ), - )); + ); + return None; } let field_ty = self.intern_mut(&field.ty); let field_tid = match self.type_map.get(&field_ty) { Some(tid) => *tid, None => { - return Err(self.error( + self.report_error( ty.pos, format!( "Unknown type '{}' for field '{}' in variant '{}'", field.ty.0, field.name.0, variant.name.0 ), - )); + ); + return None; } }; fields.push(Field { @@ -390,7 +411,7 @@ impl TypeEnv { fields, }); } - Ok(Type::Enum { + Some(Type::Enum { name, id: tid, is_extern: ty.is_extern, @@ -401,14 +422,19 @@ impl TypeEnv { } } - fn error(&self, pos: Pos, msg: String) -> SemaError { - SemaError { + fn error(&self, pos: Pos, msg: String) -> Error { + Error::CompileError { filename: self.filenames[pos.file].clone(), pos, msg, } } + fn report_error(&mut self, pos: Pos, msg: String) { + let err = self.error(pos, msg); + self.errors.push(err); + } + pub fn intern_mut(&mut self, ident: &ast::Ident) -> Sym { if let Some(s) = self.sym_map.get(&ident.0).cloned() { s @@ -446,24 +472,27 @@ impl TermEnv { rules: vec![], }; - env.collect_term_sigs(tyenv, defs)?; - env.collect_enum_variant_terms(tyenv)?; - env.collect_constructors(tyenv, defs)?; - env.collect_extractor_templates(tyenv, defs)?; - env.collect_rules(tyenv, defs)?; + env.collect_term_sigs(tyenv, defs); + env.collect_enum_variant_terms(tyenv); + env.collect_constructors(tyenv, defs); + env.collect_extractor_templates(tyenv, defs); + tyenv.return_errors()?; + env.collect_rules(tyenv, defs); + tyenv.return_errors()?; Ok(env) } - fn collect_term_sigs(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { + fn collect_term_sigs(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { for def in &defs.defs { match def { &ast::Def::Decl(ref decl) => { let tid = TermId(self.terms.len()); let name = tyenv.intern_mut(&decl.term); if self.term_map.contains_key(&name) { - return Err( - tyenv.error(decl.pos, format!("Duplicate decl for '{}'", decl.term.0)) + tyenv.report_error( + decl.pos, + format!("Duplicate decl for '{}'", decl.term.0), ); } self.term_map.insert(name, tid); @@ -474,18 +503,32 @@ impl TermEnv { .map(|id| { let sym = tyenv.intern_mut(id); tyenv.type_map.get(&sym).cloned().ok_or_else(|| { - tyenv.error(decl.pos, format!("Unknown arg type: '{}'", id.0)) + tyenv.report_error( + decl.pos, + format!("Unknown arg type: '{}'", id.0), + ); + () }) }) - .collect::>>()?; + .collect::, ()>>(); + let arg_tys = match arg_tys { + Ok(a) => a, + Err(_) => { + continue; + } + }; let ret_ty = { let sym = tyenv.intern_mut(&decl.ret_ty); - tyenv.type_map.get(&sym).cloned().ok_or_else(|| { - tyenv.error( - decl.pos, - format!("Unknown return type: '{}'", decl.ret_ty.0), - ) - })? + match tyenv.type_map.get(&sym).cloned() { + Some(t) => t, + None => { + tyenv.report_error( + decl.pos, + format!("Unknown return type: '{}'", decl.ret_ty.0), + ); + continue; + } + } }; self.terms.push(Term { @@ -499,12 +542,11 @@ impl TermEnv { _ => {} } } - - Ok(()) } - fn collect_enum_variant_terms(&mut self, tyenv: &mut TypeEnv) -> SemaResult<()> { - for ty in &tyenv.types { + fn collect_enum_variant_terms(&mut self, tyenv: &mut TypeEnv) { + 'types: for i in 0..tyenv.types.len() { + let ty = &tyenv.types[i]; match ty { &Type::Enum { pos, @@ -514,13 +556,12 @@ impl TermEnv { } => { for variant in variants { if self.term_map.contains_key(&variant.fullname) { - return Err(tyenv.error( + let variant_name = tyenv.syms[variant.fullname.index()].clone(); + tyenv.report_error( pos, - format!( - "Duplicate enum variant constructor: '{}'", - tyenv.syms[variant.fullname.index()] - ), - )); + format!("Duplicate enum variant constructor: '{}'", variant_name,), + ); + continue 'types; } let tid = TermId(self.terms.len()); let arg_tys = variant.fields.iter().map(|fld| fld.ty).collect::>(); @@ -540,11 +581,9 @@ impl TermEnv { _ => {} } } - - Ok(()) } - fn collect_constructors(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { + fn collect_constructors(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { for def in &defs.defs { match def { &ast::Def::Rule(ref rule) => { @@ -552,19 +591,20 @@ impl TermEnv { let term = match rule.pattern.root_term() { Some(t) => t, None => { - return Err(tyenv.error( + tyenv.report_error( pos, "Rule does not have a term at the LHS root".to_string(), - )); + ); + continue; } }; let sym = tyenv.intern_mut(&term); let term = match self.term_map.get(&sym) { Some(&tid) => tid, None => { - return Err( - tyenv.error(pos, "Rule LHS root term is not defined".to_string()) - ); + tyenv + .report_error(pos, "Rule LHS root term is not defined".to_string()); + continue; } }; let termdata = &mut self.terms[term.index()]; @@ -576,31 +616,32 @@ impl TermEnv { // OK, no error; multiple rules can apply to one internal constructor term. } _ => { - return Err(tyenv.error(pos, "Rule LHS root term is incorrect kind; cannot be internal constructor".to_string())); + tyenv.report_error(pos, "Rule LHS root term is incorrect kind; cannot be internal constructor".to_string()); + continue; } } } _ => {} } } - Ok(()) } - fn collect_extractor_templates( - &mut self, - tyenv: &mut TypeEnv, - defs: &ast::Defs, - ) -> SemaResult<()> { + fn collect_extractor_templates(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { for def in &defs.defs { match def { &ast::Def::Extractor(ref ext) => { let sym = tyenv.intern_mut(&ext.term); - let term = self.term_map.get(&sym).ok_or_else(|| { - tyenv.error( - ext.pos, - "Extractor macro body definition on a non-existent term".to_string(), - ) - })?; + let term = match self.term_map.get(&sym) { + Some(x) => x, + None => { + tyenv.report_error( + ext.pos, + "Extractor macro body definition on a non-existent term" + .to_string(), + ); + return; + } + }; let termdata = &mut self.terms[term.index()]; let template = ext.template.make_macro_template(&ext.args[..]); log::trace!("extractor def: {:?} becomes template {:?}", def, template); @@ -609,22 +650,21 @@ impl TermEnv { termdata.kind = TermKind::InternalExtractor { template }; } _ => { - return Err(tyenv.error( + tyenv.report_error( ext.pos, "Extractor macro body defined on term of incorrect kind" .to_string(), - )); + ); + continue; } } } _ => {} } } - - Ok(()) } - fn collect_rules(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult<()> { + fn collect_rules(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { for def in &defs.defs { match def { &ast::Def::Rule(ref rule) => { @@ -634,15 +674,26 @@ impl TermEnv { vars: vec![], }; - let (lhs, ty) = self.translate_pattern( + let (lhs, ty) = match self.translate_pattern( tyenv, rule.pos, &rule.pattern, None, &mut bindings, - )?; + ) { + Some(x) => x, + None => { + // Keep going to collect more errors. + continue; + } + }; let rhs = - self.translate_expr(tyenv, rule.pos, &rule.expr, ty, &mut bindings)?; + match self.translate_expr(tyenv, rule.pos, &rule.expr, ty, &mut bindings) { + Some(x) => x, + None => { + continue; + } + }; let rid = RuleId(self.rules.len()); self.rules.push(Rule { @@ -663,10 +714,11 @@ impl TermEnv { let term_id = match self.term_map.get(&term_sym) { Some(term) => term, None => { - return Err(tyenv.error( + tyenv.report_error( pos, format!("Constructor declared on undefined term '{}'", term.0), - )) + ); + continue; } }; let termdata = &mut self.terms[term_id.index()]; @@ -675,13 +727,13 @@ impl TermEnv { termdata.kind = TermKind::ExternalConstructor { name: func_sym }; } _ => { - return Err(tyenv.error( + tyenv.report_error( pos, format!( "Constructor defined on term of improper type '{}'", term.0 ), - )); + ); } } } @@ -697,10 +749,11 @@ impl TermEnv { let term_id = match self.term_map.get(&term_sym) { Some(term) => term, None => { - return Err(tyenv.error( + tyenv.report_error( pos, format!("Extractor declared on undefined term '{}'", term.0), - )) + ); + continue; } }; @@ -708,7 +761,8 @@ impl TermEnv { let arg_polarity = if let Some(pol) = arg_polarity.as_ref() { if pol.len() != termdata.arg_tys.len() { - return Err(tyenv.error(pos, "Incorrect number of argument-polarity directions in extractor definition".to_string())); + tyenv.report_error(pos, "Incorrect number of argument-polarity directions in extractor definition".to_string()); + continue; } pol.clone() } else { @@ -724,18 +778,17 @@ impl TermEnv { }; } _ => { - return Err(tyenv.error( + tyenv.report_error( pos, format!("Extractor defined on term of improper type '{}'", term.0), - )); + ); + continue; } } } _ => {} } } - - Ok(()) } fn translate_pattern( @@ -745,37 +798,55 @@ impl TermEnv { pat: &ast::Pattern, expected_ty: Option, bindings: &mut Bindings, - ) -> SemaResult<(Pattern, TypeId)> { + ) -> Option<(Pattern, TypeId)> { log::trace!("translate_pattern: {:?}", pat); log::trace!("translate_pattern: bindings = {:?}", bindings); match pat { // TODO: flag on primitive type decl indicating it's an integer type? &ast::Pattern::ConstInt { val } => { - let ty = expected_ty.ok_or_else(|| { - tyenv.error(pos, "Need an implied type for an integer constant".into()) - })?; - Ok((Pattern::ConstInt(ty, val), ty)) + let ty = match expected_ty { + Some(t) => t, + None => { + tyenv.report_error( + pos, + "Need an implied type for an integer constant".into(), + ); + return None; + } + }; + Some((Pattern::ConstInt(ty, val), ty)) } &ast::Pattern::Wildcard => { - let ty = expected_ty.ok_or_else(|| { - tyenv.error(pos, "Need an implied type for a wildcard".into()) - })?; - Ok((Pattern::Wildcard(ty), ty)) + let ty = match expected_ty { + Some(t) => t, + None => { + tyenv.report_error(pos, "Need an implied type for a wildcard".into()); + return None; + } + }; + Some((Pattern::Wildcard(ty), ty)) } &ast::Pattern::And { ref subpats } => { let mut expected_ty = expected_ty; let mut children = vec![]; for subpat in subpats { let (subpat, ty) = - self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; + match self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings) { + Some(x) => x, + None => { + // Try to keep going for more errors. + continue; + } + }; expected_ty = expected_ty.or(Some(ty)); children.push(subpat); } if expected_ty.is_none() { - return Err(tyenv.error(pos, "No type for (and ...) form.".to_string())); + tyenv.report_error(pos, "No type for (and ...) form.".to_string()); + return None; } let ty = expected_ty.unwrap(); - Ok((Pattern::And(ty, children), ty)) + Some((Pattern::And(ty, children), ty)) } &ast::Pattern::BindPattern { ref var, @@ -787,30 +858,32 @@ impl TermEnv { let name = tyenv.intern_mut(var); if bindings.vars.iter().any(|bv| bv.name == name) { - return Err(tyenv.error( + tyenv.report_error( pos, - format!("Rebound variable name in LHS pattern: '{}'", var.0), - )); + format!("Re-bound variable name in LHS pattern: '{}'", var.0), + ); + // Try to keep going. } let id = VarId(bindings.next_var); bindings.next_var += 1; log::trace!("binding var {:?}", var.0); bindings.vars.push(BoundVar { name, id, ty }); - Ok((Pattern::BindPattern(ty, id, Box::new(subpat)), ty)) + Some((Pattern::BindPattern(ty, id, Box::new(subpat)), ty)) } &ast::Pattern::Var { ref var } => { // Look up the variable; it must already have been bound. let name = tyenv.intern_mut(var); let bv = match bindings.vars.iter().rev().find(|bv| bv.name == name) { None => { - return Err(tyenv.error( + tyenv.report_error( pos, format!( "Unknown variable '{}' in bound-var pattern '={}'", var.0, var.0 ), - )) + ); + return None; } Some(bv) => bv, }; @@ -818,17 +891,28 @@ impl TermEnv { None => bv.ty, Some(expected_ty) if expected_ty == bv.ty => bv.ty, Some(expected_ty) => { - return Err(tyenv.error(pos, format!("Mismatched types: pattern expects type '{}' but already-bound var '{}' has type '{}'", tyenv.types[expected_ty.index()].name(tyenv), var.0, tyenv.types[bv.ty.index()].name(tyenv)))); + tyenv.report_error( + pos, + format!( + "Mismatched types: pattern expects type '{}' but already-bound var '{}' has type '{}'", + tyenv.types[expected_ty.index()].name(tyenv), + var.0, + tyenv.types[bv.ty.index()].name(tyenv))); + bv.ty // Try to keep going for more errors. } }; - Ok((Pattern::Var(ty, bv.id), ty)) + Some((Pattern::Var(ty, bv.id), ty)) } &ast::Pattern::Term { ref sym, ref args } => { let name = tyenv.intern_mut(&sym); // Look up the term. - let tid = self.term_map.get(&name).ok_or_else(|| { - tyenv.error(pos, format!("Unknown term in pattern: '{}'", sym.0)) - })?; + let tid = match self.term_map.get(&name) { + Some(t) => t, + None => { + tyenv.report_error(pos, format!("Unknown term in pattern: '{}'", sym.0)); + return None; + } + }; // Get the return type and arg types. Verify the // expected type of this pattern, if any, against the @@ -838,13 +922,19 @@ impl TermEnv { None => ret_ty, Some(expected_ty) if expected_ty == ret_ty => ret_ty, Some(expected_ty) => { - return Err(tyenv.error(pos, format!("Mismatched types: pattern expects type '{}' but term has return type '{}'", tyenv.types[expected_ty.index()].name(tyenv), tyenv.types[ret_ty.index()].name(tyenv)))); + tyenv.report_error( + pos, + format!( + "Mismatched types: pattern expects type '{}' but term has return type '{}'", + tyenv.types[expected_ty.index()].name(tyenv), + tyenv.types[ret_ty.index()].name(tyenv))); + ret_ty // Try to keep going for more errors. } }; // Check that we have the correct argument count. if self.terms[tid.index()].arg_tys.len() != args.len() { - return Err(tyenv.error( + tyenv.report_error( pos, format!( "Incorrect argument count for term '{}': got {}, expect {}", @@ -852,7 +942,7 @@ impl TermEnv { args.len(), self.terms[tid.index()].arg_tys.len() ), - )); + ); } let termdata = &self.terms[tid.index()]; @@ -861,7 +951,7 @@ impl TermEnv { &TermKind::EnumVariant { .. } => { for arg in args { if let &ast::TermArgPattern::Expr(..) = arg { - return Err(tyenv.error(pos, format!("Term in pattern '{}' cannot have an injected expr, because it is an enum variant", sym.0))); + tyenv.report_error(pos, format!("Term in pattern '{}' cannot have an injected expr, because it is an enum variant", sym.0)); } } } @@ -872,15 +962,15 @@ impl TermEnv { match (arg, pol) { (&ast::TermArgPattern::Expr(..), &ArgPolarity::Input) => {} (&ast::TermArgPattern::Expr(..), &ArgPolarity::Output) => { - return Err(tyenv.error( + tyenv.report_error( pos, "Expression used for output-polarity extractor arg" .to_string(), - )); + ); } (_, &ArgPolarity::Output) => {} (_, &ArgPolarity::Input) => { - return Err(tyenv.error(pos, "Non-expression used in pattern but expression required for input-polarity extractor arg".to_string())); + tyenv.report_error(pos, "Non-expression used in pattern but expression required for input-polarity extractor arg".to_string()); } } } @@ -895,7 +985,8 @@ impl TermEnv { let sub_ast = match template_arg { &ast::TermArgPattern::Pattern(ref pat) => pat.clone(), &ast::TermArgPattern::Expr(_) => { - return Err(tyenv.error(pos, "Cannot expand an extractor macro with an expression in a macro argument".to_string())); + tyenv.report_error(pos, "Cannot expand an extractor macro with an expression in a macro argument".to_string()); + return None; } }; macro_args.push(sub_ast.clone()); @@ -908,8 +999,10 @@ impl TermEnv { // OK. } &TermKind::Declared => { - return Err(tyenv - .error(pos, format!("Declared but undefined term '{}' used", sym.0))); + tyenv.report_error( + pos, + format!("Declared but undefined term '{}' used", sym.0), + ); } } @@ -917,12 +1010,23 @@ impl TermEnv { let mut subpats = vec![]; for (i, arg) in args.iter().enumerate() { let arg_ty = self.terms[tid.index()].arg_tys[i]; - let (subpat, _) = - self.translate_pattern_term_arg(tyenv, pos, arg, Some(arg_ty), bindings)?; + let (subpat, _) = match self.translate_pattern_term_arg( + tyenv, + pos, + arg, + Some(arg_ty), + bindings, + ) { + Some(x) => x, + None => { + // Try to keep going for more errors. + continue; + } + }; subpats.push(subpat); } - Ok((Pattern::Term(ty, *tid, subpats), ty)) + Some((Pattern::Term(ty, *tid, subpats), ty)) } &ast::Pattern::MacroArg { .. } => unreachable!(), } @@ -935,23 +1039,24 @@ impl TermEnv { pat: &ast::TermArgPattern, expected_ty: Option, bindings: &mut Bindings, - ) -> SemaResult<(TermArgPattern, TypeId)> { + ) -> Option<(TermArgPattern, TypeId)> { match pat { &ast::TermArgPattern::Pattern(ref pat) => { let (subpat, ty) = self.translate_pattern(tyenv, pos, pat, expected_ty, bindings)?; - Ok((TermArgPattern::Pattern(subpat), ty)) + Some((TermArgPattern::Pattern(subpat), ty)) } &ast::TermArgPattern::Expr(ref expr) => { if expected_ty.is_none() { - return Err(tyenv.error( + tyenv.report_error( pos, "Expression in pattern must have expected type".to_string(), - )); + ); + return None; } let ty = expected_ty.unwrap(); let expr = self.translate_expr(tyenv, pos, expr, expected_ty.unwrap(), bindings)?; - Ok((TermArgPattern::Expr(expr), ty)) + Some((TermArgPattern::Expr(expr), ty)) } } } @@ -963,28 +1068,32 @@ impl TermEnv { expr: &ast::Expr, ty: TypeId, bindings: &mut Bindings, - ) -> SemaResult { + ) -> Option { log::trace!("translate_expr: {:?}", expr); match expr { &ast::Expr::Term { ref sym, ref args } => { // Look up the term. let name = tyenv.intern_mut(&sym); // Look up the term. - let tid = self.term_map.get(&name).ok_or_else(|| { - tyenv.error(pos, format!("Unknown term in pattern: '{}'", sym.0)) - })?; + let tid = match self.term_map.get(&name) { + Some(t) => t, + None => { + tyenv.report_error(pos, format!("Unknown term in pattern: '{}'", sym.0)); + return None; + } + }; // Get the return type and arg types. Verify the // expected type of this pattern, if any, against the // return type of the term. let ret_ty = self.terms[tid.index()].ret_ty; if ret_ty != ty { - return Err(tyenv.error(pos, format!("Mismatched types: expression expects type '{}' but term has return type '{}'", tyenv.types[ty.index()].name(tyenv), tyenv.types[ret_ty.index()].name(tyenv)))); + tyenv.report_error(pos, format!("Mismatched types: expression expects type '{}' but term has return type '{}'", tyenv.types[ty.index()].name(tyenv), tyenv.types[ret_ty.index()].name(tyenv))); } // Check that we have the correct argument count. if self.terms[tid.index()].arg_tys.len() != args.len() { - return Err(tyenv.error( + tyenv.report_error( pos, format!( "Incorrect argument count for term '{}': got {}, expect {}", @@ -992,32 +1101,38 @@ impl TermEnv { args.len(), self.terms[tid.index()].arg_tys.len() ), - )); + ); } // Resolve subexpressions. let mut subexprs = vec![]; for (i, arg) in args.iter().enumerate() { let arg_ty = self.terms[tid.index()].arg_tys[i]; - let subexpr = self.translate_expr(tyenv, pos, arg, arg_ty, bindings)?; + let subexpr = match self.translate_expr(tyenv, pos, arg, arg_ty, bindings) { + Some(s) => s, + None => { + continue; + } + }; subexprs.push(subexpr); } - Ok(Expr::Term(ty, *tid, subexprs)) + Some(Expr::Term(ty, *tid, subexprs)) } &ast::Expr::Var { ref name } => { let sym = tyenv.intern_mut(name); // Look through bindings, innermost (most recent) first. let bv = match bindings.vars.iter().rev().find(|b| b.name == sym) { None => { - return Err(tyenv.error(pos, format!("Unknown variable '{}'", name.0))); + tyenv.report_error(pos, format!("Unknown variable '{}'", name.0)); + return None; } Some(bv) => bv, }; // Verify type. if bv.ty != ty { - return Err(tyenv.error( + tyenv.report_error( pos, format!( "Variable '{}' has type {} but we need {} in context", @@ -1025,12 +1140,12 @@ impl TermEnv { tyenv.types[bv.ty.index()].name(tyenv), tyenv.types[ty.index()].name(tyenv) ), - )); + ); } - Ok(Expr::Var(bv.ty, bv.id)) + Some(Expr::Var(bv.ty, bv.id)) } - &ast::Expr::ConstInt { val } => Ok(Expr::ConstInt(ty, val)), + &ast::Expr::ConstInt { val } => Some(Expr::ConstInt(ty, val)), &ast::Expr::Let { ref defs, ref body } => { let orig_binding_len = bindings.vars.len(); @@ -1040,33 +1155,41 @@ impl TermEnv { // Check that the given variable name does not already exist. let name = tyenv.intern_mut(&def.var); if bindings.vars.iter().any(|bv| bv.name == name) { - return Err( - tyenv.error(pos, format!("Variable '{}' already bound", def.var.0)) - ); + tyenv.report_error(pos, format!("Variable '{}' already bound", def.var.0)); } // Look up the type. let tysym = match tyenv.intern(&def.ty) { Some(ty) => ty, None => { - return Err(tyenv.error( + tyenv.report_error( pos, format!("Unknown type {} for variable '{}'", def.ty.0, def.var.0), - )) + ); + continue; } }; let tid = match tyenv.type_map.get(&tysym) { Some(tid) => *tid, None => { - return Err(tyenv.error( + tyenv.report_error( pos, format!("Unknown type {} for variable '{}'", def.ty.0, def.var.0), - )) + ); + continue; } }; // Evaluate the variable's value. - let val = Box::new(self.translate_expr(tyenv, pos, &def.val, ty, bindings)?); + let val = Box::new( + match self.translate_expr(tyenv, pos, &def.val, ty, bindings) { + Some(e) => e, + None => { + // Keep going for more errors. + continue; + } + }, + ); // Bind the var with the given type. let id = VarId(bindings.next_var); @@ -1083,7 +1206,7 @@ impl TermEnv { // Pop the bindings. bindings.vars.truncate(orig_binding_len); - Ok(Expr::Let(body_ty, let_defs, body)) + Some(Expr::Let(body_ty, let_defs, body)) } } } From 2a574b1b42f5342677113a2c0cffb5d64c9e48ab Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 9 Sep 2021 23:56:28 -0700 Subject: [PATCH 32/95] Properly track locations on every AST node. Semantic errors are much more useful now. Also print parse errors in same (pseudo-standard compiler error) output format. --- cranelift/isle/src/ast.rs | 107 ++++++++++++++++++++------ cranelift/isle/src/error.rs | 2 +- cranelift/isle/src/main.rs | 9 ++- cranelift/isle/src/parser.rs | 145 +++++++++++------------------------ cranelift/isle/src/sema.rs | 84 +++++++++++--------- 5 files changed, 183 insertions(+), 164 deletions(-) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index 8f1cd820c4..bbfdc59698 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -19,7 +19,7 @@ pub enum Def { /// An identifier -- a variable, term symbol, or type. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Ident(pub String); +pub struct Ident(pub String, pub Pos); /// A declaration of a type. #[derive(Clone, PartialEq, Eq, Debug)] @@ -35,8 +35,8 @@ pub struct Type { /// TODO: add structs as well? #[derive(Clone, PartialEq, Eq, Debug)] pub enum TypeValue { - Primitive(Ident), - Enum(Vec), + Primitive(Ident, Pos), + Enum(Vec, Pos), } /// One variant of an enum type. @@ -44,6 +44,7 @@ pub enum TypeValue { pub struct Variant { pub name: Ident, pub fields: Vec, + pub pos: Pos, } /// One field of an enum variant. @@ -51,6 +52,7 @@ pub struct Variant { pub struct Field { pub name: Ident, pub ty: Ident, + pub pos: Pos, } /// A declaration of a term with its argument and return types. @@ -85,22 +87,27 @@ pub struct Extractor { pub enum Pattern { /// An operator that binds a variable to a subterm and match the /// subpattern. - BindPattern { var: Ident, subpat: Box }, + BindPattern { + var: Ident, + subpat: Box, + pos: Pos, + }, /// A variable that has already been bound (`=x` syntax). - Var { var: Ident }, + Var { var: Ident, pos: Pos }, /// An operator that matches a constant integer value. - ConstInt { val: i64 }, + ConstInt { val: i64, pos: Pos }, /// An application of a type variant or term. Term { sym: Ident, args: Vec, + pos: Pos, }, /// An operator that matches anything. - Wildcard, + Wildcard { pos: Pos }, /// N sub-patterns that must all match. - And { subpats: Vec }, + And { subpats: Vec, pos: Pos }, /// Internal use only: macro argument in a template. - MacroArg { index: usize }, + MacroArg { index: usize, pos: Pos }, } impl Pattern { @@ -118,9 +125,11 @@ impl Pattern { &Pattern::BindPattern { ref var, ref subpat, - } if matches!(&**subpat, &Pattern::Wildcard) => { + pos, + .. + } if matches!(&**subpat, &Pattern::Wildcard { .. }) => { if let Some(i) = macro_args.iter().position(|arg| arg == var) { - Pattern::MacroArg { index: i } + Pattern::MacroArg { index: i, pos } } else { self.clone() } @@ -128,18 +137,24 @@ impl Pattern { &Pattern::BindPattern { ref var, ref subpat, + pos, } => Pattern::BindPattern { var: var.clone(), subpat: Box::new(subpat.make_macro_template(macro_args)), + pos, }, - &Pattern::And { ref subpats } => { + &Pattern::And { ref subpats, pos } => { let subpats = subpats .iter() .map(|subpat| subpat.make_macro_template(macro_args)) .collect::>(); - Pattern::And { subpats } + Pattern::And { subpats, pos } } - &Pattern::Term { ref sym, ref args } => { + &Pattern::Term { + ref sym, + ref args, + pos, + } => { let args = args .iter() .map(|arg| arg.make_macro_template(macro_args)) @@ -147,10 +162,13 @@ impl Pattern { Pattern::Term { sym: sym.clone(), args, + pos, } } - &Pattern::Var { .. } | &Pattern::Wildcard | &Pattern::ConstInt { .. } => self.clone(), + &Pattern::Var { .. } | &Pattern::Wildcard { .. } | &Pattern::ConstInt { .. } => { + self.clone() + } &Pattern::MacroArg { .. } => unreachable!(), } } @@ -160,18 +178,24 @@ impl Pattern { &Pattern::BindPattern { ref var, ref subpat, + pos, } => Pattern::BindPattern { var: var.clone(), subpat: Box::new(subpat.subst_macro_args(macro_args)), + pos, }, - &Pattern::And { ref subpats } => { + &Pattern::And { ref subpats, pos } => { let subpats = subpats .iter() .map(|subpat| subpat.subst_macro_args(macro_args)) .collect::>(); - Pattern::And { subpats } + Pattern::And { subpats, pos } } - &Pattern::Term { ref sym, ref args } => { + &Pattern::Term { + ref sym, + ref args, + pos, + } => { let args = args .iter() .map(|arg| arg.subst_macro_args(macro_args)) @@ -179,11 +203,26 @@ impl Pattern { Pattern::Term { sym: sym.clone(), args, + pos, } } - &Pattern::Var { .. } | &Pattern::Wildcard | &Pattern::ConstInt { .. } => self.clone(), - &Pattern::MacroArg { index } => macro_args[index].clone(), + &Pattern::Var { .. } | &Pattern::Wildcard { .. } | &Pattern::ConstInt { .. } => { + self.clone() + } + &Pattern::MacroArg { index, .. } => macro_args[index].clone(), + } + } + + pub fn pos(&self) -> Pos { + match self { + &Pattern::ConstInt { pos, .. } + | &Pattern::And { pos, .. } + | &Pattern::Term { pos, .. } + | &Pattern::BindPattern { pos, .. } + | &Pattern::Var { pos, .. } + | &Pattern::Wildcard { pos, .. } + | &Pattern::MacroArg { pos, .. } => pos, } } } @@ -231,13 +270,32 @@ impl TermArgPattern { #[derive(Clone, PartialEq, Eq, Debug)] pub enum Expr { /// A term: `(sym args...)`. - Term { sym: Ident, args: Vec }, + Term { + sym: Ident, + args: Vec, + pos: Pos, + }, /// A variable use. - Var { name: Ident }, + Var { name: Ident, pos: Pos }, /// A constant integer. - ConstInt { val: i64 }, + ConstInt { val: i64, pos: Pos }, /// The `(let ((var ty val)*) body)` form. - Let { defs: Vec, body: Box }, + Let { + defs: Vec, + body: Box, + pos: Pos, + }, +} + +impl Expr { + pub fn pos(&self) -> Pos { + match self { + &Expr::Term { pos, .. } + | &Expr::Var { pos, .. } + | &Expr::ConstInt { pos, .. } + | &Expr::Let { pos, .. } => pos, + } + } } /// One variable locally bound in a `(let ...)` expression. @@ -246,6 +304,7 @@ pub struct LetDef { pub var: Ident, pub ty: Ident, pub val: Box, + pub pos: Pos, } /// An external binding: an extractor or constructor function attached diff --git a/cranelift/isle/src/error.rs b/cranelift/isle/src/error.rs index a3afd1fce7..70a2140401 100644 --- a/cranelift/isle/src/error.rs +++ b/cranelift/isle/src/error.rs @@ -23,7 +23,7 @@ impl fmt::Display for Error { ref filename, pos, } => { - write!(f, "{}:{}: {}", filename, pos.line, msg) + write!(f, "{}:{}:{}: error: {}", filename, pos.line, pos.col, msg) } &Error::SystemError { ref msg } => { write!(f, "{}", msg) diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/main.rs index 5f2e168a19..13c37a8125 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/main.rs @@ -46,7 +46,14 @@ fn main() -> Result<(), error::Error> { let lexer = lexer::Lexer::from_files(input_files)?; let mut parser = parser::Parser::new(lexer); - let defs = parser.parse_defs()?; + let defs = match parser.parse_defs() { + Ok(defs) => defs, + Err(error) => { + eprintln!("{}", error); + eprintln!("Failed to parse input."); + std::process::exit(1); + } + }; let code = match compile::compile(&defs) { Ok(code) => code, Err(errors) => { diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/src/parser.rs index 8c6b080b10..77ca738f7d 100644 --- a/cranelift/isle/src/parser.rs +++ b/cranelift/isle/src/parser.rs @@ -147,7 +147,7 @@ impl<'a> Parser<'a> { ), )); } - Ok(Ident(s.to_string())) + Ok(Ident(s.to_string(), pos)) } fn parse_ident(&mut self) -> ParseResult { @@ -180,7 +180,8 @@ impl<'a> Parser<'a> { self.symbol()?; let primitive_ident = self.parse_ident()?; self.rparen()?; - Ok(TypeValue::Primitive(primitive_ident)) + let pos = pos.unwrap(); + Ok(TypeValue::Primitive(primitive_ident, pos)) } else if self.is_sym_str("enum") { self.symbol()?; let mut variants = vec![]; @@ -189,7 +190,8 @@ impl<'a> Parser<'a> { variants.push(variant); } self.rparen()?; - Ok(TypeValue::Enum(variants)) + let pos = pos.unwrap(); + Ok(TypeValue::Enum(variants, pos)) } else { Err(self.error(pos.unwrap(), "Unknown type definition".to_string())) } @@ -197,12 +199,15 @@ impl<'a> Parser<'a> { fn parse_type_variant(&mut self) -> ParseResult { if self.is_sym() { + let pos = self.pos().unwrap(); let name = self.parse_ident()?; Ok(Variant { name, fields: vec![], + pos, }) } else { + let pos = self.pos(); self.lparen()?; let name = self.parse_ident()?; let mut fields = vec![]; @@ -210,16 +215,19 @@ impl<'a> Parser<'a> { fields.push(self.parse_type_field()?); } self.rparen()?; - Ok(Variant { name, fields }) + let pos = pos.unwrap(); + Ok(Variant { name, fields, pos }) } } fn parse_type_field(&mut self) -> ParseResult { + let pos = self.pos(); self.lparen()?; let name = self.parse_ident()?; let ty = self.parse_ident()?; self.rparen()?; - Ok(Field { name, ty }) + let pos = pos.unwrap(); + Ok(Field { name, ty, pos }) } fn parse_decl(&mut self) -> ParseResult { @@ -342,30 +350,38 @@ impl<'a> Parser<'a> { fn parse_pattern(&mut self) -> ParseResult { let pos = self.pos(); if self.is_int() { - Ok(Pattern::ConstInt { val: self.int()? }) + let pos = pos.unwrap(); + Ok(Pattern::ConstInt { + val: self.int()?, + pos, + }) } else if self.is_sym_str("_") { + let pos = pos.unwrap(); self.symbol()?; - Ok(Pattern::Wildcard) + Ok(Pattern::Wildcard { pos }) } else if self.is_sym() { + let pos = pos.unwrap(); let s = self.symbol()?; if s.starts_with("=") { let s = &s[1..]; - let var = self.str_to_ident(pos.unwrap(), s)?; - Ok(Pattern::Var { var }) + let var = self.str_to_ident(pos, s)?; + Ok(Pattern::Var { var, pos }) } else { - let var = self.str_to_ident(pos.unwrap(), &s)?; + let var = self.str_to_ident(pos, &s)?; if self.is_at() { self.at()?; let subpat = Box::new(self.parse_pattern()?); - Ok(Pattern::BindPattern { var, subpat }) + Ok(Pattern::BindPattern { var, subpat, pos }) } else { Ok(Pattern::BindPattern { var, - subpat: Box::new(Pattern::Wildcard), + subpat: Box::new(Pattern::Wildcard { pos }), + pos, }) } } } else if self.is_lparen() { + let pos = pos.unwrap(); self.lparen()?; if self.is_sym_str("and") { self.symbol()?; @@ -374,7 +390,7 @@ impl<'a> Parser<'a> { subpats.push(self.parse_pattern()?); } self.rparen()?; - Ok(Pattern::And { subpats }) + Ok(Pattern::And { subpats, pos }) } else { let sym = self.parse_ident()?; let mut args = vec![]; @@ -382,7 +398,7 @@ impl<'a> Parser<'a> { args.push(self.parse_pattern_term_arg()?); } self.rparen()?; - Ok(Pattern::Term { sym, args }) + Ok(Pattern::Term { sym, args, pos }) } } else { Err(self.error(pos.unwrap(), "Unexpected pattern".into())) @@ -401,6 +417,7 @@ impl<'a> Parser<'a> { fn parse_expr(&mut self) -> ParseResult { let pos = self.pos(); if self.is_lparen() { + let pos = pos.unwrap(); self.lparen()?; if self.is_sym_str("let") { self.symbol()?; @@ -413,7 +430,7 @@ impl<'a> Parser<'a> { self.rparen()?; let body = Box::new(self.parse_expr()?); self.rparen()?; - Ok(Expr::Let { defs, body }) + Ok(Expr::Let { defs, body, pos }) } else { let sym = self.parse_ident()?; let mut args = vec![]; @@ -421,111 +438,37 @@ impl<'a> Parser<'a> { args.push(self.parse_expr()?); } self.rparen()?; - Ok(Expr::Term { sym, args }) + Ok(Expr::Term { sym, args, pos }) } } else if self.is_sym_str("#t") { + let pos = pos.unwrap(); self.symbol()?; - Ok(Expr::ConstInt { val: 1 }) + Ok(Expr::ConstInt { val: 1, pos }) } else if self.is_sym_str("#f") { + let pos = pos.unwrap(); self.symbol()?; - Ok(Expr::ConstInt { val: 0 }) + Ok(Expr::ConstInt { val: 0, pos }) } else if self.is_sym() { + let pos = pos.unwrap(); let name = self.parse_ident()?; - Ok(Expr::Var { name }) + Ok(Expr::Var { name, pos }) } else if self.is_int() { + let pos = pos.unwrap(); let val = self.int()?; - Ok(Expr::ConstInt { val }) + Ok(Expr::ConstInt { val, pos }) } else { Err(self.error(pos.unwrap(), "Invalid expression".into())) } } fn parse_letdef(&mut self) -> ParseResult { + let pos = self.pos(); self.lparen()?; + let pos = pos.unwrap(); let var = self.parse_ident()?; let ty = self.parse_ident()?; let val = Box::new(self.parse_expr()?); self.rparen()?; - Ok(LetDef { var, ty, val }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn parse_type() { - let text = r" - ;; comment - (type Inst extern (enum - (Alu (a Reg) (b Reg) (dest Reg)) - (Load (a Reg) (dest Reg)))) - (type u32 (primitive u32)) - "; - let defs = Parser::new(Lexer::from_str(text, "(none)")) - .parse_defs() - .expect("should parse"); - assert_eq!( - defs, - Defs { - filenames: vec!["(none)".to_string()], - defs: vec![ - Def::Type(Type { - name: Ident("Inst".to_string()), - is_extern: true, - ty: TypeValue::Enum(vec![ - Variant { - name: Ident("Alu".to_string()), - fields: vec![ - Field { - name: Ident("a".to_string()), - ty: Ident("Reg".to_string()), - }, - Field { - name: Ident("b".to_string()), - ty: Ident("Reg".to_string()), - }, - Field { - name: Ident("dest".to_string()), - ty: Ident("Reg".to_string()), - }, - ], - }, - Variant { - name: Ident("Load".to_string()), - fields: vec![ - Field { - name: Ident("a".to_string()), - ty: Ident("Reg".to_string()), - }, - Field { - name: Ident("dest".to_string()), - ty: Ident("Reg".to_string()), - }, - ], - } - ]), - pos: Pos { - file: 0, - offset: 42, - line: 3, - col: 18, - }, - }), - Def::Type(Type { - name: Ident("u32".to_string()), - is_extern: false, - ty: TypeValue::Primitive(Ident("u32".to_string())), - pos: Pos { - file: 0, - offset: 167, - line: 6, - col: 18, - }, - }), - ] - } - ); + Ok(LetDef { var, ty, val, pos }) } } diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/src/sema.rs index 19f136acf0..b7ee0dace1 100644 --- a/cranelift/isle/src/sema.rs +++ b/cranelift/isle/src/sema.rs @@ -356,17 +356,20 @@ impl TypeEnv { fn type_from_ast(&mut self, tid: TypeId, ty: &ast::Type) -> Option { let name = self.intern(&ty.name).unwrap(); match &ty.ty { - &ast::TypeValue::Primitive(ref id) => Some(Type::Primitive(tid, self.intern_mut(id))), - &ast::TypeValue::Enum(ref ty_variants) => { + &ast::TypeValue::Primitive(ref id, ..) => { + Some(Type::Primitive(tid, self.intern_mut(id))) + } + &ast::TypeValue::Enum(ref ty_variants, ..) => { let mut variants = vec![]; for variant in ty_variants { - let combined_ident = ast::Ident(format!("{}.{}", ty.name.0, variant.name.0)); + let combined_ident = + ast::Ident(format!("{}.{}", ty.name.0, variant.name.0), variant.name.1); let fullname = self.intern_mut(&combined_ident); let name = self.intern_mut(&variant.name); let id = VariantId(variants.len()); if variants.iter().any(|v: &Variant| v.name == name) { self.report_error( - ty.pos, + variant.pos, format!("Duplicate variant name in type: '{}'", variant.name.0), ); return None; @@ -376,7 +379,7 @@ impl TypeEnv { let field_name = self.intern_mut(&field.name); if fields.iter().any(|f: &Field| f.name == field_name) { self.report_error( - ty.pos, + field.pos, format!( "Duplicate field name '{}' in variant '{}' of type", field.name.0, variant.name.0 @@ -389,7 +392,7 @@ impl TypeEnv { Some(tid) => *tid, None => { self.report_error( - ty.pos, + field.ty.1, format!( "Unknown type '{}' for field '{}' in variant '{}'", field.ty.0, field.name.0, variant.name.0 @@ -503,10 +506,7 @@ impl TermEnv { .map(|id| { let sym = tyenv.intern_mut(id); tyenv.type_map.get(&sym).cloned().ok_or_else(|| { - tyenv.report_error( - decl.pos, - format!("Unknown arg type: '{}'", id.0), - ); + tyenv.report_error(id.1, format!("Unknown arg type: '{}'", id.0)); () }) }) @@ -523,7 +523,7 @@ impl TermEnv { Some(t) => t, None => { tyenv.report_error( - decl.pos, + decl.ret_ty.1, format!("Unknown return type: '{}'", decl.ret_ty.0), ); continue; @@ -676,7 +676,6 @@ impl TermEnv { let (lhs, ty) = match self.translate_pattern( tyenv, - rule.pos, &rule.pattern, None, &mut bindings, @@ -688,7 +687,7 @@ impl TermEnv { } }; let rhs = - match self.translate_expr(tyenv, rule.pos, &rule.expr, ty, &mut bindings) { + match self.translate_expr(tyenv, &rule.expr, ty, &mut bindings) { Some(x) => x, None => { continue; @@ -794,7 +793,6 @@ impl TermEnv { fn translate_pattern( &self, tyenv: &mut TypeEnv, - pos: Pos, pat: &ast::Pattern, expected_ty: Option, bindings: &mut Bindings, @@ -803,7 +801,7 @@ impl TermEnv { log::trace!("translate_pattern: bindings = {:?}", bindings); match pat { // TODO: flag on primitive type decl indicating it's an integer type? - &ast::Pattern::ConstInt { val } => { + &ast::Pattern::ConstInt { val, pos } => { let ty = match expected_ty { Some(t) => t, None => { @@ -816,7 +814,7 @@ impl TermEnv { }; Some((Pattern::ConstInt(ty, val), ty)) } - &ast::Pattern::Wildcard => { + &ast::Pattern::Wildcard { pos } => { let ty = match expected_ty { Some(t) => t, None => { @@ -826,12 +824,12 @@ impl TermEnv { }; Some((Pattern::Wildcard(ty), ty)) } - &ast::Pattern::And { ref subpats } => { + &ast::Pattern::And { ref subpats, pos } => { let mut expected_ty = expected_ty; let mut children = vec![]; for subpat in subpats { let (subpat, ty) = - match self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings) { + match self.translate_pattern(tyenv, &*subpat, expected_ty, bindings) { Some(x) => x, None => { // Try to keep going for more errors. @@ -851,10 +849,11 @@ impl TermEnv { &ast::Pattern::BindPattern { ref var, ref subpat, + pos, } => { // Do the subpattern first so we can resolve the type for sure. let (subpat, ty) = - self.translate_pattern(tyenv, pos, &*subpat, expected_ty, bindings)?; + self.translate_pattern(tyenv, &*subpat, expected_ty, bindings)?; let name = tyenv.intern_mut(var); if bindings.vars.iter().any(|bv| bv.name == name) { @@ -871,7 +870,7 @@ impl TermEnv { Some((Pattern::BindPattern(ty, id, Box::new(subpat)), ty)) } - &ast::Pattern::Var { ref var } => { + &ast::Pattern::Var { ref var, pos } => { // Look up the variable; it must already have been bound. let name = tyenv.intern_mut(var); let bv = match bindings.vars.iter().rev().find(|bv| bv.name == name) { @@ -903,7 +902,11 @@ impl TermEnv { }; Some((Pattern::Var(ty, bv.id), ty)) } - &ast::Pattern::Term { ref sym, ref args } => { + &ast::Pattern::Term { + ref sym, + ref args, + pos, + } => { let name = tyenv.intern_mut(&sym); // Look up the term. let tid = match self.term_map.get(&name) { @@ -950,7 +953,7 @@ impl TermEnv { match &termdata.kind { &TermKind::EnumVariant { .. } => { for arg in args { - if let &ast::TermArgPattern::Expr(..) = arg { + if let &ast::TermArgPattern::Expr(_) = arg { tyenv.report_error(pos, format!("Term in pattern '{}' cannot have an injected expr, because it is an enum variant", sym.0)); } } @@ -961,16 +964,16 @@ impl TermEnv { for (arg, pol) in args.iter().zip(arg_polarity.iter()) { match (arg, pol) { (&ast::TermArgPattern::Expr(..), &ArgPolarity::Input) => {} - (&ast::TermArgPattern::Expr(..), &ArgPolarity::Output) => { + (&ast::TermArgPattern::Expr(ref e), &ArgPolarity::Output) => { tyenv.report_error( - pos, + e.pos(), "Expression used for output-polarity extractor arg" .to_string(), ); } (_, &ArgPolarity::Output) => {} - (_, &ArgPolarity::Input) => { - tyenv.report_error(pos, "Non-expression used in pattern but expression required for input-polarity extractor arg".to_string()); + (&ast::TermArgPattern::Pattern(ref p), &ArgPolarity::Input) => { + tyenv.report_error(p.pos(), "Non-expression used in pattern but expression required for input-polarity extractor arg".to_string()); } } } @@ -993,7 +996,7 @@ impl TermEnv { } log::trace!("internal extractor macro args = {:?}", args); let pat = template.subst_macro_args(¯o_args[..]); - return self.translate_pattern(tyenv, pos, &pat, expected_ty, bindings); + return self.translate_pattern(tyenv, &pat, expected_ty, bindings); } &TermKind::ExternalConstructor { .. } | &TermKind::InternalConstructor => { // OK. @@ -1043,7 +1046,7 @@ impl TermEnv { match pat { &ast::TermArgPattern::Pattern(ref pat) => { let (subpat, ty) = - self.translate_pattern(tyenv, pos, pat, expected_ty, bindings)?; + self.translate_pattern(tyenv, pat, expected_ty, bindings)?; Some((TermArgPattern::Pattern(subpat), ty)) } &ast::TermArgPattern::Expr(ref expr) => { @@ -1055,7 +1058,7 @@ impl TermEnv { return None; } let ty = expected_ty.unwrap(); - let expr = self.translate_expr(tyenv, pos, expr, expected_ty.unwrap(), bindings)?; + let expr = self.translate_expr(tyenv, expr, expected_ty.unwrap(), bindings)?; Some((TermArgPattern::Expr(expr), ty)) } } @@ -1064,14 +1067,17 @@ impl TermEnv { fn translate_expr( &self, tyenv: &mut TypeEnv, - pos: Pos, expr: &ast::Expr, ty: TypeId, bindings: &mut Bindings, ) -> Option { log::trace!("translate_expr: {:?}", expr); match expr { - &ast::Expr::Term { ref sym, ref args } => { + &ast::Expr::Term { + ref sym, + ref args, + pos, + } => { // Look up the term. let name = tyenv.intern_mut(&sym); // Look up the term. @@ -1108,7 +1114,7 @@ impl TermEnv { let mut subexprs = vec![]; for (i, arg) in args.iter().enumerate() { let arg_ty = self.terms[tid.index()].arg_tys[i]; - let subexpr = match self.translate_expr(tyenv, pos, arg, arg_ty, bindings) { + let subexpr = match self.translate_expr(tyenv, arg, arg_ty, bindings) { Some(s) => s, None => { continue; @@ -1119,7 +1125,7 @@ impl TermEnv { Some(Expr::Term(ty, *tid, subexprs)) } - &ast::Expr::Var { ref name } => { + &ast::Expr::Var { ref name, pos } => { let sym = tyenv.intern_mut(name); // Look through bindings, innermost (most recent) first. let bv = match bindings.vars.iter().rev().find(|b| b.name == sym) { @@ -1145,8 +1151,12 @@ impl TermEnv { Some(Expr::Var(bv.ty, bv.id)) } - &ast::Expr::ConstInt { val } => Some(Expr::ConstInt(ty, val)), - &ast::Expr::Let { ref defs, ref body } => { + &ast::Expr::ConstInt { val, .. } => Some(Expr::ConstInt(ty, val)), + &ast::Expr::Let { + ref defs, + ref body, + pos, + } => { let orig_binding_len = bindings.vars.len(); // For each new binding... @@ -1182,7 +1192,7 @@ impl TermEnv { // Evaluate the variable's value. let val = Box::new( - match self.translate_expr(tyenv, pos, &def.val, ty, bindings) { + match self.translate_expr(tyenv, &def.val, ty, bindings) { Some(e) => e, None => { // Keep going for more errors. @@ -1200,7 +1210,7 @@ impl TermEnv { } // Evaluate the body, expecting the type of the overall let-expr. - let body = Box::new(self.translate_expr(tyenv, pos, body, ty, bindings)?); + let body = Box::new(self.translate_expr(tyenv, body, ty, bindings)?); let body_ty = body.ty(); // Pop the bindings. From 3ed146b782a020e026c046c28ccb2d05ed0b397e Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Fri, 10 Sep 2021 00:08:46 -0700 Subject: [PATCH 33/95] README update. --- cranelift/isle/README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cranelift/isle/README.md b/cranelift/isle/README.md index 3145c7256e..a17a547b52 100644 --- a/cranelift/isle/README.md +++ b/cranelift/isle/README.md @@ -1,3 +1,37 @@ +# ISLE: Instruction Selection/Lowering Expressions DSL + +ISLE is a DSL that allows one to write instruction-lowering rules for a +compiler backend. It is based on a "term-rewriting" paradigm in which the input +-- some sort of compiler IR -- is, conceptually, a tree of terms, and we have a +set of rewrite rules that turn this into another tree of terms. + +This repository contains a prototype meta-compiler that compiles ISLE rules +down to an instruction selector implementation in generated Rust code. The +generated code operates efficiently in a single pass over the input, and merges +all rules into a decision tree, sharing work where possible, while respecting +user-configurable priorities on each rule. + +The ISLE language is designed so that the rules can both be compiled into an +efficient compiler backend and can be used in formal reasoning about the +compiler. The compiler in this repository implements the former. The latter +use-case is future work and outside the scope of this prototype, but at a high +level, the rules can be seen as simple equivalences between values in two +languages, and so should be translatable to formal constraints or other logical +specification languages. + +Some more details are in [BA RFC +#15](https://github.com/bytecodealliance/rfcs/pull/15); additional +documentation will eventually be added to carefully specify the language +semantics. + +## Sketch of Instruction Selector + +Please see [this Cranelift +branch](https://github.com/cfallin/wasmtime/tree/isle) for an ongoing sketch of +an instruction selector backend in Cranelift that uses ISLE. + +## Example Usage + ```plain $ cargo build --release $ target/release/isle -i isle_examples/test.isle -o isle_examples/test.rs From 83672a88cc5cf7cdb4d5c889c6234ee8e0132b05 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Fri, 10 Sep 2021 01:27:09 -0700 Subject: [PATCH 34/95] bugfix to macro template generation: exclude Pos on ident from equality (broken by recent commit) --- cranelift/isle/src/ast.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/src/ast.rs index bbfdc59698..9e6e06f6e7 100644 --- a/cranelift/isle/src/ast.rs +++ b/cranelift/isle/src/ast.rs @@ -120,7 +120,7 @@ impl Pattern { } pub fn make_macro_template(&self, macro_args: &[Ident]) -> Pattern { - log::trace!("repplace_macro_args: {:?} with {:?}", self, macro_args); + log::trace!("make_macro_template: {:?} with {:?}", self, macro_args); match self { &Pattern::BindPattern { ref var, @@ -128,7 +128,7 @@ impl Pattern { pos, .. } if matches!(&**subpat, &Pattern::Wildcard { .. }) => { - if let Some(i) = macro_args.iter().position(|arg| arg == var) { + if let Some(i) = macro_args.iter().position(|arg| arg.0 == var.0) { Pattern::MacroArg { index: i, pos } } else { self.clone() @@ -174,6 +174,7 @@ impl Pattern { } pub fn subst_macro_args(&self, macro_args: &[Pattern]) -> Pattern { + log::trace!("subst_macro_args: {:?} with {:?}", self, macro_args); match self { &Pattern::BindPattern { ref var, From e751f12ac5ba572867c95d34773c5e7f49e81e19 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 14 Sep 2021 22:55:08 -0700 Subject: [PATCH 35/95] Refactor to allow use as library, in order to allow build.rs usage. --- cranelift/isle/src/{main.rs => bin/isle.rs} | 12 ++---------- cranelift/isle/src/ir.rs | 4 ---- cranelift/isle/src/lexer.rs | 6 ------ 3 files changed, 2 insertions(+), 20 deletions(-) rename cranelift/isle/src/{main.rs => bin/isle.rs} (94%) diff --git a/cranelift/isle/src/main.rs b/cranelift/isle/src/bin/isle.rs similarity index 94% rename from cranelift/isle/src/main.rs rename to cranelift/isle/src/bin/isle.rs index 13c37a8125..be99d7301f 100644 --- a/cranelift/isle/src/main.rs +++ b/cranelift/isle/src/bin/isle.rs @@ -1,15 +1,6 @@ -#![allow(dead_code)] - use clap::{App, Arg}; -mod ast; -mod codegen; -mod compile; -mod error; -mod ir; -mod lexer; -mod parser; -mod sema; +use isle::{error, lexer, parser, compile}; fn main() -> Result<(), error::Error> { let _ = env_logger::try_init(); @@ -73,3 +64,4 @@ fn main() -> Result<(), error::Error> { Ok(()) } + diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/src/ir.rs index d9657297ea..82cc027d35 100644 --- a/cranelift/isle/src/ir.rs +++ b/cranelift/isle/src/ir.rs @@ -477,10 +477,6 @@ impl ExprSequence { }); } - fn add_multi_return(&mut self, index: usize, ty: TypeId, value: Value) { - self.add_inst(ExprInst::Return { index, ty, value }); - } - /// Creates a sequence of ExprInsts to generate the given /// expression value. Returns the value ID as well as the root /// term ID, if any. diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/src/lexer.rs index 261dc9c910..372d169585 100644 --- a/cranelift/isle/src/lexer.rs +++ b/cranelift/isle/src/lexer.rs @@ -12,12 +12,6 @@ pub struct Lexer<'a> { lookahead: Option<(Pos, Token)>, } -#[derive(Clone, Debug)] -enum LexerInput<'a> { - String { s: &'a str, filename: &'a str }, - File { content: String, filename: String }, -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] pub struct Pos { pub file: usize, From 521010cc4f68be196d7dbe93882c7c6bf85606ad Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 14 Sep 2021 23:24:49 -0700 Subject: [PATCH 36/95] Split into isle and islec crates --- cranelift/isle/Cargo.lock | 12 ++++++++++-- cranelift/isle/Cargo.toml | 13 ++----------- cranelift/isle/isle/Cargo.toml | 9 +++++++++ cranelift/isle/{ => isle}/src/ast.rs | 0 cranelift/isle/{ => isle}/src/codegen.rs | 0 cranelift/isle/{ => isle}/src/compile.rs | 0 cranelift/isle/{ => isle}/src/error.rs | 0 cranelift/isle/{ => isle}/src/ir.rs | 0 cranelift/isle/{ => isle}/src/lexer.rs | 0 cranelift/isle/isle/src/lib.rs | 9 +++++++++ cranelift/isle/{ => isle}/src/parser.rs | 0 cranelift/isle/{ => isle}/src/sema.rs | 0 cranelift/isle/islec/Cargo.toml | 12 ++++++++++++ .../isle/{src/bin/isle.rs => islec/src/main.rs} | 0 14 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 cranelift/isle/isle/Cargo.toml rename cranelift/isle/{ => isle}/src/ast.rs (100%) rename cranelift/isle/{ => isle}/src/codegen.rs (100%) rename cranelift/isle/{ => isle}/src/compile.rs (100%) rename cranelift/isle/{ => isle}/src/error.rs (100%) rename cranelift/isle/{ => isle}/src/ir.rs (100%) rename cranelift/isle/{ => isle}/src/lexer.rs (100%) create mode 100644 cranelift/isle/isle/src/lib.rs rename cranelift/isle/{ => isle}/src/parser.rs (100%) rename cranelift/isle/{ => isle}/src/sema.rs (100%) create mode 100644 cranelift/isle/islec/Cargo.toml rename cranelift/isle/{src/bin/isle.rs => islec/src/main.rs} (100%) diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index 26a438a64e..63a6b3a949 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -70,17 +70,25 @@ dependencies = [ [[package]] name = "isle" version = "0.1.0" +dependencies = [ + "log", +] + +[[package]] +name = "islec" +version = "0.1.0" dependencies = [ "clap", "env_logger", + "isle", "log", ] [[package]] name = "libc" -version = "0.2.97" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "log" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml index 2d7da0ae6f..2f3fcbb680 100644 --- a/cranelift/isle/Cargo.toml +++ b/cranelift/isle/Cargo.toml @@ -1,11 +1,2 @@ -[package] -name = "isle" -version = "0.1.0" -authors = ["Chris Fallin "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" - -[dependencies] -log = "0.4" -env_logger = { version = "0.8", default-features = false } -clap = "2.33" +[workspace] +members = [ "isle", "islec" ] diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml new file mode 100644 index 0000000000..3fe78c9eb7 --- /dev/null +++ b/cranelift/isle/isle/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "isle" +version = "0.1.0" +authors = ["Chris Fallin "] +edition = "2018" +license = "Apache-2.0 WITH LLVM-exception" + +[dependencies] +log = "0.4" diff --git a/cranelift/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs similarity index 100% rename from cranelift/isle/src/ast.rs rename to cranelift/isle/isle/src/ast.rs diff --git a/cranelift/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs similarity index 100% rename from cranelift/isle/src/codegen.rs rename to cranelift/isle/isle/src/codegen.rs diff --git a/cranelift/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs similarity index 100% rename from cranelift/isle/src/compile.rs rename to cranelift/isle/isle/src/compile.rs diff --git a/cranelift/isle/src/error.rs b/cranelift/isle/isle/src/error.rs similarity index 100% rename from cranelift/isle/src/error.rs rename to cranelift/isle/isle/src/error.rs diff --git a/cranelift/isle/src/ir.rs b/cranelift/isle/isle/src/ir.rs similarity index 100% rename from cranelift/isle/src/ir.rs rename to cranelift/isle/isle/src/ir.rs diff --git a/cranelift/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs similarity index 100% rename from cranelift/isle/src/lexer.rs rename to cranelift/isle/isle/src/lexer.rs diff --git a/cranelift/isle/isle/src/lib.rs b/cranelift/isle/isle/src/lib.rs new file mode 100644 index 0000000000..5d9dcb088a --- /dev/null +++ b/cranelift/isle/isle/src/lib.rs @@ -0,0 +1,9 @@ +pub mod ast; +pub mod codegen; +pub mod compile; +pub mod error; +pub mod ir; +pub mod lexer; +pub mod parser; +pub mod sema; + diff --git a/cranelift/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs similarity index 100% rename from cranelift/isle/src/parser.rs rename to cranelift/isle/isle/src/parser.rs diff --git a/cranelift/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs similarity index 100% rename from cranelift/isle/src/sema.rs rename to cranelift/isle/isle/src/sema.rs diff --git a/cranelift/isle/islec/Cargo.toml b/cranelift/isle/islec/Cargo.toml new file mode 100644 index 0000000000..4454234640 --- /dev/null +++ b/cranelift/isle/islec/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "islec" +version = "0.1.0" +authors = ["Chris Fallin "] +edition = "2018" +license = "Apache-2.0 WITH LLVM-exception" + +[dependencies] +log = "0.4" +isle = { version = "*", path = "../isle/" } +env_logger = { version = "0.8", default-features = false } +clap = "2.33" diff --git a/cranelift/isle/src/bin/isle.rs b/cranelift/isle/islec/src/main.rs similarity index 100% rename from cranelift/isle/src/bin/isle.rs rename to cranelift/isle/islec/src/main.rs From d0ace640a2629f3ac7c9cd6709cc526367725996 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 14 Sep 2021 23:30:42 -0700 Subject: [PATCH 37/95] TODO update. --- cranelift/isle/TODO | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 0e7f3f3759..6a94f23175 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -2,9 +2,18 @@ - Clean up and factor the codegen properly. +- Get rid of the expression syntax ` Date: Wed, 15 Sep 2021 00:01:51 -0700 Subject: [PATCH 38/95] Support extern constants of any primitive type. --- cranelift/isle/TODO | 2 - cranelift/isle/isle/src/ast.rs | 22 +++- cranelift/isle/isle/src/codegen.rs | 23 ++++ cranelift/isle/isle/src/ir.rs | 41 ++++--- cranelift/isle/isle/src/parser.rs | 47 +++++++- cranelift/isle/isle/src/sema.rs | 140 ++++++++++++++++++------ cranelift/isle/isle_examples/test4.isle | 8 ++ 7 files changed, 222 insertions(+), 61 deletions(-) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 6a94f23175..495ded7b90 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -6,8 +6,6 @@ parse instead where we know the polarity of pattern-term args and parse in-args as exprs. -- Support extern constants. - - Look into whether optimizations are possible: - More in-depth fallibility analysis (avoid failure edges where possible) diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index 9e6e06f6e7..97584b5627 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -96,6 +96,8 @@ pub enum Pattern { Var { var: Ident, pos: Pos }, /// An operator that matches a constant integer value. ConstInt { val: i64, pos: Pos }, + /// An operator that matches an external constant value. + ConstPrim { val: Ident, pos: Pos }, /// An application of a type variant or term. Term { sym: Ident, @@ -166,9 +168,10 @@ impl Pattern { } } - &Pattern::Var { .. } | &Pattern::Wildcard { .. } | &Pattern::ConstInt { .. } => { - self.clone() - } + &Pattern::Var { .. } + | &Pattern::Wildcard { .. } + | &Pattern::ConstInt { .. } + | &Pattern::ConstPrim { .. } => self.clone(), &Pattern::MacroArg { .. } => unreachable!(), } } @@ -208,9 +211,10 @@ impl Pattern { } } - &Pattern::Var { .. } | &Pattern::Wildcard { .. } | &Pattern::ConstInt { .. } => { - self.clone() - } + &Pattern::Var { .. } + | &Pattern::Wildcard { .. } + | &Pattern::ConstInt { .. } + | &Pattern::ConstPrim { .. } => self.clone(), &Pattern::MacroArg { index, .. } => macro_args[index].clone(), } } @@ -218,6 +222,7 @@ impl Pattern { pub fn pos(&self) -> Pos { match self { &Pattern::ConstInt { pos, .. } + | &Pattern::ConstPrim { pos, .. } | &Pattern::And { pos, .. } | &Pattern::Term { pos, .. } | &Pattern::BindPattern { pos, .. } @@ -280,6 +285,8 @@ pub enum Expr { Var { name: Ident, pos: Pos }, /// A constant integer. ConstInt { val: i64, pos: Pos }, + /// A constant of some other primitive type. + ConstPrim { val: Ident, pos: Pos }, /// The `(let ((var ty val)*) body)` form. Let { defs: Vec, @@ -294,6 +301,7 @@ impl Expr { &Expr::Term { pos, .. } | &Expr::Var { pos, .. } | &Expr::ConstInt { pos, .. } + | &Expr::ConstPrim { pos, .. } | &Expr::Let { pos, .. } => pos, } } @@ -344,6 +352,8 @@ pub enum Extern { /// The position of this decl. pos: Pos, }, + /// An external constant: `(const $IDENT type)` form. + Const { name: Ident, ty: Ident, pos: Pos }, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 5eed4b7cb4..96d0b4c029 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -797,6 +797,23 @@ impl<'a> Codegen<'a> { self.const_int(val, ty) )?; } + &ExprInst::ConstPrim { ty, val } => { + let value = Value::Expr { + inst: id, + output: 0, + }; + self.define_val(&value, ctx, /* is_ref = */ false, ty); + let name = self.value_name(&value); + let ty_name = self.type_name(ty, /* by_ref = */ false); + writeln!( + code, + "{}let {}: {} = {};", + indent, + name, + ty_name, + self.typeenv.syms[val.index()], + )?; + } &ExprInst::CreateVariant { ref inputs, ty, @@ -955,6 +972,12 @@ impl<'a> Codegen<'a> { writeln!(code, "{}if {} == {} {{", indent, input, int_val)?; Ok(false) } + &PatternInst::MatchPrim { ref input, val, .. } => { + let input = self.value_by_val(input, ctx); + let sym = &self.typeenv.syms[val.index()]; + writeln!(code, "{}if {} == {} {{", indent, input, sym)?; + Ok(false) + } &PatternInst::MatchVariant { ref input, input_ty, diff --git a/cranelift/isle/isle/src/ir.rs b/cranelift/isle/isle/src/ir.rs index 82cc027d35..9c3171607e 100644 --- a/cranelift/isle/isle/src/ir.rs +++ b/cranelift/isle/isle/src/ir.rs @@ -32,6 +32,9 @@ pub enum PatternInst { int_val: i64, }, + /// Try matching the given value as the given constant. Produces no values. + MatchPrim { input: Value, ty: TypeId, val: Sym }, + /// Try matching the given value as the given variant, producing /// `|arg_tys|` values as output. MatchVariant { @@ -69,6 +72,9 @@ pub enum ExprInst { /// Produce a constant integer. ConstInt { ty: TypeId, val: i64 }, + /// Produce a constant extern value. + ConstPrim { ty: TypeId, val: Sym }, + /// Create a variant. CreateVariant { inputs: Vec<(Value, TypeId)>, @@ -96,6 +102,7 @@ impl ExprInst { pub fn visit_values(&self, mut f: F) { match self { &ExprInst::ConstInt { .. } => {} + &ExprInst::ConstPrim { .. } => {} &ExprInst::Construct { ref inputs, .. } | &ExprInst::CreateVariant { ref inputs, .. } => { for (input, _ty) in inputs { @@ -143,21 +150,6 @@ impl ExprSequence { None } } - - pub fn is_const_variant(&self) -> Option<(TypeId, VariantId)> { - if self.insts.len() == 2 && matches!(&self.insts[1], &ExprInst::Return { .. }) { - match &self.insts[0] { - &ExprInst::CreateVariant { - ref inputs, - ty, - variant, - } if inputs.len() == 0 => Some((ty, variant)), - _ => None, - } - } else { - None - } - } } #[derive(Clone, Copy, Debug)] @@ -196,6 +188,10 @@ impl PatternSequence { self.add_inst(PatternInst::MatchInt { input, ty, int_val }); } + fn add_match_prim(&mut self, input: Value, ty: TypeId, val: Sym) { + self.add_inst(PatternInst::MatchPrim { input, ty, val }); + } + fn add_match_variant( &mut self, input: Value, @@ -290,9 +286,15 @@ impl PatternSequence { // Assert that the value matches the constant integer. let input_val = input .to_value() - .expect("Cannot match an =var pattern against root term"); + .expect("Cannot match an integer pattern against root term"); self.add_match_int(input_val, ty, value); } + &Pattern::ConstPrim(ty, value) => { + let input_val = input + .to_value() + .expect("Cannot match a constant-primitive pattern against root term"); + self.add_match_prim(input_val, ty, value); + } &Pattern::Term(ty, term, ref args) => { match input { ValueOrArgs::ImplicitTermFromArgs(termid) => { @@ -435,6 +437,12 @@ impl ExprSequence { Value::Expr { inst, output: 0 } } + fn add_const_prim(&mut self, ty: TypeId, val: Sym) -> Value { + let inst = InstId(self.insts.len()); + self.add_inst(ExprInst::ConstPrim { ty, val }); + Value::Expr { inst, output: 0 } + } + fn add_create_variant( &mut self, inputs: &[(Value, TypeId)], @@ -490,6 +498,7 @@ impl ExprSequence { log::trace!("gen_expr: expr {:?}", expr); match expr { &Expr::ConstInt(ty, val) => self.add_const_int(ty, val), + &Expr::ConstPrim(ty, val) => self.add_const_prim(ty, val), &Expr::Let(_ty, ref bindings, ref subexpr) => { let mut vars = vars.clone(); for &(var, _var_ty, ref var_expr) in bindings { diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 77ca738f7d..81cf81c2cf 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -72,6 +72,13 @@ impl<'a> Parser<'a> { }) } + fn is_const(&self) -> bool { + self.is(|tok| match tok { + &Token::Symbol(ref tok_s) if tok_s.starts_with("$") => true, + _ => false, + }) + } + fn lparen(&mut self) -> ParseResult<()> { self.take(|tok| *tok == Token::LParen).map(|_| ()) } @@ -129,20 +136,20 @@ impl<'a> Parser<'a> { fn str_to_ident(&self, pos: Pos, s: &str) -> ParseResult { let first = s.chars().next().unwrap(); - if !first.is_alphabetic() && first != '_' { + if !first.is_alphabetic() && first != '_' && first != '$' { return Err(self.error( pos, - format!("Identifier '{}' does not start with letter or _", s), + format!("Identifier '{}' does not start with letter or _ or $", s), )); } if s.chars() .skip(1) - .any(|c| !c.is_alphanumeric() && c != '_' && c != '.') + .any(|c| !c.is_alphanumeric() && c != '_' && c != '.' && c != '$') { return Err(self.error( pos, format!( - "Identifier '{}' contains invalid character (not a-z, A-Z, 0-9, _, .)", + "Identifier '{}' contains invalid character (not a-z, A-Z, 0-9, _, ., $)", s ), )); @@ -156,6 +163,20 @@ impl<'a> Parser<'a> { self.str_to_ident(pos.unwrap(), &s) } + fn parse_const(&mut self) -> ParseResult { + let pos = self.pos(); + let ident = self.parse_ident()?; + if ident.0.starts_with("$") { + let s = &ident.0[1..]; + Ok(Ident(s.to_string(), ident.1)) + } else { + Err(self.error( + pos.unwrap(), + "Not a constant identifier; must start with a '$'".to_string(), + )) + } + } + fn parse_type(&mut self) -> ParseResult { let pos = self.pos(); let name = self.parse_ident()?; @@ -303,6 +324,16 @@ impl<'a> Parser<'a> { arg_polarity, infallible, }) + } else if self.is_sym_str("const") { + self.symbol()?; + let pos = self.pos(); + let name = self.parse_const()?; + let ty = self.parse_ident()?; + Ok(Extern::Const { + name, + ty, + pos: pos.unwrap(), + }) } else { Err(self.error( pos.unwrap(), @@ -355,6 +386,10 @@ impl<'a> Parser<'a> { val: self.int()?, pos, }) + } else if self.is_const() { + let pos = pos.unwrap(); + let val = self.parse_const()?; + Ok(Pattern::ConstPrim { val, pos }) } else if self.is_sym_str("_") { let pos = pos.unwrap(); self.symbol()?; @@ -448,6 +483,10 @@ impl<'a> Parser<'a> { let pos = pos.unwrap(); self.symbol()?; Ok(Expr::ConstInt { val: 0, pos }) + } else if self.is_const() { + let pos = pos.unwrap(); + let val = self.parse_const()?; + Ok(Expr::ConstPrim { val, pos }) } else if self.is_sym() { let pos = pos.unwrap(); let name = self.parse_ident()?; diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index b7ee0dace1..61f19cd9d9 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -35,6 +35,7 @@ pub struct TypeEnv { pub sym_map: HashMap, pub types: Vec, pub type_map: HashMap, + pub const_types: HashMap, pub errors: Vec, } @@ -238,6 +239,7 @@ pub enum Pattern { BindPattern(TypeId, VarId, Box), Var(TypeId, VarId), ConstInt(TypeId, i64), + ConstPrim(TypeId, Sym), Term(TypeId, TermId, Vec), Wildcard(TypeId), And(TypeId, Vec), @@ -254,6 +256,7 @@ pub enum Expr { Term(TypeId, TermId, Vec), Var(TypeId, VarId), ConstInt(TypeId, i64), + ConstPrim(TypeId, Sym), Let(TypeId, Vec<(VarId, TypeId, Box)>, Box), } @@ -263,6 +266,7 @@ impl Pattern { &Self::BindPattern(t, ..) => t, &Self::Var(t, ..) => t, &Self::ConstInt(t, ..) => t, + &Self::ConstPrim(t, ..) => t, &Self::Term(t, ..) => t, &Self::Wildcard(t, ..) => t, &Self::And(t, ..) => t, @@ -284,6 +288,7 @@ impl Expr { &Self::Term(t, ..) => t, &Self::Var(t, ..) => t, &Self::ConstInt(t, ..) => t, + &Self::ConstPrim(t, ..) => t, &Self::Let(t, ..) => t, } } @@ -297,6 +302,7 @@ impl TypeEnv { sym_map: HashMap::new(), types: vec![], type_map: HashMap::new(), + const_types: HashMap::new(), errors: vec![], }; @@ -340,6 +346,29 @@ impl TypeEnv { } } + // Now collect types for extern constants. + for def in &defs.defs { + match def { + &ast::Def::Extern(ast::Extern::Const { + ref name, + ref ty, + pos, + }) => { + let ty = tyenv.intern_mut(ty); + let ty = match tyenv.type_map.get(&ty) { + Some(ty) => *ty, + None => { + tyenv.report_error(pos, "Unknown type for constant".into()); + continue; + } + }; + let name = tyenv.intern_mut(name); + tyenv.const_types.insert(name, ty); + } + _ => {} + } + } + tyenv.return_errors()?; Ok(tyenv) @@ -674,25 +703,20 @@ impl TermEnv { vars: vec![], }; - let (lhs, ty) = match self.translate_pattern( - tyenv, - &rule.pattern, - None, - &mut bindings, - ) { - Some(x) => x, - None => { - // Keep going to collect more errors. - continue; - } - }; - let rhs = - match self.translate_expr(tyenv, &rule.expr, ty, &mut bindings) { + let (lhs, ty) = + match self.translate_pattern(tyenv, &rule.pattern, None, &mut bindings) { Some(x) => x, None => { + // Keep going to collect more errors. continue; } }; + let rhs = match self.translate_expr(tyenv, &rule.expr, ty, &mut bindings) { + Some(x) => x, + None => { + continue; + } + }; let rid = RuleId(self.rules.len()); self.rules.push(Rule { @@ -814,6 +838,20 @@ impl TermEnv { }; Some((Pattern::ConstInt(ty, val), ty)) } + &ast::Pattern::ConstPrim { ref val, pos } => { + let val = tyenv.intern_mut(val); + let const_ty = match tyenv.const_types.get(&val) { + Some(ty) => *ty, + None => { + tyenv.report_error(pos, "Unknown constant".into()); + return None; + } + }; + if expected_ty.is_some() && expected_ty != Some(const_ty) { + tyenv.report_error(pos, "Type mismatch for constant".into()); + } + Some((Pattern::ConstPrim(const_ty, val), const_ty)) + } &ast::Pattern::Wildcard { pos } => { let ty = match expected_ty { Some(t) => t, @@ -1045,8 +1083,7 @@ impl TermEnv { ) -> Option<(TermArgPattern, TypeId)> { match pat { &ast::TermArgPattern::Pattern(ref pat) => { - let (subpat, ty) = - self.translate_pattern(tyenv, pat, expected_ty, bindings)?; + let (subpat, ty) = self.translate_pattern(tyenv, pat, expected_ty, bindings)?; Some((TermArgPattern::Pattern(subpat), ty)) } &ast::TermArgPattern::Expr(ref expr) => { @@ -1152,6 +1189,29 @@ impl TermEnv { Some(Expr::Var(bv.ty, bv.id)) } &ast::Expr::ConstInt { val, .. } => Some(Expr::ConstInt(ty, val)), + &ast::Expr::ConstPrim { ref val, pos } => { + let val = tyenv.intern_mut(val); + let const_ty = match tyenv.const_types.get(&val) { + Some(ty) => *ty, + None => { + tyenv.report_error(pos, "Unknown constant".into()); + return None; + } + }; + if const_ty != ty { + 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[const_ty.index()].name(tyenv) + ), + ); + return None; + } + Some(Expr::ConstPrim(ty, val)) + } &ast::Expr::Let { ref defs, ref body, @@ -1191,15 +1251,13 @@ impl TermEnv { }; // Evaluate the variable's value. - let val = Box::new( - match self.translate_expr(tyenv, &def.val, ty, bindings) { - Some(e) => e, - None => { - // Keep going for more errors. - continue; - } - }, - ); + let val = Box::new(match self.translate_expr(tyenv, &def.val, ty, bindings) { + Some(e) => e, + None => { + // Keep going for more errors. + continue; + } + }); // Bind the var with the given type. let id = VarId(bindings.next_var); @@ -1240,14 +1298,30 @@ mod test { .expect("should parse"); let tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); - let sym_a = tyenv.intern(&Ident("A".to_string())).unwrap(); - let sym_b = tyenv.intern(&Ident("B".to_string())).unwrap(); - let sym_c = tyenv.intern(&Ident("C".to_string())).unwrap(); - let sym_a_b = tyenv.intern(&Ident("A.B".to_string())).unwrap(); - let sym_a_c = tyenv.intern(&Ident("A.C".to_string())).unwrap(); - let sym_u32 = tyenv.intern(&Ident("u32".to_string())).unwrap(); - let sym_f1 = tyenv.intern(&Ident("f1".to_string())).unwrap(); - let sym_f2 = tyenv.intern(&Ident("f2".to_string())).unwrap(); + let sym_a = tyenv + .intern(&Ident("A".to_string(), Default::default())) + .unwrap(); + let sym_b = tyenv + .intern(&Ident("B".to_string(), Default::default())) + .unwrap(); + let sym_c = tyenv + .intern(&Ident("C".to_string(), Default::default())) + .unwrap(); + let sym_a_b = tyenv + .intern(&Ident("A.B".to_string(), Default::default())) + .unwrap(); + let sym_a_c = tyenv + .intern(&Ident("A.C".to_string(), Default::default())) + .unwrap(); + let sym_u32 = tyenv + .intern(&Ident("u32".to_string(), Default::default())) + .unwrap(); + let sym_f1 = tyenv + .intern(&Ident("f1".to_string(), Default::default())) + .unwrap(); + let sym_f2 = tyenv + .intern(&Ident("f2".to_string(), Default::default())) + .unwrap(); assert_eq!(tyenv.type_map.get(&sym_u32).unwrap(), &TypeId(0)); assert_eq!(tyenv.type_map.get(&sym_a).unwrap(), &TypeId(1)); diff --git a/cranelift/isle/isle_examples/test4.isle b/cranelift/isle/isle_examples/test4.isle index 0e1f45901f..2035167239 100644 --- a/cranelift/isle/isle_examples/test4.isle +++ b/cranelift/isle/isle_examples/test4.isle @@ -7,6 +7,9 @@ (extern extractor Ext1 ext1) (extern extractor Ext2 ext2) +(extern const $A u32) +(extern const $B u32) + (decl C (bool) A) (extern constructor C c) @@ -32,3 +35,8 @@ (rule (Lower2 (Opcode.C)) (MachInst.F)) + +(decl F (Opcode) u32) +(rule + (F _) + $B) \ No newline at end of file From 91904aa756ae5e00cc75d0ac5556038864617589 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Wed, 15 Sep 2021 16:07:49 -0700 Subject: [PATCH 39/95] TODO items from call with fitzgen --- cranelift/isle/TODO | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 495ded7b90..7db6d3597e 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -15,3 +15,7 @@ - Build inlining and simplification: inline invocations of internal constructors, and eliminate ctor-etor or makevariant-matchvariant pairs. + +- Ideas from discussion with fitzgen + - Turn arg-polarity and exprs on extractors into purer "InstFormat" + - Extern types as associated types on Context trait \ No newline at end of file From 66ba1d89b561fcf8c55e4771297033c3ff7f68e5 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 23 Sep 2021 11:22:17 -0700 Subject: [PATCH 40/95] Remove TODO we don't want to do If the extern types are represented with associated types, then we can't match on them. --- cranelift/isle/TODO | 1 - 1 file changed, 1 deletion(-) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 7db6d3597e..087256a281 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -18,4 +18,3 @@ - Ideas from discussion with fitzgen - Turn arg-polarity and exprs on extractors into purer "InstFormat" - - Extern types as associated types on Context trait \ No newline at end of file From 5f5484ddbc985c858b4aaaa5021405ecc54940a5 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 27 Sep 2021 14:38:12 -0700 Subject: [PATCH 41/95] Use `matches!(..)` to make `TrieNode::is_empty` more concise --- cranelift/isle/isle/src/codegen.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 96d0b4c029..5a219a102a 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -234,10 +234,7 @@ enum TrieNode { impl TrieNode { fn is_empty(&self) -> bool { - match self { - &TrieNode::Empty => true, - _ => false, - } + matches!(self, &TrieNode::Empty) } fn insert( From 012f4b04d7980cbc0e9d63133020e767ebd36b92 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 27 Sep 2021 14:53:15 -0700 Subject: [PATCH 42/95] Update TODO --- cranelift/isle/TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cranelift/isle/TODO b/cranelift/isle/TODO index 087256a281..dfd7fce33b 100644 --- a/cranelift/isle/TODO +++ b/cranelift/isle/TODO @@ -18,3 +18,5 @@ - Ideas from discussion with fitzgen - Turn arg-polarity and exprs on extractors into purer "InstFormat" + - Emit two contexts: an immutable context for inputs and a mutable context for + outputs From 3535f82056a5d0b033f6dd82920c11821a4ae156 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 27 Sep 2021 14:53:38 -0700 Subject: [PATCH 43/95] Remove an unused field in `TermFunctionBuilder` --- cranelift/isle/isle/src/codegen.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 5a219a102a..f518fd12b0 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -423,14 +423,12 @@ impl TrieNode { /// the calling term). #[derive(Debug)] struct TermFunctionBuilder { - root_term: TermId, trie: TrieNode, } impl TermFunctionBuilder { - fn new(root_term: TermId) -> Self { + fn new() -> Self { TermFunctionBuilder { - root_term, trie: TrieNode::Empty, } } @@ -479,7 +477,7 @@ impl<'a> TermFunctionsBuilder<'a> { ); self.builders_by_term .entry(root_term) - .or_insert_with(|| TermFunctionBuilder::new(root_term)) + .or_insert_with(|| TermFunctionBuilder::new()) .add_rule(prio, pattern.clone(), expr.clone()); } } From 972dc00a9221fffdf77a7109ec4f990b3465a6c1 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 27 Sep 2021 15:57:32 -0700 Subject: [PATCH 44/95] Don't use `&mut dyn Write` in codegen We always use a `String` which has infallible writing so we can remove all these `Result`s and pretend-fallible methods. --- cranelift/isle/isle/src/codegen.rs | 231 +++++++++++++++-------------- cranelift/isle/isle/src/compile.rs | 4 +- 2 files changed, 120 insertions(+), 115 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index f518fd12b0..cc5dda9994 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -1,8 +1,8 @@ //! Generate Rust code from a series of Sequences. use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; +use crate::sema::ExternalSig; use crate::sema::{RuleId, TermEnv, TermId, Type, TypeEnv, TypeId, Variant}; -use crate::{error::Error, sema::ExternalSig}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; @@ -506,60 +506,56 @@ struct BodyContext { } impl<'a> Codegen<'a> { - pub fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Result, Error> { + pub fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Codegen<'a> { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); log::trace!("builder: {:?}", builder); let functions_by_term = builder.finalize(); - Ok(Codegen { + Codegen { typeenv, termenv, functions_by_term, - }) + } } - pub fn generate_rust(&self) -> Result { + pub fn generate_rust(&self) -> String { let mut code = String::new(); - self.generate_header(&mut code)?; - self.generate_ctx_trait(&mut code)?; - self.generate_internal_types(&mut code)?; - self.generate_internal_term_constructors(&mut code)?; + self.generate_header(&mut code); + self.generate_ctx_trait(&mut code); + self.generate_internal_types(&mut code); + self.generate_internal_term_constructors(&mut code); - Ok(code) + code } - fn generate_header(&self, code: &mut dyn Write) -> Result<(), Error> { - writeln!(code, "// GENERATED BY ISLE. DO NOT EDIT!")?; - writeln!(code, "//")?; + fn generate_header(&self, code: &mut String) { + writeln!(code, "// GENERATED BY ISLE. DO NOT EDIT!").unwrap(); + writeln!(code, "//").unwrap(); writeln!( code, "// Generated automatically from the instruction-selection DSL code in:", - )?; + ) + .unwrap(); for file in &self.typeenv.filenames { - writeln!(code, "// - {}", file)?; + writeln!(code, "// - {}", file).unwrap(); } writeln!( code, "\n#![allow(dead_code, unreachable_code, unreachable_patterns)]" - )?; + ) + .unwrap(); writeln!( code, "#![allow(unused_imports, unused_variables, non_snake_case)]" - )?; + ) + .unwrap(); - writeln!(code, "\nuse super::*; // Pulls in all external types.")?; - - Ok(()) + writeln!(code, "\nuse super::*; // Pulls in all external types.").unwrap(); } - fn generate_trait_sig( - &self, - code: &mut dyn Write, - indent: &str, - sig: &ExternalSig, - ) -> Result<(), Error> { + fn generate_trait_sig(&self, code: &mut String, indent: &str, sig: &ExternalSig) { writeln!( code, "{}fn {}(&mut self, {}) -> {}({},){};", @@ -578,37 +574,38 @@ impl<'a> Codegen<'a> { .collect::>() .join(", "), if sig.infallible { "" } else { ">" }, - )?; - Ok(()) + ) + .unwrap(); } - fn generate_ctx_trait(&self, code: &mut dyn Write) -> Result<(), Error> { - writeln!(code, "")?; + fn generate_ctx_trait(&self, code: &mut String) { + writeln!(code, "").unwrap(); writeln!( code, "/// Context during lowering: an implementation of this trait" - )?; + ) + .unwrap(); writeln!( code, "/// must be provided with all external constructors and extractors." - )?; + ) + .unwrap(); writeln!( code, "/// A mutable borrow is passed along through all lowering logic." - )?; - writeln!(code, "pub trait Context {{")?; + ) + .unwrap(); + writeln!(code, "pub trait Context {{").unwrap(); for term in &self.termenv.terms { if term.is_external() { let ext_sig = term.to_sig(self.typeenv).unwrap(); - self.generate_trait_sig(code, " ", &ext_sig)?; + self.generate_trait_sig(code, " ", &ext_sig); } } - writeln!(code, "}}")?; - - Ok(()) + writeln!(code, "}}").unwrap(); } - fn generate_internal_types(&self, code: &mut dyn Write) -> Result<(), Error> { + fn generate_internal_types(&self, code: &mut String) { for ty in &self.typeenv.types { match ty { &Type::Enum { @@ -624,30 +621,30 @@ impl<'a> Codegen<'a> { "\n/// Internal type {}: defined at {}.", name, pos.pretty_print_line(&self.typeenv.filenames[..]) - )?; - writeln!(code, "#[derive(Clone, Debug)]")?; - writeln!(code, "pub enum {} {{", name)?; + ) + .unwrap(); + writeln!(code, "#[derive(Clone, Debug)]").unwrap(); + writeln!(code, "pub enum {} {{", name).unwrap(); for variant in variants { let name = &self.typeenv.syms[variant.name.index()]; if variant.fields.is_empty() { - writeln!(code, " {},", name)?; + writeln!(code, " {},", name).unwrap(); } else { - writeln!(code, " {} {{", name)?; + writeln!(code, " {} {{", name).unwrap(); for field in &variant.fields { let name = &self.typeenv.syms[field.name.index()]; let ty_name = self.typeenv.types[field.ty.index()].name(&self.typeenv); - writeln!(code, " {}: {},", name, ty_name)?; + writeln!(code, " {}: {},", name, ty_name).unwrap(); } - writeln!(code, " }},")?; + writeln!(code, " }},").unwrap(); } } - writeln!(code, "}}")?; + writeln!(code, "}}").unwrap(); } _ => {} } } - Ok(()) } fn type_name(&self, typeid: TypeId, by_ref: bool) -> String { @@ -718,7 +715,7 @@ impl<'a> Codegen<'a> { } } - fn generate_internal_term_constructors(&self, code: &mut dyn Write) -> Result<(), Error> { + fn generate_internal_term_constructors(&self, code: &mut String) { for (&termid, trie) in &self.functions_by_term { let termdata = &self.termenv.terms[termid.index()]; @@ -744,35 +741,35 @@ impl<'a> Codegen<'a> { code, "\n// Generated as internal constructor for term {}.", self.typeenv.syms[termdata.name.index()], - )?; + ) + .unwrap(); writeln!( code, "pub fn {}(ctx: &mut C, {}) -> Option<{}> {{", sig.func_name, args, ret, - )?; + ) + .unwrap(); let mut body_ctx: BodyContext = Default::default(); let returned = - self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx)?; + self.generate_body(code, /* depth = */ 0, trie, " ", &mut body_ctx); if !returned { - writeln!(code, " return None;")?; + writeln!(code, " return None;").unwrap(); } - writeln!(code, "}}")?; + writeln!(code, "}}").unwrap(); } - - Ok(()) } fn generate_expr_inst( &self, - code: &mut dyn Write, + code: &mut String, id: InstId, inst: &ExprInst, indent: &str, ctx: &mut BodyContext, returns: &mut Vec<(usize, String)>, - ) -> Result<(), Error> { + ) { log::trace!("generate_expr_inst: {:?}", inst); match inst { &ExprInst::ConstInt { ty, val } => { @@ -790,7 +787,8 @@ impl<'a> Codegen<'a> { name, ty_name, self.const_int(val, ty) - )?; + ) + .unwrap(); } &ExprInst::ConstPrim { ty, val } => { let value = Value::Expr { @@ -807,7 +805,8 @@ impl<'a> Codegen<'a> { name, ty_name, self.typeenv.syms[val.index()], - )?; + ) + .unwrap(); } &ExprInst::CreateVariant { ref inputs, @@ -840,17 +839,19 @@ impl<'a> Codegen<'a> { code, "{}let {} = {};", indent, outputname, full_variant_name - )?; + ) + .unwrap(); } else { writeln!( code, "{}let {} = {} {{", indent, outputname, full_variant_name - )?; + ) + .unwrap(); for input_field in input_fields { - writeln!(code, "{} {},", indent, input_field)?; + writeln!(code, "{} {},", indent, input_field).unwrap(); } - writeln!(code, "{}}};", indent)?; + writeln!(code, "{}}};", indent).unwrap(); } self.define_val(&output, ctx, /* is_ref = */ false, ty); } @@ -883,7 +884,8 @@ impl<'a> Codegen<'a> { sig.full_name, input_exprs.join(", "), fallible_try, - )?; + ) + .unwrap(); self.define_val(&output, ctx, /* is_ref = */ false, termdata.ret_ty); } &ExprInst::Return { @@ -893,8 +895,6 @@ impl<'a> Codegen<'a> { returns.push((index, value_expr)); } } - - Ok(()) } fn match_variant_binders( @@ -925,12 +925,12 @@ impl<'a> Codegen<'a> { /// infallible. fn generate_pattern_inst( &self, - code: &mut dyn Write, + code: &mut String, id: InstId, inst: &PatternInst, indent: &str, ctx: &mut BodyContext, - ) -> Result { + ) -> bool { match inst { &PatternInst::Arg { index, ty } => { let output = Value::Pattern { @@ -942,7 +942,7 @@ impl<'a> Codegen<'a> { &Type::Primitive(..) => false, _ => true, }; - writeln!(code, "{}let {} = arg{};", indent, outputname, index)?; + writeln!(code, "{}let {} = arg{};", indent, outputname, index).unwrap(); self.define_val( &Value::Pattern { inst: id, @@ -952,26 +952,26 @@ impl<'a> Codegen<'a> { is_ref, ty, ); - Ok(true) + true } &PatternInst::MatchEqual { ref a, ref b, .. } => { let a = self.value_by_ref(a, ctx); let b = self.value_by_ref(b, ctx); - writeln!(code, "{}if {} == {} {{", indent, a, b)?; - Ok(false) + writeln!(code, "{}if {} == {} {{", indent, a, b).unwrap(); + false } &PatternInst::MatchInt { ref input, int_val, .. } => { let input = self.value_by_val(input, ctx); - writeln!(code, "{}if {} == {} {{", indent, input, int_val)?; - Ok(false) + writeln!(code, "{}if {} == {} {{", indent, input, int_val).unwrap(); + false } &PatternInst::MatchPrim { ref input, val, .. } => { let input = self.value_by_val(input, ctx); let sym = &self.typeenv.syms[val.index()]; - writeln!(code, "{}if {} == {} {{", indent, input, sym)?; - Ok(false) + writeln!(code, "{}if {} == {} {{", indent, input, sym).unwrap(); + false } &PatternInst::MatchVariant { ref input, @@ -997,8 +997,9 @@ impl<'a> Codegen<'a> { code, "{}if let {}::{} {} = {} {{", indent, ty_name, variantname, args, input - )?; - Ok(false) + ) + .unwrap(); + false } &PatternInst::Extract { ref inputs, @@ -1035,8 +1036,9 @@ impl<'a> Codegen<'a> { output_binders.join(", "), sig.full_name, input_values.join(", "), - )?; - Ok(true) + ) + .unwrap(); + true } else { writeln!( code, @@ -1045,8 +1047,9 @@ impl<'a> Codegen<'a> { output_binders.join(", "), sig.full_name, input_values.join(", "), - )?; - Ok(false) + ) + .unwrap(); + false } } &PatternInst::Expr { @@ -1065,25 +1068,26 @@ impl<'a> Codegen<'a> { indent, self.value_name(&output), self.const_int(val, ty), - )?; + ) + .unwrap(); self.define_val(&output, ctx, /* is_ref = */ false, ty); - Ok(true) + true } &PatternInst::Expr { ref seq, output_ty, .. } => { let closure_name = format!("closure{}", id.index()); - writeln!(code, "{}let {} = || {{", indent, closure_name)?; + writeln!(code, "{}let {} = || {{", indent, closure_name).unwrap(); let subindent = format!("{} ", indent); let mut subctx = ctx.clone(); let mut returns = vec![]; for (id, inst) in seq.insts.iter().enumerate() { let id = InstId(id); - self.generate_expr_inst(code, id, inst, &subindent, &mut subctx, &mut returns)?; + self.generate_expr_inst(code, id, inst, &subindent, &mut subctx, &mut returns); } assert_eq!(returns.len(), 1); - writeln!(code, "{}return Some({});", subindent, returns[0].1)?; - writeln!(code, "{}}};", indent)?; + writeln!(code, "{}return Some({});", subindent, returns[0].1).unwrap(); + writeln!(code, "{}}};", indent).unwrap(); let output = Value::Pattern { inst: id, @@ -1095,22 +1099,23 @@ impl<'a> Codegen<'a> { indent, self.value_binder(&output, /* is_ref = */ false, output_ty), closure_name - )?; + ) + .unwrap(); self.define_val(&output, ctx, /* is_ref = */ false, output_ty); - Ok(false) + false } } } fn generate_body( &self, - code: &mut dyn Write, + code: &mut String, depth: usize, trie: &TrieNode, indent: &str, ctx: &mut BodyContext, - ) -> Result { + ) -> bool { log::trace!("generate_body: trie {:?}", trie); let mut returned = false; match trie { @@ -1122,16 +1127,17 @@ impl<'a> Codegen<'a> { "{}// Rule at {}.", indent, output.pos.pretty_print_line(&self.typeenv.filenames[..]) - )?; + ) + .unwrap(); // If this is a leaf node, generate the ExprSequence and return. let mut returns = vec![]; for (id, inst) in output.insts.iter().enumerate() { let id = InstId(id); - self.generate_expr_inst(code, id, inst, indent, ctx, &mut returns)?; + self.generate_expr_inst(code, id, inst, indent, ctx, &mut returns); } assert_eq!(returns.len(), 1); - writeln!(code, "{}return Some({});", indent, returns[0].1)?; + writeln!(code, "{}return Some({});", indent, returns[0].1).unwrap(); returned = true; } @@ -1181,7 +1187,7 @@ impl<'a> Codegen<'a> { // a `match` form if there are at least two // adjacent options. if last - i > 1 { - self.generate_body_matches(code, depth, &edges[i..last], indent, ctx)?; + self.generate_body_matches(code, depth, &edges[i..last], indent, ctx); i = last; continue; } else { @@ -1194,18 +1200,17 @@ impl<'a> Codegen<'a> { match symbol { &TrieSymbol::EndOfMatch => { - returned = - self.generate_body(code, depth + 1, node, indent, ctx)?; + returned = self.generate_body(code, depth + 1, node, indent, ctx); } &TrieSymbol::Match { ref op } => { let id = InstId(depth); let infallible = - self.generate_pattern_inst(code, id, op, indent, ctx)?; + self.generate_pattern_inst(code, id, op, indent, ctx); let i = if infallible { indent } else { &subindent[..] }; let sub_returned = - self.generate_body(code, depth + 1, node, i, ctx)?; + self.generate_body(code, depth + 1, node, i, ctx); if !infallible { - writeln!(code, "{}}}", indent)?; + writeln!(code, "{}}}", indent).unwrap(); } if infallible && sub_returned { returned = true; @@ -1218,17 +1223,17 @@ impl<'a> Codegen<'a> { } } - Ok(returned) + returned } fn generate_body_matches( &self, - code: &mut dyn Write, + code: &mut String, depth: usize, edges: &[TrieEdge], indent: &str, ctx: &mut BodyContext, - ) -> Result<(), Error> { + ) { let (input, input_ty) = match &edges[0].symbol { &TrieSymbol::Match { op: @@ -1254,7 +1259,8 @@ impl<'a> Codegen<'a> { "{}match {} {{", indent, self.value_by_ref(&input, ctx) - )?; + ) + .unwrap(); // Emit each case. for &TrieEdge { @@ -1288,18 +1294,17 @@ impl<'a> Codegen<'a> { code, "{} &{}::{} {} => {{", indent, input_ty_name, variantname, fields, - )?; + ) + .unwrap(); let subindent = format!("{} ", indent); - self.generate_body(code, depth + 1, node, &subindent, ctx)?; - writeln!(code, "{} }}", indent)?; + self.generate_body(code, depth + 1, node, &subindent, ctx); + writeln!(code, "{} }}", indent).unwrap(); } // Always add a catchall, because we don't do exhaustiveness // checking on the MatcHVariants. - writeln!(code, "{} _ => {{}}", indent)?; + writeln!(code, "{} _ => {{}}", indent).unwrap(); - writeln!(code, "{}}}", indent)?; - - Ok(()) + writeln!(code, "{}}}", indent).unwrap(); } } diff --git a/cranelift/isle/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs index 8f98b8c1c6..69d7a2aa5e 100644 --- a/cranelift/isle/isle/src/compile.rs +++ b/cranelift/isle/isle/src/compile.rs @@ -6,6 +6,6 @@ use crate::{ast, codegen, sema}; pub fn compile(defs: &ast::Defs) -> Result> { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; - let codegen = codegen::Codegen::compile(&typeenv, &termenv).map_err(|e| vec![e])?; - codegen.generate_rust().map_err(|e| vec![e]) + let codegen = codegen::Codegen::compile(&typeenv, &termenv); + Ok(codegen.generate_rust()) } From 922a3886d5c7ce39fcb5c8a029f68603ef941a18 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 12:03:11 -0700 Subject: [PATCH 45/95] Fix `let` variable typing rules --- cranelift/isle/isle/src/sema.rs | 2 +- cranelift/isle/isle_examples/let.isle | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 cranelift/isle/isle_examples/let.isle diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 61f19cd9d9..71491f84c6 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -1251,7 +1251,7 @@ impl TermEnv { }; // Evaluate the variable's value. - let val = Box::new(match self.translate_expr(tyenv, &def.val, ty, bindings) { + let val = Box::new(match self.translate_expr(tyenv, &def.val, tid, bindings) { Some(e) => e, None => { // Keep going for more errors. diff --git a/cranelift/isle/isle_examples/let.isle b/cranelift/isle/isle_examples/let.isle new file mode 100644 index 0000000000..c43d1ec668 --- /dev/null +++ b/cranelift/isle/isle_examples/let.isle @@ -0,0 +1,21 @@ +(type u32 (primitive u32)) +(type A (enum (Add (x u32) (y u32)) (Sub (x u32) (y u32)))) +(type B (enum (B (z u32)))) + +(decl Sub (u32 u32) u32) +(extern constructor Sub sub) + +(decl Add (u32 u32) u32) +(extern constructor Add add) + +(decl Lower (A) B) + +(rule + (Lower (A.Add x y)) + (let ((z u32 (Add x y))) + (B.B z))) + +(rule + (Lower (A.Sub x y)) + (let ((z u32 (Sub x y))) + (B.B z))) From b93304b327e1856483c95afb778372a18a0e8fde Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 12:11:43 -0700 Subject: [PATCH 46/95] Add docs to all public exports and `deny(missing_docs)` going forward --- cranelift/isle/isle/README.md | 9 + cranelift/isle/isle/src/ast.rs | 13 +- cranelift/isle/isle/src/codegen.rs | 17 +- cranelift/isle/isle/src/compile.rs | 4 +- cranelift/isle/isle/src/error.rs | 7 + cranelift/isle/isle/src/ir.rs | 144 +++++++++++---- cranelift/isle/isle/src/lexer.rs | 31 ++++ cranelift/isle/isle/src/lib.rs | 21 ++- cranelift/isle/isle/src/parser.rs | 8 +- cranelift/isle/isle/src/sema.rs | 272 ++++++++++++++++++++++------- 10 files changed, 420 insertions(+), 106 deletions(-) create mode 100644 cranelift/isle/isle/README.md diff --git a/cranelift/isle/isle/README.md b/cranelift/isle/isle/README.md new file mode 100644 index 0000000000..fbd1d48e08 --- /dev/null +++ b/cranelift/isle/isle/README.md @@ -0,0 +1,9 @@ +# ISLE: Instruction Selection / Lowering Expressions + +ISLE is a domain specific language (DSL) for instruction selection and lowering +clif instructions to vcode's `MachInst`s in Cranelift. + +ISLE is a statically-typed term-rewriting language. You define rewriting rules +that map input terms (clif instructions) into output terms (`MachInst`s). These +rules get compiled down into Rust source test that uses a tree of `match` +expressions that is as good or better than what you would have written by hand. diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index 97584b5627..489e0b81d1 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -1,3 +1,7 @@ +//! Abstract syntax tree (AST) created from parsed ISLE. + +#![allow(missing_docs)] + use crate::lexer::Pos; /// The parsed form of an ISLE file. @@ -356,12 +360,13 @@ pub enum Extern { Const { name: Ident, ty: Ident, pos: Pos }, } +/// Whether an argument is an input or an output. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ArgPolarity { - /// An arg that must be given an Expr in the pattern and passes - /// data *to* the extractor op. + /// An arg that must be given an Expr in the pattern and passes data *to* + /// the extractor op. Input, - /// An arg that must be given a regular pattern (not Expr) and - /// receives data *from* the extractor op. + /// An arg that must be given a regular pattern (not Expr) and receives data + /// *from* the extractor op. Output, } diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index cc5dda9994..576fc0aa75 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -6,6 +6,11 @@ use crate::sema::{RuleId, TermEnv, TermId, Type, TypeEnv, TypeId, Variant}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; +/// Emit Rust source code for the given type and term environments. +pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv) -> String { + Codegen::compile(typeenv, termenv).generate_rust() +} + /// One "input symbol" for the decision tree that handles matching on /// a term. Each symbol represents one step: we either run a match op, /// or we finish the match. @@ -493,7 +498,7 @@ impl<'a> TermFunctionsBuilder<'a> { } #[derive(Clone, Debug)] -pub struct Codegen<'a> { +struct Codegen<'a> { typeenv: &'a TypeEnv, termenv: &'a TermEnv, functions_by_term: HashMap, @@ -506,7 +511,7 @@ struct BodyContext { } impl<'a> Codegen<'a> { - pub fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Codegen<'a> { + fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Codegen<'a> { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); log::trace!("builder: {:?}", builder); @@ -518,7 +523,7 @@ impl<'a> Codegen<'a> { } } - pub fn generate_rust(&self) -> String { + fn generate_rust(&self) -> String { let mut code = String::new(); self.generate_header(&mut code); @@ -561,7 +566,7 @@ impl<'a> Codegen<'a> { "{}fn {}(&mut self, {}) -> {}({},){};", indent, sig.func_name, - sig.arg_tys + sig.param_tys .iter() .enumerate() .map(|(i, &ty)| format!("arg{}: {}", i, self.type_name(ty, /* by_ref = */ true))) @@ -728,7 +733,7 @@ impl<'a> Codegen<'a> { let sig = termdata.to_sig(self.typeenv).unwrap(); let args = sig - .arg_tys + .param_tys .iter() .enumerate() .map(|(i, &ty)| format!("arg{}: {}", i, self.type_name(ty, true))) @@ -874,7 +879,7 @@ impl<'a> Codegen<'a> { let outputname = self.value_name(&output); let termdata = &self.termenv.terms[term.index()]; let sig = termdata.to_sig(self.typeenv).unwrap(); - assert_eq!(input_exprs.len(), sig.arg_tys.len()); + assert_eq!(input_exprs.len(), sig.param_tys.len()); let fallible_try = if infallible { "" } else { "?" }; writeln!( code, diff --git a/cranelift/isle/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs index 69d7a2aa5e..68304852e7 100644 --- a/cranelift/isle/isle/src/compile.rs +++ b/cranelift/isle/isle/src/compile.rs @@ -3,9 +3,9 @@ use crate::error::Error; use crate::{ast, codegen, sema}; +/// Compile the given AST definitions into Rust source code. pub fn compile(defs: &ast::Defs) -> Result> { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; - let codegen = codegen::Codegen::compile(&typeenv, &termenv); - Ok(codegen.generate_rust()) + Ok(codegen::codegen(&typeenv, &termenv)) } diff --git a/cranelift/isle/isle/src/error.rs b/cranelift/isle/isle/src/error.rs index 70a2140401..b123511ddd 100644 --- a/cranelift/isle/isle/src/error.rs +++ b/cranelift/isle/isle/src/error.rs @@ -3,14 +3,21 @@ use crate::lexer::Pos; use std::fmt; +/// Errors produced by ISLE. #[derive(Clone, Debug)] pub enum Error { + /// The input ISLE source has an error. CompileError { + /// The error message. msg: String, + /// The ISLE source filename where the error occurs. filename: String, + /// The position within the file that the error occurs at. pos: Pos, }, + /// An error from elsewhere in the system. SystemError { + /// The error message. msg: String, }, } diff --git a/cranelift/isle/isle/src/ir.rs b/cranelift/isle/isle/src/ir.rs index 9c3171607e..3138537eb8 100644 --- a/cranelift/isle/isle/src/ir.rs +++ b/cranelift/isle/isle/src/ir.rs @@ -1,18 +1,31 @@ //! Lowered matching IR. -use crate::declare_id; use crate::lexer::Pos; use crate::sema::*; use std::collections::HashMap; -declare_id!(InstId); +declare_id!( + /// The id of an instruction in a `PatternSequence`. + InstId +); +/// A value produced by a LHS or RHS instruction. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Value { /// A value produced by an instruction in the Pattern (LHS). - Pattern { inst: InstId, output: usize }, + Pattern { + /// The instruction that produces this value. + inst: InstId, + /// This value is the `output`th value produced by this pattern. + output: usize, + }, /// A value produced by an instruction in the Expr (RHS). - Expr { inst: InstId, output: usize }, + Expr { + /// The instruction that produces this value. + inst: InstId, + /// This value is the `output`th value produced by this expression. + output: usize, + }, } /// A single Pattern instruction. @@ -20,48 +33,81 @@ pub enum Value { pub enum PatternInst { /// Get the Nth input argument, which corresponds to the Nth field /// of the root term. - Arg { index: usize, ty: TypeId }, + Arg { + /// The index of the argument to get. + index: usize, + /// The type of the argument. + ty: TypeId, + }, /// Match a value as equal to another value. Produces no values. - MatchEqual { a: Value, b: Value, ty: TypeId }, + MatchEqual { + /// The first value. + a: Value, + /// The second value. + b: Value, + /// The type of the values. + ty: TypeId, + }, /// Try matching the given value as the given integer. Produces no values. MatchInt { + /// The value to match on. input: Value, + /// The value's type. ty: TypeId, + /// The integer to match against the value. int_val: i64, }, /// Try matching the given value as the given constant. Produces no values. - MatchPrim { input: Value, ty: TypeId, val: Sym }, - - /// Try matching the given value as the given variant, producing - /// `|arg_tys|` values as output. - MatchVariant { + MatchPrim { + /// The value to match on. input: Value, + /// The type of the value. + ty: TypeId, + /// The primitive to match against the value. + val: Sym, + }, + + /// Try matching the given value as the given variant, producing `|arg_tys|` + /// values as output. + MatchVariant { + /// The value to match on. + input: Value, + /// The type of the value. input_ty: TypeId, + /// The types of values produced upon a successful match. arg_tys: Vec, + /// The value type's variant that we are matching against. variant: VariantId, }, - /// Invoke an extractor, taking the given values as input (the - /// first is the value to extract, the other are the - /// `Input`-polarity extractor args) and producing an output valu - /// efor each `Output`-polarity extractor arg. + /// Invoke an extractor, taking the given values as input (the first is the + /// value to extract, the other are the `Input`-polarity extractor args) and + /// producing an output value for each `Output`-polarity extractor arg. Extract { + /// The value to extract, followed by polarity extractor args. inputs: Vec, + /// The types of the inputs. input_tys: Vec, + /// The types of the output values produced upon a successful match. output_tys: Vec, + /// This extractor's term. term: TermId, + /// Whether this extraction is infallible or not. infallible: bool, }, - /// Evaluate an expression and provide the given value as the - /// result of this match instruction. The expression has access to - /// the pattern-values up to this point in the sequence. + /// Evaluate an expression and provide the given value as the result of this + /// match instruction. The expression has access to the pattern-values up to + /// this point in the sequence. Expr { + /// The expression to evaluate. seq: ExprSequence, + /// The value produced by the expression. output: Value, + /// The type of the output value. output_ty: TypeId, }, } @@ -70,35 +116,58 @@ pub enum PatternInst { #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum ExprInst { /// Produce a constant integer. - ConstInt { ty: TypeId, val: i64 }, + ConstInt { + /// This integer type. + ty: TypeId, + /// The integer value. Must fit within the type. + val: i64, + }, /// Produce a constant extern value. - ConstPrim { ty: TypeId, val: Sym }, + ConstPrim { + /// The primitive type. + ty: TypeId, + /// The primitive value. + val: Sym, + }, /// Create a variant. CreateVariant { + /// The input arguments that will make up this variant's fields. + /// + /// These must be in the same order as the variant's fields. inputs: Vec<(Value, TypeId)>, + /// The enum type. ty: TypeId, + /// The variant within the enum that we are contructing. variant: VariantId, }, /// Invoke a constructor. Construct { + /// The arguments to the constructor. inputs: Vec<(Value, TypeId)>, + /// The type of the constructor. ty: TypeId, + /// The constructor term. term: TermId, + /// Whether this constructor is infallible or not. infallible: bool, }, /// Set the Nth return value. Produces no values. Return { + /// The index of the return value to set. index: usize, + /// The type of the return value. ty: TypeId, + /// The value to set as the `index`th return value. value: Value, }, } impl ExprInst { + /// Invoke `f` for each value in this expression. pub fn visit_values(&self, mut f: F) { match self { &ExprInst::ConstInt { .. } => {} @@ -117,29 +186,34 @@ impl ExprInst { } /// A linear sequence of instructions that match on and destructure an -/// argument. A pattern is fallible (may not match). If it does not -/// fail, its result consists of the values produced by the -/// `PatternInst`s, which may be used by a subsequent `Expr`. +/// argument. A pattern is fallible (may not match). If it does not fail, its +/// result consists of the values produced by the `PatternInst`s, which may be +/// used by a subsequent `Expr`. #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct PatternSequence { - /// Instruction sequence for pattern. InstId indexes into this - /// sequence for `Value::Pattern` values. + /// Instruction sequence for pattern. + /// + /// `InstId` indexes into this sequence for `Value::Pattern` values. pub insts: Vec, } -/// A linear sequence of instructions that produce a new value from -/// the right-hand side of a rule, given bindings that come from a -/// `Pattern` derived from the left-hand side. +/// A linear sequence of instructions that produce a new value from the +/// right-hand side of a rule, given bindings that come from a `Pattern` derived +/// from the left-hand side. #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct ExprSequence { - /// Instruction sequence for expression. InstId indexes into this - /// sequence for `Value::Expr` values. + /// Instruction sequence for expression. + /// + /// `InstId` indexes into this sequence for `Value::Expr` values. pub insts: Vec, /// Position at which the rule producing this sequence was located. pub pos: Pos, } impl ExprSequence { + /// Is this expression sequence producing a constant integer? + /// + /// If so, return the integer type and the constant. pub fn is_const_int(&self) -> Option<(TypeId, i64)> { if self.insts.len() == 2 && matches!(&self.insts[1], &ExprInst::Return { .. }) { match &self.insts[0] { @@ -499,13 +573,17 @@ impl ExprSequence { match expr { &Expr::ConstInt(ty, val) => self.add_const_int(ty, val), &Expr::ConstPrim(ty, val) => self.add_const_prim(ty, val), - &Expr::Let(_ty, ref bindings, ref subexpr) => { + &Expr::Let { + ty: _ty, + ref bindings, + ref body, + } => { let mut vars = vars.clone(); for &(var, _var_ty, ref var_expr) in bindings { let var_value = self.gen_expr(typeenv, termenv, &*var_expr, &vars); vars.insert(var, var_value); } - self.gen_expr(typeenv, termenv, &*subexpr, &vars) + self.gen_expr(typeenv, termenv, body, &vars) } &Expr::Var(_ty, var_id) => vars.get(&var_id).cloned().unwrap(), &Expr::Term(ty, term, ref arg_exprs) => { @@ -535,7 +613,7 @@ impl ExprSequence { /* infallible = */ true, ) } - _ => panic!("Should have been caught by typechecking"), + otherwise => panic!("Should have been caught by typechecking: {:?}", otherwise), } } } diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index 372d169585..9a8fa92bb8 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -3,8 +3,14 @@ use crate::error::Error; use std::borrow::Cow; +/// The lexer. +/// +/// Breaks source text up into a sequence of tokens (with source positions). #[derive(Clone, Debug)] pub struct Lexer<'a> { + /// Arena of filenames from the input source. + /// + /// Indexed via `Pos::file`. pub filenames: Vec, file_starts: Vec, buf: Cow<'a, [u8]>, @@ -12,34 +18,52 @@ pub struct Lexer<'a> { lookahead: Option<(Pos, Token)>, } +/// A source position. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] pub struct Pos { + /// This source position's file. + /// + /// Indexes into `Lexer::filenames` early in the compiler pipeline, and + /// later into `TypeEnv::filenames` once we get into semantic analysis. pub file: usize, + /// This source position's byte offset in the file. pub offset: usize, + /// This source position's line number in the file. pub line: usize, + /// This source position's column number in the file. pub col: usize, } impl Pos { + /// Print this source position as `file.isle:12:34`. pub fn pretty_print(&self, filenames: &[String]) -> String { format!("{}:{}:{}", filenames[self.file], self.line, self.col) } + /// Print this source position as `file.isle line 12`. pub fn pretty_print_line(&self, filenames: &[String]) -> String { format!("{} line {}", filenames[self.file], self.line) } } +/// A token of ISLE source. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Token { + /// Left paren. LParen, + /// Right paren. RParen, + /// A symbol, e.g. `Foo`. Symbol(String), + /// An integer. Int(i64), + /// `@` At, + /// `<` Lt, } impl<'a> Lexer<'a> { + /// Create a new lexer for the given source contents and filename. pub fn from_str(s: &'a str, filename: &'a str) -> Lexer<'a> { let mut l = Lexer { filenames: vec![filename.to_string()], @@ -57,6 +81,7 @@ impl<'a> Lexer<'a> { l } + /// Create a new lexer from the given files. pub fn from_files(filenames: Vec) -> Result, Error> { assert!(!filenames.is_empty()); let file_contents: Vec = filenames @@ -94,10 +119,12 @@ impl<'a> Lexer<'a> { Ok(l) } + /// Get the lexer's current file offset. pub fn offset(&self) -> usize { self.pos.offset } + /// Get the lexer's current source position. pub fn pos(&self) -> Pos { self.pos } @@ -218,10 +245,12 @@ impl<'a> Lexer<'a> { } } + /// Peek ahead at the next token. pub fn peek(&self) -> Option<&(Pos, Token)> { self.lookahead.as_ref() } + /// Are we at the end of the source input? pub fn eof(&self) -> bool { self.lookahead.is_none() } @@ -238,6 +267,7 @@ impl<'a> std::iter::Iterator for Lexer<'a> { } impl Token { + /// Is this an `Int` token? pub fn is_int(&self) -> bool { match self { Token::Int(_) => true, @@ -245,6 +275,7 @@ impl Token { } } + /// Is this a `Sym` token? pub fn is_sym(&self) -> bool { match self { Token::Symbol(_) => true, diff --git a/cranelift/isle/isle/src/lib.rs b/cranelift/isle/isle/src/lib.rs index 5d9dcb088a..b32fa349ff 100644 --- a/cranelift/isle/isle/src/lib.rs +++ b/cranelift/isle/isle/src/lib.rs @@ -1,3 +1,23 @@ +#![doc = include_str!("../README.md")] +#![deny(missing_docs)] + +macro_rules! declare_id { + ( + $(#[$attr:meta])* + $name:ident + ) => { + $(#[$attr])* + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct $name(pub usize); + impl $name { + /// Get the index of this id. + pub fn index(self) -> usize { + self.0 + } + } + }; +} + pub mod ast; pub mod codegen; pub mod compile; @@ -6,4 +26,3 @@ pub mod ir; pub mod lexer; pub mod parser; pub mod sema; - diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 81cf81c2cf..ae0a3b0059 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -4,19 +4,24 @@ use crate::ast::*; use crate::error::*; use crate::lexer::{Lexer, Pos, Token}; +/// The ISLE parser. +/// +/// Takes in a lexer and creates an AST. #[derive(Clone, Debug)] pub struct Parser<'a> { lexer: Lexer<'a>, } +/// Either `Ok(T)` or an `Err(isle::Error)`. pub type ParseResult = std::result::Result; impl<'a> Parser<'a> { + /// Construct a new parser from the given lexer. pub fn new(lexer: Lexer<'a>) -> Parser<'a> { Parser { lexer } } - pub fn error(&self, pos: Pos, msg: String) -> Error { + fn error(&self, pos: Pos, msg: String) -> Error { Error::CompileError { filename: self.lexer.filenames[pos.file].clone(), pos, @@ -106,6 +111,7 @@ impl<'a> Parser<'a> { } } + /// Parse the top-level ISLE definitions and return their AST. pub fn parse_defs(&mut self) -> ParseResult { let mut defs = vec![]; while !self.lexer.eof() { diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 71491f84c6..148a8c0c07 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -1,121 +1,226 @@ //! Semantic analysis. +//! +//! This module primarily contains the type environment and term environment. +//! +//! The type environment is constructed by analyzing an input AST. The type +//! environment records the types used in the input source and the types of our +//! various rules and symbols. ISLE's type system is intentionally easy to +//! check, only requires a single pass over the AST, and doesn't require any +//! unification or anything like that. +//! +//! The term environment is constructed from both the AST and type +//! envionment. It is sort of a typed and reorganized AST that more directly +//! reflects ISLE semantics than the input ISLE source code (where as the AST is +//! the opposite). use crate::ast; use crate::error::*; use crate::lexer::Pos; use std::collections::HashMap; +/// Either `Ok(T)` or a one or more `Error`s. +/// +/// This allows us to return multiple type errors at the same time, for example. pub type SemaResult = std::result::Result>; -#[macro_export] -macro_rules! declare_id { - ($name:ident) => { - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct $name(pub usize); - impl $name { - pub fn index(self) -> usize { - self.0 - } - } - }; -} - -declare_id!(Sym); -declare_id!(TypeId); -declare_id!(VariantId); -declare_id!(FieldId); -declare_id!(TermId); -declare_id!(RuleId); -declare_id!(VarId); +declare_id!( + /// The id of an interned symbol. + Sym +); +declare_id!( + /// The id of an interned type inside the `TypeEnv`. + TypeId +); +declare_id!( + /// The id of a variant inside an enum. + VariantId +); +declare_id!( + /// The id of a field inside a variant. + FieldId +); +declare_id!( + /// The id of an interned term inside the `TermEnv`. + TermId +); +declare_id!( + /// The id of an interned rule inside the `TermEnv`. + RuleId +); +declare_id!( + /// The id of a bound variable inside a `Bindings`. + VarId +); +/// The type environment. +/// +/// Keeps track of which symbols and rules have which types. #[derive(Clone, Debug)] pub struct TypeEnv { + /// Arena of input ISLE source filenames. + /// + /// We refer to these indirectly through the `Pos::file` indices. pub filenames: Vec, + + /// Arena of interned symbol names. + /// + /// Referred to indirectly via `Sym` indices. pub syms: Vec, + + /// Map of already-interned symbol names to their `Sym` ids. pub sym_map: HashMap, + + /// Arena of type definitions. + /// + /// Referred to indirectly via `TypeId`s. pub types: Vec, + + /// A map from a type name symbol to its `TypeId`. pub type_map: HashMap, + + /// The types of constant symbols. pub const_types: HashMap, + + /// Type errors that we've found so far during type checking. pub errors: Vec, } +/// A type. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Type { + /// A primitive, `Copy` type. + /// + /// These are always defined externally, and we allow literals of these + /// types to pass through from ISLE source code to the emitted Rust code. Primitive(TypeId, Sym), + + /// A sum type. + /// + /// Note that enums with only one variant are equivalent to a "struct". Enum { + /// The name of this enum. name: Sym, + /// This `enum`'s type id. id: TypeId, + /// Is this `enum` defined in external Rust code? + /// + /// If so, ISLE will not emit a definition for it. If not, then it will + /// emit a Rust definition for it. is_extern: bool, + /// The different variants for this enum. variants: Vec, + /// The ISLE source position where this `enum` is defined. pos: Pos, }, } impl Type { + /// Get the name of this `Type`. pub fn name<'a>(&self, tyenv: &'a TypeEnv) -> &'a str { match self { Self::Primitive(_, name) | Self::Enum { name, .. } => &tyenv.syms[name.index()], } } + /// Is this a primitive type? pub fn is_prim(&self) -> bool { - match self { - &Type::Primitive(..) => true, - _ => false, - } + matches!(self, Type::Primitive(..)) } } +/// A variant of an enum. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Variant { + /// The name of this variant. pub name: Sym, + + /// The full, prefixed-with-the-enum's-name name of this variant. + /// + /// E.g. if the enum is `Foo` and this variant is `Bar`, then the + /// `fullname` is `Foo.Bar`. pub fullname: Sym, + + /// The id of this variant, i.e. the index of this variant within its + /// enum's `Type::Enum::variants`. pub id: VariantId, + + /// The data fields of this enum variant. pub fields: Vec, } +/// A field of a `Variant`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Field { + /// The name of this field. pub name: Sym, + /// This field's id. pub id: FieldId, + /// The type of this field. pub ty: TypeId, } +/// The term environment. +/// +/// This is sort of a typed and reorganized AST that more directly reflects ISLE +/// semantics than the input ISLE source code (where as the AST is the +/// opposite). #[derive(Clone, Debug)] pub struct TermEnv { + /// Arena of interned terms defined in this ISLE program. + /// + /// This is indexed by `TermId`. pub terms: Vec, + + /// A map from am interned `Term`'s name to its `TermId`. pub term_map: HashMap, + + /// Arena of interned rules defined in this ISLE program. + /// + /// This is indexed by `RuleId`. pub rules: Vec, } +/// A term. +/// +/// Maps parameter types to result types if this is a constructor term, or +/// result types to parameter types if this is an extractor term. Or both if +/// this term can be either a constructor or an extractor. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Term { + /// This term's id. pub id: TermId, + /// The name of this term. pub name: Sym, + /// The parameter types to this term. pub arg_tys: Vec, + /// The result types of this term. pub ret_ty: TypeId, + /// The kind of this term. pub kind: TermKind, } +/// The kind of a term. #[derive(Clone, Debug, PartialEq, Eq)] pub enum TermKind { + /// An enum variant constructor or extractor. EnumVariant { - /// Which variant of the enum: e.g. for enum type `A` if a - /// term is `(A.A1 ...)` then the variant ID corresponds to - /// `A1`. + /// Which variant of the enum: e.g. for enum type `A` if a term is + /// `(A.A1 ...)` then the variant ID corresponds to `A1`. variant: VariantId, }, - /// A term with "internal" rules that work in the forward - /// direction. Becomes a compiled Rust function in the generated - /// code. + /// A term with "internal" rules that work in the forward direction. Becomes + /// a compiled Rust function in the generated code. InternalConstructor, - /// A term that defines an "extractor macro" in the LHS of a - /// pattern. Its arguments take patterns and are simply - /// substituted with the given patterns when used. - InternalExtractor { template: ast::Pattern }, + /// A term that defines an "extractor macro" in the LHS of a pattern. Its + /// arguments take patterns and are simply substituted with the given + /// patterns when used. + InternalExtractor { + /// This extractor's pattern. + template: ast::Pattern, + }, /// A term defined solely by an external extractor function. ExternalExtractor { - /// Extractor func. + /// The external name of the extractor function. name: Sym, /// Which arguments of the extractor are inputs and which are outputs? arg_polarity: Vec, @@ -124,7 +229,7 @@ pub enum TermKind { }, /// A term defined solely by an external constructor function. ExternalConstructor { - /// Constructor func. + /// The external name of the constructor function. name: Sym, }, /// Declared but no body or externs associated (yet). @@ -133,27 +238,28 @@ pub enum TermKind { pub use crate::ast::ArgPolarity; +/// An external function signature. #[derive(Clone, Debug)] pub struct ExternalSig { + /// The name of the external function. pub func_name: String, + /// The name of the external function, prefixed with the context trait. pub full_name: String, - pub arg_tys: Vec, + /// The types of this function signature's parameters. + pub param_tys: Vec, + /// The types of this function signature's results. pub ret_tys: Vec, + /// Whether this signature is infallible or not. pub infallible: bool, } impl Term { + /// Get this term's type. pub fn ty(&self) -> TypeId { self.ret_ty } - pub fn to_variant(&self) -> Option { - match &self.kind { - &TermKind::EnumVariant { variant } => Some(variant), - _ => None, - } - } - + /// Is this term a constructor? pub fn is_constructor(&self) -> bool { match &self.kind { &TermKind::InternalConstructor { .. } | &TermKind::ExternalConstructor { .. } => true, @@ -161,13 +267,7 @@ impl Term { } } - pub fn is_extractor(&self) -> bool { - match &self.kind { - &TermKind::InternalExtractor { .. } | &TermKind::ExternalExtractor { .. } => true, - _ => false, - } - } - + /// Is this term external? pub fn is_external(&self) -> bool { match &self.kind { &TermKind::ExternalExtractor { .. } | &TermKind::ExternalConstructor { .. } => true, @@ -175,12 +275,13 @@ impl Term { } } + /// Get this term's external function signature, if any. pub fn to_sig(&self, tyenv: &TypeEnv) -> Option { match &self.kind { &TermKind::ExternalConstructor { name } => Some(ExternalSig { func_name: tyenv.syms[name.index()].clone(), full_name: format!("C::{}", tyenv.syms[name.index()]), - arg_tys: self.arg_tys.clone(), + param_tys: self.arg_tys.clone(), ret_tys: vec![self.ret_ty], infallible: true, }), @@ -205,7 +306,7 @@ impl Term { Some(ExternalSig { func_name: tyenv.syms[name.index()].clone(), full_name: format!("C::{}", tyenv.syms[name.index()]), - arg_tys, + param_tys: arg_tys, ret_tys, infallible, }) @@ -215,7 +316,7 @@ impl Term { Some(ExternalSig { func_name: name.clone(), full_name: name, - arg_tys: self.arg_tys.clone(), + param_tys: self.arg_tys.clone(), ret_tys: vec![self.ret_ty], infallible: false, }) @@ -225,42 +326,87 @@ impl Term { } } +/// A term rewrite rule. #[derive(Clone, Debug)] pub struct Rule { + /// This rule's id. pub id: RuleId, + /// The left-hand side pattern that this rule matches. pub lhs: Pattern, + /// The right-hand side expression that this rule evaluates upon successful + /// match. pub rhs: Expr, + /// The priority of this rule, if any. pub prio: Option, + /// The source position where this rule is defined. pub pos: Pos, } +/// A left-hand side pattern of some rule. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Pattern { + /// Bind a variable of the given type from the current value. + /// + /// Keep matching on the value with the subpattern. BindPattern(TypeId, VarId, Box), + + /// Match the current value against an already bound variable with the given + /// type. Var(TypeId, VarId), + + /// Match the current value against a constant integer of the given integer + /// type. ConstInt(TypeId, i64), + + /// Match the current value against a constant primitive value of the given + /// primitive type. ConstPrim(TypeId, Sym), + + /// Match the current value against the given extractor term with the given + /// arguments. Term(TypeId, TermId, Vec), + + /// Match anything of the given type successfully. Wildcard(TypeId), + + /// Match all of the following patterns of the given type. And(TypeId, Vec), } +/// Arguments to a term inside a pattern (i.e. an extractor). #[derive(Clone, Debug, PartialEq, Eq)] pub enum TermArgPattern { + /// A pattern to match sub-values (i.e. the extractor's results) against. Pattern(Pattern), + /// An expression to generate a value that is passed into the extractor. Expr(Expr), } +/// A right-hand side expression of some rule. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Expr { + /// Invoke this term constructor with the given arguments. Term(TypeId, TermId, Vec), + /// Get the value of a variable that was bound in the left-hand side. Var(TypeId, VarId), + /// Get a constant integer. ConstInt(TypeId, i64), + /// Get a constant primitive. ConstPrim(TypeId, Sym), - Let(TypeId, Vec<(VarId, TypeId, Box)>, Box), + /// Evaluate the nested expressions and bind their results to the given + /// variables, then evaluate the body expression. + Let { + /// The type of the result of this let expression. + ty: TypeId, + /// The expressions that are evaluated and bound to the given variables. + bindings: Vec<(VarId, TypeId, Box)>, + /// The body expression that is evaluated after the bindings. + body: Box, + }, } impl Pattern { + /// Get this pattern's type. pub fn ty(&self) -> TypeId { match self { &Self::BindPattern(t, ..) => t, @@ -273,6 +419,7 @@ impl Pattern { } } + /// Get the root term of this pattern, if any. pub fn root_term(&self) -> Option { match self { &Pattern::Term(_, term, _) => Some(term), @@ -283,18 +430,20 @@ impl Pattern { } impl Expr { + /// Get this expression's type. pub fn ty(&self) -> TypeId { match self { &Self::Term(t, ..) => t, &Self::Var(t, ..) => t, &Self::ConstInt(t, ..) => t, &Self::ConstPrim(t, ..) => t, - &Self::Let(t, ..) => t, + &Self::Let { ty: t, .. } => t, } } } impl TypeEnv { + /// Construct the type environment from the AST. pub fn from_ast(defs: &ast::Defs) -> SemaResult { let mut tyenv = TypeEnv { filenames: defs.filenames.clone(), @@ -467,7 +616,7 @@ impl TypeEnv { self.errors.push(err); } - pub fn intern_mut(&mut self, ident: &ast::Ident) -> Sym { + fn intern_mut(&mut self, ident: &ast::Ident) -> Sym { if let Some(s) = self.sym_map.get(&ident.0).cloned() { s } else { @@ -478,7 +627,7 @@ impl TypeEnv { } } - pub fn intern(&self, ident: &ast::Ident) -> Option { + fn intern(&self, ident: &ast::Ident) -> Option { self.sym_map.get(&ident.0).cloned() } } @@ -497,6 +646,7 @@ struct BoundVar { } impl TermEnv { + /// Construct the term environment from the AST and the type environment. pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult { let mut env = TermEnv { terms: vec![], @@ -1274,7 +1424,11 @@ impl TermEnv { // Pop the bindings. bindings.vars.truncate(orig_binding_len); - Some(Expr::Let(body_ty, let_defs, body)) + Some(Expr::Let { + ty: body_ty, + bindings: let_defs, + body, + }) } } } From 38da2cee3e173273d575c0db4e2048382b89ef33 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 13:09:24 -0700 Subject: [PATCH 47/95] Switch to using `thiserror` --- cranelift/isle/Cargo.lock | 56 ++++++++++++++++++++++++++++++++ cranelift/isle/isle/Cargo.toml | 1 + cranelift/isle/isle/src/error.rs | 49 +++++++--------------------- 3 files changed, 68 insertions(+), 38 deletions(-) diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index 63a6b3a949..b55178b6d5 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -72,6 +72,7 @@ name = "isle" version = "0.1.0" dependencies = [ "log", + "thiserror", ] [[package]] @@ -99,12 +100,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "syn" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -114,12 +144,38 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 3fe78c9eb7..8600e84129 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -7,3 +7,4 @@ license = "Apache-2.0 WITH LLVM-exception" [dependencies] log = "0.4" +thiserror = "1.0.29" diff --git a/cranelift/isle/isle/src/error.rs b/cranelift/isle/isle/src/error.rs index b123511ddd..6b2d30ecaf 100644 --- a/cranelift/isle/isle/src/error.rs +++ b/cranelift/isle/isle/src/error.rs @@ -1,12 +1,18 @@ //! Error types. +use std::sync::Arc; + use crate::lexer::Pos; -use std::fmt; /// Errors produced by ISLE. -#[derive(Clone, Debug)] +#[derive(thiserror::Error, Clone, Debug)] pub enum Error { + /// An I/O error. + #[error(transparent)] + IoError(Arc), + /// The input ISLE source has an error. + #[error("{}:{}:{}: {}", .filename, .pos.line, .pos.col, .msg)] CompileError { /// The error message. msg: String, @@ -15,43 +21,10 @@ pub enum Error { /// The position within the file that the error occurs at. pos: Pos, }, - /// An error from elsewhere in the system. - SystemError { - /// The error message. - msg: String, - }, } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &Error::CompileError { - ref msg, - ref filename, - pos, - } => { - write!(f, "{}:{}:{}: error: {}", filename, pos.line, pos.col, msg) - } - &Error::SystemError { ref msg } => { - write!(f, "{}", msg) - } - } - } -} - -impl std::error::Error for Error {} - -impl std::convert::From for Error { - fn from(e: std::fmt::Error) -> Error { - Error::SystemError { - msg: format!("{}", e), - } - } -} -impl std::convert::From for Error { - fn from(e: std::io::Error) -> Error { - Error::SystemError { - msg: format!("{}", e), - } +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IoError(Arc::new(e)) } } From 6ffb02d9f65d2938843f9adc18e86fe6eefcd26a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 14:51:02 -0700 Subject: [PATCH 48/95] Use `miette` for reporting errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gives us errors with annotated context like this: ``` Error: × type error: Unknown variable 'x' ╭─[isle_examples/let.isle:24:1] 24 │ (Lower (B.B z)) 25 │ (A.Add x y)) · ┬ · ╰── Unknown variable 'x' ╰──── ``` --- cranelift/isle/Cargo.lock | 221 ++++++++++++++++++++++++++++- cranelift/isle/isle/Cargo.toml | 1 + cranelift/isle/isle/src/ast.rs | 4 +- cranelift/isle/isle/src/compile.rs | 4 +- cranelift/isle/isle/src/error.rs | 126 ++++++++++++++-- cranelift/isle/isle/src/lexer.rs | 36 +++-- cranelift/isle/isle/src/parser.rs | 10 +- cranelift/isle/isle/src/sema.rs | 39 ++--- cranelift/isle/islec/Cargo.toml | 1 + cranelift/isle/islec/src/main.rs | 41 +++--- 10 files changed, 415 insertions(+), 68 deletions(-) diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index b55178b6d5..f4bf43116d 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -22,12 +46,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cc" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" + [[package]] name = "cfg-if" version = "1.0.0" @@ -44,7 +95,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -58,6 +109,12 @@ dependencies = [ "log", ] +[[package]] +name = "gimli" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -67,11 +124,18 @@ dependencies = [ "libc", ] +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + [[package]] name = "isle" version = "0.1.0" dependencies = [ "log", + "miette", "thiserror", ] @@ -83,6 +147,7 @@ dependencies = [ "env_logger", "isle", "log", + "miette", ] [[package]] @@ -100,6 +165,73 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miette" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "024831248cacc3305f5bb33d9daf9df54d6d95bf462c58f388845a17388c47fe" +dependencies = [ + "atty", + "backtrace", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "term_size", + "textwrap 0.14.2", + "thiserror", +] + +[[package]] +name = "miette-derive" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074acd9c89172a516def5d82b8d90fb724fecba9dcae6fcdd88a75689601349f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "object" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "owo-colors" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a61765925aec40abdb23812a3a1a01fafc6ffb9da22768b2ce665a9e84e527c" + [[package]] name = "proc-macro2" version = "1.0.29" @@ -118,12 +250,69 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "supports-color" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f5b0f9e689dd52e27228469dd68b7416b60d75b7571ae9060a5f4c50048fee" +dependencies = [ + "atty", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" +dependencies = [ + "atty", +] + +[[package]] +name = "supports-unicode" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" +dependencies = [ + "atty", +] + [[package]] name = "syn" version = "1.0.77" @@ -135,6 +324,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -144,6 +343,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.29" @@ -164,6 +374,15 @@ dependencies = [ "syn", ] +[[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + [[package]] name = "unicode-width" version = "0.1.8" diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 8600e84129..dec3145407 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -7,4 +7,5 @@ license = "Apache-2.0 WITH LLVM-exception" [dependencies] log = "0.4" +miette = "3.0.0" thiserror = "1.0.29" diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index 489e0b81d1..f256e9cc1d 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -3,12 +3,14 @@ #![allow(missing_docs)] use crate::lexer::Pos; +use std::sync::Arc; /// The parsed form of an ISLE file. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Defs { pub defs: Vec, - pub filenames: Vec, + pub filenames: Vec>, + pub file_texts: Vec>, } /// One toplevel form in an ISLE file. diff --git a/cranelift/isle/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs index 68304852e7..c33b740674 100644 --- a/cranelift/isle/isle/src/compile.rs +++ b/cranelift/isle/isle/src/compile.rs @@ -1,10 +1,10 @@ //! Compilation process, from AST to Sema to Sequences of Insts. -use crate::error::Error; +use crate::error::Result; use crate::{ast, codegen, sema}; /// Compile the given AST definitions into Rust source code. -pub fn compile(defs: &ast::Defs) -> Result> { +pub fn compile(defs: &ast::Defs) -> Result { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; Ok(codegen::codegen(&typeenv, &termenv)) diff --git a/cranelift/isle/isle/src/error.rs b/cranelift/isle/isle/src/error.rs index 6b2d30ecaf..f9278a3365 100644 --- a/cranelift/isle/isle/src/error.rs +++ b/cranelift/isle/isle/src/error.rs @@ -1,26 +1,56 @@ //! Error types. +use miette::{Diagnostic, SourceCode, SourceSpan}; use std::sync::Arc; -use crate::lexer::Pos; +/// Either `Ok(T)` or `Err(isle::Error)`. +pub type Result = std::result::Result; /// Errors produced by ISLE. -#[derive(thiserror::Error, Clone, Debug)] +#[derive(thiserror::Error, Diagnostic, Clone, Debug)] pub enum Error { /// An I/O error. #[error(transparent)] IoError(Arc), - /// The input ISLE source has an error. - #[error("{}:{}:{}: {}", .filename, .pos.line, .pos.col, .msg)] - CompileError { + /// The input ISLE source has a parse error. + #[error("parse error: {msg}")] + #[diagnostic()] + ParseError { /// The error message. msg: String, - /// The ISLE source filename where the error occurs. - filename: String, - /// The position within the file that the error occurs at. - pos: Pos, + + /// The input ISLE source. + #[source_code] + src: Source, + + /// The location of the parse error. + #[label("{msg}")] + span: SourceSpan, }, + + /// The input ISLE source has a type error. + #[error("type error: {msg}")] + #[diagnostic()] + TypeError { + /// The error message. + msg: String, + + /// The input ISLE source. + #[source_code] + src: Source, + + /// The location of the type error. + #[label("{msg}")] + span: SourceSpan, + }, + + /// Multiple errors. + #[error("Found {} errors:\n\n{}", + self.unwrap_errors().len(), + DisplayErrors(self.unwrap_errors()))] + #[diagnostic()] + Errors(#[related] Vec), } impl From for Error { @@ -28,3 +58,81 @@ impl From for Error { Error::IoError(Arc::new(e)) } } + +impl From> for Error { + fn from(es: Vec) -> Self { + Error::Errors(es) + } +} + +impl Error { + fn unwrap_errors(&self) -> &[Error] { + match self { + Error::Errors(e) => e, + _ => panic!("`isle::Error::unwrap_errors` on non-`isle::Error::Errors`"), + } + } +} + +struct DisplayErrors<'a>(&'a [Error]); +impl std::fmt::Display for DisplayErrors<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for e in self.0 { + writeln!(f, "{}", e)?; + } + Ok(()) + } +} + +/// A source file and its contents. +#[derive(Clone)] +pub struct Source { + name: Arc, + text: Arc, +} + +impl std::fmt::Debug for Source { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Source") + .field("name", &self.name) + .field("source", &""); + Ok(()) + } +} + +impl Source { + pub(crate) fn new(name: Arc, text: Arc) -> Self { + Self { name, text } + } + + /// Get this source's file name. + pub fn name(&self) -> &Arc { + &self.name + } + + /// Get this source's text contents. + pub fn text(&self) -> &Arc { + &self.name + } +} + +impl SourceCode for Source { + fn read_span<'a>( + &'a self, + span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> std::result::Result + 'a>, miette::MietteError> { + let contents = self + .text + .read_span(span, context_lines_before, context_lines_after)?; + Ok(Box::new(miette::MietteSpanContents::new_named( + self.name.to_string(), + contents.data(), + contents.span().clone(), + contents.line(), + contents.column(), + contents.line_count(), + ))) + } +} diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index 9a8fa92bb8..f71bd053ff 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -1,7 +1,8 @@ //! Lexer for the ISLE language. -use crate::error::Error; +use crate::error::Result; use std::borrow::Cow; +use std::sync::Arc; /// The lexer. /// @@ -11,7 +12,13 @@ pub struct Lexer<'a> { /// Arena of filenames from the input source. /// /// Indexed via `Pos::file`. - pub filenames: Vec, + pub filenames: Vec>, + + /// Arena of file source texts. + /// + /// Indexed via `Pos::file`. + pub file_texts: Vec>, + file_starts: Vec, buf: Cow<'a, [u8]>, pos: Pos, @@ -36,11 +43,11 @@ pub struct Pos { impl Pos { /// Print this source position as `file.isle:12:34`. - pub fn pretty_print(&self, filenames: &[String]) -> String { + pub fn pretty_print(&self, filenames: &[Arc]) -> String { format!("{}:{}:{}", filenames[self.file], self.line, self.col) } /// Print this source position as `file.isle line 12`. - pub fn pretty_print_line(&self, filenames: &[String]) -> String { + pub fn pretty_print_line(&self, filenames: &[Arc]) -> String { format!("{} line {}", filenames[self.file], self.line) } } @@ -66,7 +73,8 @@ impl<'a> Lexer<'a> { /// Create a new lexer for the given source contents and filename. pub fn from_str(s: &'a str, filename: &'a str) -> Lexer<'a> { let mut l = Lexer { - filenames: vec![filename.to_string()], + filenames: vec![filename.into()], + file_texts: vec![s.into()], file_starts: vec![0], buf: Cow::Borrowed(s.as_bytes()), pos: Pos { @@ -82,22 +90,27 @@ impl<'a> Lexer<'a> { } /// Create a new lexer from the given files. - pub fn from_files(filenames: Vec) -> Result, Error> { + pub fn from_files(filenames: impl IntoIterator) -> Result> + where + S: AsRef, + { + let filenames: Vec> = filenames.into_iter().map(|f| f.as_ref().into()).collect(); assert!(!filenames.is_empty()); - let file_contents: Vec = filenames + + let file_contents: Vec> = filenames .iter() .map(|f| { use std::io::Read; - let mut f = std::fs::File::open(f)?; + let mut f = std::fs::File::open(&**f)?; let mut s = String::new(); f.read_to_string(&mut s)?; - Ok(s) + Ok(s.into()) }) - .collect::, Error>>()?; + .collect::>()?; let mut file_starts = vec![]; let mut buf = String::new(); - for file in file_contents { + for file in &file_contents { file_starts.push(buf.len()); buf += &file; buf += "\n"; @@ -105,6 +118,7 @@ impl<'a> Lexer<'a> { let mut l = Lexer { filenames, + file_texts: file_contents, buf: Cow::Owned(buf.into_bytes()), file_starts, pos: Pos { diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index ae0a3b0059..4588e1c99b 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -22,10 +22,13 @@ impl<'a> Parser<'a> { } fn error(&self, pos: Pos, msg: String) -> Error { - Error::CompileError { - filename: self.lexer.filenames[pos.file].clone(), - pos, + Error::ParseError { msg, + src: Source::new( + self.lexer.filenames[pos.file].clone(), + self.lexer.file_texts[pos.file].clone(), + ), + span: miette::SourceSpan::from((pos.offset, 1)), } } @@ -120,6 +123,7 @@ impl<'a> Parser<'a> { Ok(Defs { defs, filenames: self.lexer.filenames.clone(), + file_texts: self.lexer.file_texts.clone(), }) } diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 148a8c0c07..2082079e5e 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -17,11 +17,7 @@ use crate::ast; use crate::error::*; use crate::lexer::Pos; use std::collections::HashMap; - -/// Either `Ok(T)` or a one or more `Error`s. -/// -/// This allows us to return multiple type errors at the same time, for example. -pub type SemaResult = std::result::Result>; +use std::sync::Arc; declare_id!( /// The id of an interned symbol. @@ -60,7 +56,12 @@ pub struct TypeEnv { /// Arena of input ISLE source filenames. /// /// We refer to these indirectly through the `Pos::file` indices. - pub filenames: Vec, + pub filenames: Vec>, + + /// Arena of input ISLE source contents. + /// + /// We refer to these indirectly through the `Pos::file` indices. + pub file_texts: Vec>, /// Arena of interned symbol names. /// @@ -444,9 +445,10 @@ impl Expr { impl TypeEnv { /// Construct the type environment from the AST. - pub fn from_ast(defs: &ast::Defs) -> SemaResult { + pub fn from_ast(defs: &ast::Defs) -> Result { let mut tyenv = TypeEnv { filenames: defs.filenames.clone(), + file_texts: defs.file_texts.clone(), syms: vec![], sym_map: HashMap::new(), types: vec![], @@ -523,11 +525,11 @@ impl TypeEnv { Ok(tyenv) } - fn return_errors(&mut self) -> SemaResult<()> { - if self.errors.len() > 0 { - Err(std::mem::take(&mut self.errors)) - } else { - Ok(()) + fn return_errors(&mut self) -> Result<()> { + match self.errors.len() { + 0 => Ok(()), + 1 => Err(self.errors.pop().unwrap()), + _ => Err(Error::Errors(std::mem::take(&mut self.errors))), } } @@ -604,10 +606,13 @@ impl TypeEnv { } fn error(&self, pos: Pos, msg: String) -> Error { - Error::CompileError { - filename: self.filenames[pos.file].clone(), - pos, + Error::TypeError { msg, + src: Source::new( + self.filenames[pos.file].clone(), + self.file_texts[pos.file].clone(), + ), + span: miette::SourceSpan::from((pos.offset, 1)), } } @@ -647,7 +652,7 @@ struct BoundVar { impl TermEnv { /// Construct the term environment from the AST and the type environment. - pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> SemaResult { + pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result { let mut env = TermEnv { terms: vec![], term_map: HashMap::new(), @@ -689,7 +694,7 @@ impl TermEnv { () }) }) - .collect::, ()>>(); + .collect::, _>>(); let arg_tys = match arg_tys { Ok(a) => a, Err(_) => { diff --git a/cranelift/isle/islec/Cargo.toml b/cranelift/isle/islec/Cargo.toml index 4454234640..61230c0e98 100644 --- a/cranelift/isle/islec/Cargo.toml +++ b/cranelift/isle/islec/Cargo.toml @@ -10,3 +10,4 @@ log = "0.4" isle = { version = "*", path = "../isle/" } env_logger = { version = "0.8", default-features = false } clap = "2.33" +miette = { version = "3.0.0", features = ["fancy"] } diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs index be99d7301f..dd41a0c391 100644 --- a/cranelift/isle/islec/src/main.rs +++ b/cranelift/isle/islec/src/main.rs @@ -1,10 +1,20 @@ use clap::{App, Arg}; +use isle::{compile, lexer, parser}; +use miette::{IntoDiagnostic, Result}; -use isle::{error, lexer, parser, compile}; - -fn main() -> Result<(), error::Error> { +fn main() -> Result<()> { let _ = env_logger::try_init(); + let _ = miette::set_hook(Box::new(|_| { + Box::new( + miette::MietteHandlerOpts::new() + // `miette` mistakenly uses braille-optimized output for emacs's + // `M-x shell`. + .force_graphical(true) + .build(), + ) + })); + let matches = App::new("isle") .version(env!("CARGO_PKG_VERSION")) .author("Chris Fallin ") @@ -37,31 +47,14 @@ fn main() -> Result<(), error::Error> { let lexer = lexer::Lexer::from_files(input_files)?; let mut parser = parser::Parser::new(lexer); - let defs = match parser.parse_defs() { - Ok(defs) => defs, - Err(error) => { - eprintln!("{}", error); - eprintln!("Failed to parse input."); - std::process::exit(1); - } - }; - let code = match compile::compile(&defs) { - Ok(code) => code, - Err(errors) => { - for error in errors { - eprintln!("{}", error); - } - eprintln!("Failed to compile."); - std::process::exit(1); - } - }; + let defs = parser.parse_defs()?; + let code = compile::compile(&defs)?; { use std::io::Write; - let mut f = std::fs::File::create(output_file)?; - writeln!(&mut f, "{}", code)?; + let mut f = std::fs::File::create(output_file).into_diagnostic()?; + writeln!(&mut f, "{}", code).into_diagnostic()?; } Ok(()) } - From cfaa35d8c0df86164c1cb7e30be448c0b7f93c7a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 15:14:42 -0700 Subject: [PATCH 49/95] Use `structopt` to derive CLI flags Instead of using `clap` directly --- cranelift/isle/Cargo.lock | 77 ++++++++++++++++++++++++++++++- cranelift/isle/isle/src/error.rs | 20 ++++++-- cranelift/isle/isle/src/lexer.rs | 38 ++++++++-------- cranelift/isle/islec/Cargo.toml | 2 +- cranelift/isle/islec/src/main.rs | 78 +++++++++++++++++--------------- 5 files changed, 153 insertions(+), 62 deletions(-) diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index f4bf43116d..08c529adf6 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -115,6 +115,15 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -143,13 +152,19 @@ dependencies = [ name = "islec" version = "0.1.0" dependencies = [ - "clap", "env_logger", "isle", "log", "miette", + "structopt", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.101" @@ -232,6 +247,30 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a61765925aec40abdb23812a3a1a01fafc6ffb9da22768b2ce665a9e84e527c" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.29" @@ -285,6 +324,30 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "structopt" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "supports-color" version = "1.1.1" @@ -383,6 +446,12 @@ dependencies = [ "regex", ] +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + [[package]] name = "unicode-width" version = "0.1.8" @@ -401,6 +470,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "winapi" version = "0.3.9" diff --git a/cranelift/isle/isle/src/error.rs b/cranelift/isle/isle/src/error.rs index f9278a3365..4aee4c0e2e 100644 --- a/cranelift/isle/isle/src/error.rs +++ b/cranelift/isle/isle/src/error.rs @@ -10,8 +10,14 @@ pub type Result = std::result::Result; #[derive(thiserror::Error, Diagnostic, Clone, Debug)] pub enum Error { /// An I/O error. - #[error(transparent)] - IoError(Arc), + #[error("{context}")] + IoError { + /// The underlying I/O error. + #[source] + error: Arc, + /// The context explaining what caused the I/O error. + context: String, + }, /// The input ISLE source has a parse error. #[error("parse error: {msg}")] @@ -53,9 +59,13 @@ pub enum Error { Errors(#[related] Vec), } -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::IoError(Arc::new(e)) +impl Error { + /// Create a `isle::Error` from the given I/O error and context. + pub fn from_io(error: std::io::Error, context: impl Into) -> Self { + Error::IoError { + error: Arc::new(error), + context: context.into(), + } } } diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index f71bd053ff..561a962fb3 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -1,7 +1,8 @@ //! Lexer for the ISLE language. -use crate::error::Result; +use crate::error::{Error, Result}; use std::borrow::Cow; +use std::path::Path; use std::sync::Arc; /// The lexer. @@ -90,35 +91,36 @@ impl<'a> Lexer<'a> { } /// Create a new lexer from the given files. - pub fn from_files(filenames: impl IntoIterator) -> Result> + pub fn from_files

(file_paths: impl IntoIterator) -> Result> where - S: AsRef, + P: AsRef, { - let filenames: Vec> = filenames.into_iter().map(|f| f.as_ref().into()).collect(); - assert!(!filenames.is_empty()); + let mut filenames = Vec::>::new(); + let mut file_texts = Vec::>::new(); - let file_contents: Vec> = filenames - .iter() - .map(|f| { - use std::io::Read; - let mut f = std::fs::File::open(&**f)?; - let mut s = String::new(); - f.read_to_string(&mut s)?; - Ok(s.into()) - }) - .collect::>()?; + for f in file_paths { + let f = f.as_ref(); + + filenames.push(f.display().to_string().into()); + + let s = std::fs::read_to_string(f) + .map_err(|e| Error::from_io(e, format!("failed to read file: {}", f.display())))?; + file_texts.push(s.into()); + } + + assert!(!filenames.is_empty()); let mut file_starts = vec![]; let mut buf = String::new(); - for file in &file_contents { + for text in &file_texts { file_starts.push(buf.len()); - buf += &file; + buf += &text; buf += "\n"; } let mut l = Lexer { filenames, - file_texts: file_contents, + file_texts, buf: Cow::Owned(buf.into_bytes()), file_starts, pos: Pos { diff --git a/cranelift/isle/islec/Cargo.toml b/cranelift/isle/islec/Cargo.toml index 61230c0e98..cc80b44fc0 100644 --- a/cranelift/isle/islec/Cargo.toml +++ b/cranelift/isle/islec/Cargo.toml @@ -9,5 +9,5 @@ license = "Apache-2.0 WITH LLVM-exception" log = "0.4" isle = { version = "*", path = "../isle/" } env_logger = { version = "0.8", default-features = false } -clap = "2.33" miette = { version = "3.0.0", features = ["fancy"] } +structopt = "0.3.23" diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs index dd41a0c391..031c1769b8 100644 --- a/cranelift/isle/islec/src/main.rs +++ b/cranelift/isle/islec/src/main.rs @@ -1,6 +1,23 @@ -use clap::{App, Arg}; use isle::{compile, lexer, parser}; -use miette::{IntoDiagnostic, Result}; +use miette::{Context, IntoDiagnostic, Result}; +use std::{ + fs, + io::{self, Write}, + path::PathBuf, +}; +use structopt::StructOpt; + +#[derive(StructOpt)] +struct Opts { + /// The output file to write the generated Rust code to. `stdout` is used if + /// this is not given. + #[structopt(short, long, parse(from_os_str))] + output: Option, + + /// The input ISLE DSL source files. + #[structopt(parse(from_os_str))] + inputs: Vec, +} fn main() -> Result<()> { let _ = env_logger::try_init(); @@ -15,46 +32,33 @@ fn main() -> Result<()> { ) })); - let matches = App::new("isle") - .version(env!("CARGO_PKG_VERSION")) - .author("Chris Fallin ") - .about("Instruction selection logic engine (ISLE) code generator") - .arg( - Arg::with_name("input") - .short("i") - .long("input") - .value_name("FILE.isle") - .takes_value(true) - .multiple(true) - .required(true), - ) - .arg( - Arg::with_name("output") - .short("o") - .long("output") - .value_name("FILE.rs") - .takes_value(true) - .required(true), - ) - .get_matches(); + let opts = Opts::from_args(); - let input_files = matches - .values_of("input") - .unwrap() - .map(|s| s.to_string()) - .collect::>(); - let output_file = matches.value_of("output").unwrap(); - - let lexer = lexer::Lexer::from_files(input_files)?; + let lexer = lexer::Lexer::from_files(opts.inputs)?; let mut parser = parser::Parser::new(lexer); let defs = parser.parse_defs()?; let code = compile::compile(&defs)?; - { - use std::io::Write; - let mut f = std::fs::File::create(output_file).into_diagnostic()?; - writeln!(&mut f, "{}", code).into_diagnostic()?; - } + let stdout = io::stdout(); + let (mut output, output_name): (Box, _) = match &opts.output { + Some(f) => { + let output = Box::new( + fs::File::create(f) + .into_diagnostic() + .with_context(|| format!("failed to create '{}'", f.display()))?, + ); + (output, f.display().to_string()) + } + None => { + let output = Box::new(stdout.lock()); + (output, "".to_string()) + } + }; + + output + .write_all(code.as_bytes()) + .into_diagnostic() + .with_context(|| format!("failed to write to '{}'", output_name))?; Ok(()) } From 825258939bee841a3715edf6e2a1fb8a95a4d1cc Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 15:54:01 -0700 Subject: [PATCH 50/95] Define a fuzz target for the parser --- cranelift/isle/Cargo.lock | 25 +++++++++++++++++++++++ cranelift/isle/Cargo.toml | 6 +++++- cranelift/isle/fuzz/.gitignore | 3 +++ cranelift/isle/fuzz/Cargo.toml | 19 +++++++++++++++++ cranelift/isle/fuzz/fuzz_targets/parse.rs | 9 ++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 cranelift/isle/fuzz/.gitignore create mode 100644 cranelift/isle/fuzz/Cargo.toml create mode 100644 cranelift/isle/fuzz/fuzz_targets/parse.rs diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index 08c529adf6..6a0f0849c0 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -35,6 +35,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "arbitrary" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577b08a4acd7b99869f863c50011b01eb73424ccc798ecd996f2e24817adfca7" + [[package]] name = "atty" version = "0.2.14" @@ -148,6 +154,14 @@ dependencies = [ "thiserror", ] +[[package]] +name = "isle-fuzz" +version = "0.0.0" +dependencies = [ + "isle", + "libfuzzer-sys", +] + [[package]] name = "islec" version = "0.1.0" @@ -171,6 +185,17 @@ version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +[[package]] +name = "libfuzzer-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "log" version = "0.4.14" diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml index 2f3fcbb680..d1cf4b88ec 100644 --- a/cranelift/isle/Cargo.toml +++ b/cranelift/isle/Cargo.toml @@ -1,2 +1,6 @@ [workspace] -members = [ "isle", "islec" ] +members = [ + "./fuzz", + "./isle", + "./islec", +] diff --git a/cranelift/isle/fuzz/.gitignore b/cranelift/isle/fuzz/.gitignore new file mode 100644 index 0000000000..a0925114d6 --- /dev/null +++ b/cranelift/isle/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/cranelift/isle/fuzz/Cargo.toml b/cranelift/isle/fuzz/Cargo.toml new file mode 100644 index 0000000000..dbfb2ddb9a --- /dev/null +++ b/cranelift/isle/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "isle-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +isle = { path = "../isle" } +libfuzzer-sys = "0.4" + +[[bin]] +name = "parse" +path = "fuzz_targets/parse.rs" +test = false +doc = false diff --git a/cranelift/isle/fuzz/fuzz_targets/parse.rs b/cranelift/isle/fuzz/fuzz_targets/parse.rs new file mode 100644 index 0000000000..f2a572dbd7 --- /dev/null +++ b/cranelift/isle/fuzz/fuzz_targets/parse.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|s: &str| { + let lexer = isle::lexer::Lexer::from_str(s, "fuzz-input.isle"); + let mut parser = isle::parser::Parser::new(lexer); + let _ = parser.parse_defs(); +}); From 6a523938de2b00f6da63e77ec2fa62f00436b91a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 16:07:42 -0700 Subject: [PATCH 51/95] Fix overflows when tokenizing integer literals --- cranelift/isle/fuzz/fuzz_targets/parse.rs | 7 ++- cranelift/isle/isle/src/lexer.rs | 68 ++++++++++++++--------- cranelift/isle/isle/src/parser.rs | 2 +- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/cranelift/isle/fuzz/fuzz_targets/parse.rs b/cranelift/isle/fuzz/fuzz_targets/parse.rs index f2a572dbd7..747bb21e20 100644 --- a/cranelift/isle/fuzz/fuzz_targets/parse.rs +++ b/cranelift/isle/fuzz/fuzz_targets/parse.rs @@ -3,7 +3,8 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|s: &str| { - let lexer = isle::lexer::Lexer::from_str(s, "fuzz-input.isle"); - let mut parser = isle::parser::Parser::new(lexer); - let _ = parser.parse_defs(); + if let Ok(lexer) = isle::lexer::Lexer::from_str(s, "fuzz-input.isle") { + let mut parser = isle::parser::Parser::new(lexer); + let _ = parser.parse_defs(); + } }); diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index 561a962fb3..c247c0c3d7 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -1,6 +1,6 @@ //! Lexer for the ISLE language. -use crate::error::{Error, Result}; +use crate::error::{Error, Result, Source}; use std::borrow::Cow; use std::path::Path; use std::sync::Arc; @@ -72,7 +72,7 @@ pub enum Token { impl<'a> Lexer<'a> { /// Create a new lexer for the given source contents and filename. - pub fn from_str(s: &'a str, filename: &'a str) -> Lexer<'a> { + pub fn from_str(s: &'a str, filename: &'a str) -> Result> { let mut l = Lexer { filenames: vec![filename.into()], file_texts: vec![s.into()], @@ -86,8 +86,8 @@ impl<'a> Lexer<'a> { }, lookahead: None, }; - l.reload(); - l + l.reload()?; + Ok(l) } /// Create a new lexer from the given files. @@ -131,7 +131,7 @@ impl<'a> Lexer<'a> { }, lookahead: None, }; - l.reload(); + l.reload()?; Ok(l) } @@ -162,7 +162,18 @@ impl<'a> Lexer<'a> { } } - fn next_token(&mut self) -> Option<(Pos, Token)> { + fn error(&self, pos: Pos, msg: impl Into) -> Error { + Error::ParseError { + msg: msg.into(), + src: Source::new( + self.filenames[pos.file].clone(), + self.file_texts[pos.file].clone(), + ), + span: miette::SourceSpan::from((pos.offset, 1)), + } + } + + fn next_token(&mut self) -> Result> { fn is_sym_first_char(c: u8) -> bool { match c { b'-' | b'0'..=b'9' | b'(' | b')' | b';' => false, @@ -194,26 +205,26 @@ impl<'a> Lexer<'a> { } if self.pos.offset == self.buf.len() { - return None; + return Ok(None); } let char_pos = self.pos; match self.buf[self.pos.offset] { b'(' => { self.advance_pos(); - Some((char_pos, Token::LParen)) + Ok(Some((char_pos, Token::LParen))) } b')' => { self.advance_pos(); - Some((char_pos, Token::RParen)) + Ok(Some((char_pos, Token::RParen))) } b'@' => { self.advance_pos(); - Some((char_pos, Token::At)) + Ok(Some((char_pos, Token::At))) } b'<' => { self.advance_pos(); - Some((char_pos, Token::Lt)) + Ok(Some((char_pos, Token::Lt))) } c if is_sym_first_char(c) => { let start = self.pos.offset; @@ -226,7 +237,7 @@ impl<'a> Lexer<'a> { let end = self.pos.offset; let s = std::str::from_utf8(&self.buf[start..end]) .expect("Only ASCII characters, should be UTF-8"); - Some((start_pos, Token::Symbol(s.to_string()))) + Ok(Some((start_pos, Token::Symbol(s.to_string())))) } c if (c >= b'0' && c <= b'9') || c == b'-' => { let start_pos = self.pos; @@ -236,11 +247,16 @@ impl<'a> Lexer<'a> { } else { false }; - let mut num = 0; + let mut num = 0_i64; while self.pos.offset < self.buf.len() && (self.buf[self.pos.offset] >= b'0' && self.buf[self.pos.offset] <= b'9') { - num = (num * 10) + (self.buf[self.pos.offset] - b'0') as i64; + let base = num + .checked_mul(10) + .ok_or_else(|| self.error(start_pos, "integer literal too large"))?; + num = base + .checked_add((self.buf[self.pos.offset] - b'0') as i64) + .ok_or_else(|| self.error(start_pos, "integer literal too large"))?; self.advance_pos(); } @@ -249,16 +265,24 @@ impl<'a> Lexer<'a> { } else { Token::Int(num) }; - Some((start_pos, tok)) + Ok(Some((start_pos, tok))) } c => panic!("Unexpected character '{}' at offset {}", c, self.pos.offset), } } - fn reload(&mut self) { + /// Get the next token from this lexer's token stream, if any. + pub fn next(&mut self) -> Result> { + let tok = self.lookahead.take(); + self.reload()?; + Ok(tok) + } + + fn reload(&mut self) -> Result<()> { if self.lookahead.is_none() && self.pos.offset < self.buf.len() { - self.lookahead = self.next_token(); + self.lookahead = self.next_token()?; } + Ok(()) } /// Peek ahead at the next token. @@ -272,16 +296,6 @@ impl<'a> Lexer<'a> { } } -impl<'a> std::iter::Iterator for Lexer<'a> { - type Item = (Pos, Token); - - fn next(&mut self) -> Option<(Pos, Token)> { - let tok = self.lookahead.take(); - self.reload(); - tok - } -} - impl Token { /// Is this an `Int` token? pub fn is_int(&self) -> bool { diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 4588e1c99b..7ea9b6e489 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -37,7 +37,7 @@ impl<'a> Parser<'a> { if !f(peek) { return Err(self.error(pos, format!("Unexpected token {:?}", peek))); } - Ok(self.lexer.next().unwrap().1) + Ok(self.lexer.next()?.unwrap().1) } else { Err(self.error(self.lexer.pos(), "Unexpected EOF".to_string())) } From f2b6244b9c76414a9229b0e0d08ae86ed8d88c2e Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 16:12:07 -0700 Subject: [PATCH 52/95] Fix some panics on parsing error paths --- cranelift/isle/isle/src/parser.rs | 64 ++++++++++--------------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 7ea9b6e489..0cad0387d3 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -51,8 +51,10 @@ impl<'a> Parser<'a> { } } - fn pos(&self) -> Option { - self.lexer.peek().map(|(pos, _)| *pos) + fn pos(&self) -> Pos { + self.lexer + .peek() + .map_or_else(|| self.lexer.pos(), |(pos, _)| *pos) } fn is_lparen(&self) -> bool { @@ -137,7 +139,7 @@ impl<'a> Parser<'a> { "extractor" => Def::Extractor(self.parse_etor()?), "extern" => Def::Extern(self.parse_extern()?), s => { - return Err(self.error(pos.unwrap(), format!("Unexpected identifier: {}", s))); + return Err(self.error(pos, format!("Unexpected identifier: {}", s))); } }; self.rparen()?; @@ -170,7 +172,7 @@ impl<'a> Parser<'a> { fn parse_ident(&mut self) -> ParseResult { let pos = self.pos(); let s = self.symbol()?; - self.str_to_ident(pos.unwrap(), &s) + self.str_to_ident(pos, &s) } fn parse_const(&mut self) -> ParseResult { @@ -181,7 +183,7 @@ impl<'a> Parser<'a> { Ok(Ident(s.to_string(), ident.1)) } else { Err(self.error( - pos.unwrap(), + pos, "Not a constant identifier; must start with a '$'".to_string(), )) } @@ -200,7 +202,7 @@ impl<'a> Parser<'a> { name, is_extern, ty, - pos: pos.unwrap(), + pos, }) } @@ -211,7 +213,6 @@ impl<'a> Parser<'a> { self.symbol()?; let primitive_ident = self.parse_ident()?; self.rparen()?; - let pos = pos.unwrap(); Ok(TypeValue::Primitive(primitive_ident, pos)) } else if self.is_sym_str("enum") { self.symbol()?; @@ -221,16 +222,15 @@ impl<'a> Parser<'a> { variants.push(variant); } self.rparen()?; - let pos = pos.unwrap(); Ok(TypeValue::Enum(variants, pos)) } else { - Err(self.error(pos.unwrap(), "Unknown type definition".to_string())) + Err(self.error(pos, "Unknown type definition".to_string())) } } fn parse_type_variant(&mut self) -> ParseResult { if self.is_sym() { - let pos = self.pos().unwrap(); + let pos = self.pos(); let name = self.parse_ident()?; Ok(Variant { name, @@ -246,7 +246,6 @@ impl<'a> Parser<'a> { fields.push(self.parse_type_field()?); } self.rparen()?; - let pos = pos.unwrap(); Ok(Variant { name, fields, pos }) } } @@ -257,7 +256,6 @@ impl<'a> Parser<'a> { let name = self.parse_ident()?; let ty = self.parse_ident()?; self.rparen()?; - let pos = pos.unwrap(); Ok(Field { name, ty, pos }) } @@ -278,7 +276,7 @@ impl<'a> Parser<'a> { term, arg_tys, ret_ty, - pos: pos.unwrap(), + pos, }) } @@ -288,11 +286,7 @@ impl<'a> Parser<'a> { self.symbol()?; let term = self.parse_ident()?; let func = self.parse_ident()?; - Ok(Extern::Constructor { - term, - func, - pos: pos.unwrap(), - }) + Ok(Extern::Constructor { term, func, pos }) } else if self.is_sym_str("extractor") { self.symbol()?; @@ -317,9 +311,7 @@ impl<'a> Parser<'a> { self.symbol()?; pol.push(ArgPolarity::Output); } else { - return Err( - self.error(pos.unwrap(), "Invalid argument polarity".to_string()) - ); + return Err(self.error(pos, "Invalid argument polarity".to_string())); } } self.rparen()?; @@ -330,7 +322,7 @@ impl<'a> Parser<'a> { Ok(Extern::Extractor { term, func, - pos: pos.unwrap(), + pos, arg_polarity, infallible, }) @@ -339,14 +331,10 @@ impl<'a> Parser<'a> { let pos = self.pos(); let name = self.parse_const()?; let ty = self.parse_ident()?; - Ok(Extern::Const { - name, - ty, - pos: pos.unwrap(), - }) + Ok(Extern::Const { name, ty, pos }) } else { Err(self.error( - pos.unwrap(), + pos, "Invalid extern: must be (extern constructor ...) or (extern extractor ...)" .to_string(), )) @@ -367,7 +355,7 @@ impl<'a> Parser<'a> { term, args, template, - pos: pos.unwrap(), + pos, }) } @@ -383,7 +371,7 @@ impl<'a> Parser<'a> { Ok(Rule { pattern, expr, - pos: pos.unwrap(), + pos, prio, }) } @@ -391,21 +379,17 @@ impl<'a> Parser<'a> { fn parse_pattern(&mut self) -> ParseResult { let pos = self.pos(); if self.is_int() { - let pos = pos.unwrap(); Ok(Pattern::ConstInt { val: self.int()?, pos, }) } else if self.is_const() { - let pos = pos.unwrap(); let val = self.parse_const()?; Ok(Pattern::ConstPrim { val, pos }) } else if self.is_sym_str("_") { - let pos = pos.unwrap(); self.symbol()?; Ok(Pattern::Wildcard { pos }) } else if self.is_sym() { - let pos = pos.unwrap(); let s = self.symbol()?; if s.starts_with("=") { let s = &s[1..]; @@ -426,7 +410,6 @@ impl<'a> Parser<'a> { } } } else if self.is_lparen() { - let pos = pos.unwrap(); self.lparen()?; if self.is_sym_str("and") { self.symbol()?; @@ -446,7 +429,7 @@ impl<'a> Parser<'a> { Ok(Pattern::Term { sym, args, pos }) } } else { - Err(self.error(pos.unwrap(), "Unexpected pattern".into())) + Err(self.error(pos, "Unexpected pattern".into())) } } @@ -462,7 +445,6 @@ impl<'a> Parser<'a> { fn parse_expr(&mut self) -> ParseResult { let pos = self.pos(); if self.is_lparen() { - let pos = pos.unwrap(); self.lparen()?; if self.is_sym_str("let") { self.symbol()?; @@ -486,34 +468,28 @@ impl<'a> Parser<'a> { Ok(Expr::Term { sym, args, pos }) } } else if self.is_sym_str("#t") { - let pos = pos.unwrap(); self.symbol()?; Ok(Expr::ConstInt { val: 1, pos }) } else if self.is_sym_str("#f") { - let pos = pos.unwrap(); self.symbol()?; Ok(Expr::ConstInt { val: 0, pos }) } else if self.is_const() { - let pos = pos.unwrap(); let val = self.parse_const()?; Ok(Expr::ConstPrim { val, pos }) } else if self.is_sym() { - let pos = pos.unwrap(); let name = self.parse_ident()?; Ok(Expr::Var { name, pos }) } else if self.is_int() { - let pos = pos.unwrap(); let val = self.int()?; Ok(Expr::ConstInt { val, pos }) } else { - Err(self.error(pos.unwrap(), "Invalid expression".into())) + Err(self.error(pos, "Invalid expression".into())) } } fn parse_letdef(&mut self) -> ParseResult { let pos = self.pos(); self.lparen()?; - let pos = pos.unwrap(); let var = self.parse_ident()?; let ty = self.parse_ident()?; let val = Box::new(self.parse_expr()?); From e3aeb850b2357a8525ccb74de36a55c916d9c5dc Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 28 Sep 2021 16:27:59 -0700 Subject: [PATCH 53/95] Fix a panic when parsing var patterns --- cranelift/isle/Cargo.lock | 13 +++++- cranelift/isle/fuzz/Cargo.toml | 2 + cranelift/isle/fuzz/fuzz_targets/parse.rs | 11 ++++- cranelift/isle/isle/src/lexer.rs | 1 + cranelift/isle/isle/src/parser.rs | 56 +++++++++++------------ 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock index 6a0f0849c0..3e8e8cb497 100644 --- a/cranelift/isle/Cargo.lock +++ b/cranelift/isle/Cargo.lock @@ -115,6 +115,15 @@ dependencies = [ "log", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "log", +] + [[package]] name = "gimli" version = "0.25.0" @@ -158,15 +167,17 @@ dependencies = [ name = "isle-fuzz" version = "0.0.0" dependencies = [ + "env_logger 0.9.0", "isle", "libfuzzer-sys", + "log", ] [[package]] name = "islec" version = "0.1.0" dependencies = [ - "env_logger", + "env_logger 0.8.4", "isle", "log", "miette", diff --git a/cranelift/isle/fuzz/Cargo.toml b/cranelift/isle/fuzz/Cargo.toml index dbfb2ddb9a..bec0b681b6 100644 --- a/cranelift/isle/fuzz/Cargo.toml +++ b/cranelift/isle/fuzz/Cargo.toml @@ -9,8 +9,10 @@ edition = "2018" cargo-fuzz = true [dependencies] +env_logger = { version = "0.9.0", default-features = false } isle = { path = "../isle" } libfuzzer-sys = "0.4" +log = "0.4.14" [[bin]] name = "parse" diff --git a/cranelift/isle/fuzz/fuzz_targets/parse.rs b/cranelift/isle/fuzz/fuzz_targets/parse.rs index 747bb21e20..73cee40e87 100644 --- a/cranelift/isle/fuzz/fuzz_targets/parse.rs +++ b/cranelift/isle/fuzz/fuzz_targets/parse.rs @@ -3,8 +3,15 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|s: &str| { - if let Ok(lexer) = isle::lexer::Lexer::from_str(s, "fuzz-input.isle") { + let _ = env_logger::try_init(); + + let lexer = isle::lexer::Lexer::from_str(s, "fuzz-input.isle"); + log::debug!("lexer = {:?}", lexer); + + if let Ok(lexer) = lexer { let mut parser = isle::parser::Parser::new(lexer); - let _ = parser.parse_defs(); + + let defs = parser.parse_defs(); + log::debug!("defs = {:?}", defs); } }); diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index c247c0c3d7..f123b1a9de 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -237,6 +237,7 @@ impl<'a> Lexer<'a> { let end = self.pos.offset; let s = std::str::from_utf8(&self.buf[start..end]) .expect("Only ASCII characters, should be UTF-8"); + debug_assert!(!s.is_empty()); Ok(Some((start_pos, Token::Symbol(s.to_string())))) } c if (c >= b'0' && c <= b'9') || c == b'-' => { diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 0cad0387d3..1ad29881ec 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -12,9 +12,6 @@ pub struct Parser<'a> { lexer: Lexer<'a>, } -/// Either `Ok(T)` or an `Err(isle::Error)`. -pub type ParseResult = std::result::Result; - impl<'a> Parser<'a> { /// Construct a new parser from the given lexer. pub fn new(lexer: Lexer<'a>) -> Parser<'a> { @@ -32,7 +29,7 @@ impl<'a> Parser<'a> { } } - fn take bool>(&mut self, f: F) -> ParseResult { + fn take bool>(&mut self, f: F) -> Result { if let Some(&(pos, ref peek)) = self.lexer.peek() { if !f(peek) { return Err(self.error(pos, format!("Unexpected token {:?}", peek))); @@ -89,27 +86,27 @@ impl<'a> Parser<'a> { }) } - fn lparen(&mut self) -> ParseResult<()> { + fn lparen(&mut self) -> Result<()> { self.take(|tok| *tok == Token::LParen).map(|_| ()) } - fn rparen(&mut self) -> ParseResult<()> { + fn rparen(&mut self) -> Result<()> { self.take(|tok| *tok == Token::RParen).map(|_| ()) } - fn at(&mut self) -> ParseResult<()> { + fn at(&mut self) -> Result<()> { self.take(|tok| *tok == Token::At).map(|_| ()) } - fn lt(&mut self) -> ParseResult<()> { + fn lt(&mut self) -> Result<()> { self.take(|tok| *tok == Token::Lt).map(|_| ()) } - fn symbol(&mut self) -> ParseResult { + fn symbol(&mut self) -> Result { match self.take(|tok| tok.is_sym())? { Token::Symbol(s) => Ok(s), _ => unreachable!(), } } - fn int(&mut self) -> ParseResult { + fn int(&mut self) -> Result { match self.take(|tok| tok.is_int())? { Token::Int(i) => Ok(i), _ => unreachable!(), @@ -117,7 +114,7 @@ impl<'a> Parser<'a> { } /// Parse the top-level ISLE definitions and return their AST. - pub fn parse_defs(&mut self) -> ParseResult { + pub fn parse_defs(&mut self) -> Result { let mut defs = vec![]; while !self.lexer.eof() { defs.push(self.parse_def()?); @@ -129,7 +126,7 @@ impl<'a> Parser<'a> { }) } - fn parse_def(&mut self) -> ParseResult { + fn parse_def(&mut self) -> Result { self.lparen()?; let pos = self.pos(); let def = match &self.symbol()?[..] { @@ -146,8 +143,11 @@ impl<'a> Parser<'a> { Ok(def) } - fn str_to_ident(&self, pos: Pos, s: &str) -> ParseResult { - let first = s.chars().next().unwrap(); + fn str_to_ident(&self, pos: Pos, s: &str) -> Result { + let first = s + .chars() + .next() + .ok_or_else(|| self.error(pos, "empty symbol".into()))?; if !first.is_alphabetic() && first != '_' && first != '$' { return Err(self.error( pos, @@ -169,13 +169,13 @@ impl<'a> Parser<'a> { Ok(Ident(s.to_string(), pos)) } - fn parse_ident(&mut self) -> ParseResult { + fn parse_ident(&mut self) -> Result { let pos = self.pos(); let s = self.symbol()?; self.str_to_ident(pos, &s) } - fn parse_const(&mut self) -> ParseResult { + fn parse_const(&mut self) -> Result { let pos = self.pos(); let ident = self.parse_ident()?; if ident.0.starts_with("$") { @@ -189,7 +189,7 @@ impl<'a> Parser<'a> { } } - fn parse_type(&mut self) -> ParseResult { + fn parse_type(&mut self) -> Result { let pos = self.pos(); let name = self.parse_ident()?; let mut is_extern = false; @@ -206,7 +206,7 @@ impl<'a> Parser<'a> { }) } - fn parse_typevalue(&mut self) -> ParseResult { + fn parse_typevalue(&mut self) -> Result { let pos = self.pos(); self.lparen()?; if self.is_sym_str("primitive") { @@ -228,7 +228,7 @@ impl<'a> Parser<'a> { } } - fn parse_type_variant(&mut self) -> ParseResult { + fn parse_type_variant(&mut self) -> Result { if self.is_sym() { let pos = self.pos(); let name = self.parse_ident()?; @@ -250,7 +250,7 @@ impl<'a> Parser<'a> { } } - fn parse_type_field(&mut self) -> ParseResult { + fn parse_type_field(&mut self) -> Result { let pos = self.pos(); self.lparen()?; let name = self.parse_ident()?; @@ -259,7 +259,7 @@ impl<'a> Parser<'a> { Ok(Field { name, ty, pos }) } - fn parse_decl(&mut self) -> ParseResult { + fn parse_decl(&mut self) -> Result { let pos = self.pos(); let term = self.parse_ident()?; @@ -280,7 +280,7 @@ impl<'a> Parser<'a> { }) } - fn parse_extern(&mut self) -> ParseResult { + fn parse_extern(&mut self) -> Result { let pos = self.pos(); if self.is_sym_str("constructor") { self.symbol()?; @@ -341,7 +341,7 @@ impl<'a> Parser<'a> { } } - fn parse_etor(&mut self) -> ParseResult { + fn parse_etor(&mut self) -> Result { let pos = self.pos(); self.lparen()?; let term = self.parse_ident()?; @@ -359,7 +359,7 @@ impl<'a> Parser<'a> { }) } - fn parse_rule(&mut self) -> ParseResult { + fn parse_rule(&mut self) -> Result { let pos = self.pos(); let prio = if self.is_int() { Some(self.int()?) @@ -376,7 +376,7 @@ impl<'a> Parser<'a> { }) } - fn parse_pattern(&mut self) -> ParseResult { + fn parse_pattern(&mut self) -> Result { let pos = self.pos(); if self.is_int() { Ok(Pattern::ConstInt { @@ -433,7 +433,7 @@ impl<'a> Parser<'a> { } } - fn parse_pattern_term_arg(&mut self) -> ParseResult { + fn parse_pattern_term_arg(&mut self) -> Result { if self.is_lt() { self.lt()?; Ok(TermArgPattern::Expr(self.parse_expr()?)) @@ -442,7 +442,7 @@ impl<'a> Parser<'a> { } } - fn parse_expr(&mut self) -> ParseResult { + fn parse_expr(&mut self) -> Result { let pos = self.pos(); if self.is_lparen() { self.lparen()?; @@ -487,7 +487,7 @@ impl<'a> Parser<'a> { } } - fn parse_letdef(&mut self) -> ParseResult { + fn parse_letdef(&mut self) -> Result { let pos = self.pos(); self.lparen()?; let var = self.parse_ident()?; From 6604a26e27ed714348cc6ed10f66ec5e7e2d2725 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 29 Sep 2021 13:18:30 -0700 Subject: [PATCH 54/95] Add a top-level parse function And make `parse_defs` take `self` by ownership. This avoids a couple `Vec` clones. --- cranelift/isle/fuzz/fuzz_targets/parse.rs | 4 +--- cranelift/isle/isle/src/parser.rs | 15 ++++++++++----- cranelift/isle/islec/src/main.rs | 3 +-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cranelift/isle/fuzz/fuzz_targets/parse.rs b/cranelift/isle/fuzz/fuzz_targets/parse.rs index 73cee40e87..67390f865c 100644 --- a/cranelift/isle/fuzz/fuzz_targets/parse.rs +++ b/cranelift/isle/fuzz/fuzz_targets/parse.rs @@ -9,9 +9,7 @@ fuzz_target!(|s: &str| { log::debug!("lexer = {:?}", lexer); if let Ok(lexer) = lexer { - let mut parser = isle::parser::Parser::new(lexer); - - let defs = parser.parse_defs(); + let defs = isle::parser::parse(lexer); log::debug!("defs = {:?}", defs); } }); diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 1ad29881ec..8d8bbad5d8 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -4,11 +4,17 @@ use crate::ast::*; use crate::error::*; use crate::lexer::{Lexer, Pos, Token}; +/// Parse the top-level ISLE definitions and return their AST. +pub fn parse(lexer: Lexer) -> Result { + let parser = Parser::new(lexer); + parser.parse_defs() +} + /// The ISLE parser. /// /// Takes in a lexer and creates an AST. #[derive(Clone, Debug)] -pub struct Parser<'a> { +struct Parser<'a> { lexer: Lexer<'a>, } @@ -113,16 +119,15 @@ impl<'a> Parser<'a> { } } - /// Parse the top-level ISLE definitions and return their AST. - pub fn parse_defs(&mut self) -> Result { + fn parse_defs(mut self) -> Result { let mut defs = vec![]; while !self.lexer.eof() { defs.push(self.parse_def()?); } Ok(Defs { defs, - filenames: self.lexer.filenames.clone(), - file_texts: self.lexer.file_texts.clone(), + filenames: self.lexer.filenames, + file_texts: self.lexer.file_texts, }) } diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs index 031c1769b8..c4e675d064 100644 --- a/cranelift/isle/islec/src/main.rs +++ b/cranelift/isle/islec/src/main.rs @@ -35,8 +35,7 @@ fn main() -> Result<()> { let opts = Opts::from_args(); let lexer = lexer::Lexer::from_files(opts.inputs)?; - let mut parser = parser::Parser::new(lexer); - let defs = parser.parse_defs()?; + let defs = parser::parse(lexer)?; let code = compile::compile(&defs)?; let stdout = io::stdout(); From 9be1942b113115de61fde2a3938db352b2fb6d8e Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 29 Sep 2021 13:38:00 -0700 Subject: [PATCH 55/95] Use `.copied()` instead of `.cloned()` --- cranelift/isle/fuzz/fuzz_targets/{parse.rs => compile.rs} | 0 cranelift/isle/isle/src/sema.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename cranelift/isle/fuzz/fuzz_targets/{parse.rs => compile.rs} (100%) diff --git a/cranelift/isle/fuzz/fuzz_targets/parse.rs b/cranelift/isle/fuzz/fuzz_targets/compile.rs similarity index 100% rename from cranelift/isle/fuzz/fuzz_targets/parse.rs rename to cranelift/isle/fuzz/fuzz_targets/compile.rs diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 2082079e5e..797441e0f4 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -622,7 +622,7 @@ impl TypeEnv { } fn intern_mut(&mut self, ident: &ast::Ident) -> Sym { - if let Some(s) = self.sym_map.get(&ident.0).cloned() { + if let Some(s) = self.sym_map.get(&ident.0).copied() { s } else { let s = Sym(self.syms.len()); From a099b2b5900c01e2087bcc53f8c21b9ae3f9926f Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 29 Sep 2021 17:18:18 -0700 Subject: [PATCH 56/95] Extend fuzzing to semantic analysis and codegen * Fix a panic when we are substituting macro args, but we already had an error involving the macro. * Fix a stack overflow when an internal extractor's definition is recursive. --- cranelift/isle/fuzz/Cargo.toml | 4 +- cranelift/isle/fuzz/fuzz_targets/compile.rs | 25 ++- cranelift/isle/isle/src/ast.rs | 55 ++++-- cranelift/isle/isle/src/sema.rs | 197 +++++++++++--------- 4 files changed, 177 insertions(+), 104 deletions(-) diff --git a/cranelift/isle/fuzz/Cargo.toml b/cranelift/isle/fuzz/Cargo.toml index bec0b681b6..51a593006c 100644 --- a/cranelift/isle/fuzz/Cargo.toml +++ b/cranelift/isle/fuzz/Cargo.toml @@ -15,7 +15,7 @@ libfuzzer-sys = "0.4" log = "0.4.14" [[bin]] -name = "parse" -path = "fuzz_targets/parse.rs" +name = "compile" +path = "fuzz_targets/compile.rs" test = false doc = false diff --git a/cranelift/isle/fuzz/fuzz_targets/compile.rs b/cranelift/isle/fuzz/fuzz_targets/compile.rs index 67390f865c..8526c29cce 100644 --- a/cranelift/isle/fuzz/fuzz_targets/compile.rs +++ b/cranelift/isle/fuzz/fuzz_targets/compile.rs @@ -7,9 +7,26 @@ fuzz_target!(|s: &str| { let lexer = isle::lexer::Lexer::from_str(s, "fuzz-input.isle"); log::debug!("lexer = {:?}", lexer); + let lexer = match lexer { + Ok(l) => l, + Err(_) => return, + }; - if let Ok(lexer) = lexer { - let defs = isle::parser::parse(lexer); - log::debug!("defs = {:?}", defs); - } + let defs = isle::parser::parse(lexer); + log::debug!("defs = {:?}", defs); + let defs = match defs { + Ok(d) => d, + Err(_) => return, + }; + + let code = isle::compile::compile(&defs); + log::debug!("code = {:?}", code); + let code = match code { + Ok(c) => c, + Err(_) => return, + }; + + // TODO: check that the generated code is valid Rust. This will require + // stubbing out extern types, extractors, and constructors. + drop(code); }); diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index f256e9cc1d..5af7eaf6d7 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -127,6 +127,33 @@ impl Pattern { } } + /// Call `f` for each of the terms in this pattern. + pub fn terms(&self, f: &mut dyn FnMut(&Ident)) { + match self { + Pattern::Term { sym, args, .. } => { + f(sym); + for arg in args { + if let TermArgPattern::Pattern(p) = arg { + p.terms(f); + } + } + } + Pattern::And { subpats, .. } => { + for p in subpats { + p.terms(f); + } + } + Pattern::BindPattern { subpat, .. } => { + subpat.terms(f); + } + Pattern::Var { .. } + | Pattern::ConstInt { .. } + | Pattern::ConstPrim { .. } + | Pattern::Wildcard { .. } + | Pattern::MacroArg { .. } => {} + } + } + pub fn make_macro_template(&self, macro_args: &[Ident]) -> Pattern { log::trace!("make_macro_template: {:?} with {:?}", self, macro_args); match self { @@ -182,24 +209,24 @@ impl Pattern { } } - pub fn subst_macro_args(&self, macro_args: &[Pattern]) -> Pattern { + pub fn subst_macro_args(&self, macro_args: &[Pattern]) -> Option { log::trace!("subst_macro_args: {:?} with {:?}", self, macro_args); match self { &Pattern::BindPattern { ref var, ref subpat, pos, - } => Pattern::BindPattern { + } => Some(Pattern::BindPattern { var: var.clone(), - subpat: Box::new(subpat.subst_macro_args(macro_args)), + subpat: Box::new(subpat.subst_macro_args(macro_args)?), pos, - }, + }), &Pattern::And { ref subpats, pos } => { let subpats = subpats .iter() .map(|subpat| subpat.subst_macro_args(macro_args)) - .collect::>(); - Pattern::And { subpats, pos } + .collect::>>()?; + Some(Pattern::And { subpats, pos }) } &Pattern::Term { ref sym, @@ -209,19 +236,19 @@ impl Pattern { let args = args .iter() .map(|arg| arg.subst_macro_args(macro_args)) - .collect::>(); - Pattern::Term { + .collect::>>()?; + Some(Pattern::Term { sym: sym.clone(), args, pos, - } + }) } &Pattern::Var { .. } | &Pattern::Wildcard { .. } | &Pattern::ConstInt { .. } - | &Pattern::ConstPrim { .. } => self.clone(), - &Pattern::MacroArg { index, .. } => macro_args[index].clone(), + | &Pattern::ConstPrim { .. } => Some(self.clone()), + &Pattern::MacroArg { index, .. } => macro_args.get(index).cloned(), } } @@ -264,12 +291,12 @@ impl TermArgPattern { } } - fn subst_macro_args(&self, args: &[Pattern]) -> TermArgPattern { + fn subst_macro_args(&self, args: &[Pattern]) -> Option { match self { &TermArgPattern::Pattern(ref pat) => { - TermArgPattern::Pattern(pat.subst_macro_args(args)) + Some(TermArgPattern::Pattern(pat.subst_macro_args(args)?)) } - &TermArgPattern::Expr(_) => self.clone(), + &TermArgPattern::Expr(_) => Some(self.clone()), } } } diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 797441e0f4..d1d8213edb 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -17,6 +17,7 @@ use crate::ast; use crate::error::*; use crate::lexer::Pos; use std::collections::HashMap; +use std::collections::HashSet; use std::sync::Arc; declare_id!( @@ -443,6 +444,22 @@ impl Expr { } } +/// Given an `Option`, unwrap the inner `T` value, or `continue` if it is +/// `None`. +/// +/// Useful for when we encountered an error earlier in our analysis but kept +/// going to find more errors, and now we've run into some missing data that +/// would have been filled in if we didn't hit that original error, but we want +/// to keep going to find more errors. +macro_rules! unwrap_or_continue { + ($e:expr) => { + match $e { + Some(x) => x, + None => continue, + } + }; +} + impl TypeEnv { /// Construct the type environment from the AST. pub fn from_ast(defs: &ast::Defs) -> Result { @@ -484,12 +501,7 @@ impl TypeEnv { for def in &defs.defs { match def { &ast::Def::Type(ref td) => { - let ty = match tyenv.type_from_ast(TypeId(tid), td) { - Some(ty) => ty, - None => { - continue; - } - }; + let ty = unwrap_or_continue!(tyenv.type_from_ast(TypeId(tid), td)); tyenv.types.push(ty); tid += 1; } @@ -606,14 +618,16 @@ impl TypeEnv { } fn error(&self, pos: Pos, msg: String) -> Error { - Error::TypeError { + let e = Error::TypeError { msg, src: Source::new( self.filenames[pos.file].clone(), self.file_texts[pos.file].clone(), ), span: miette::SourceSpan::from((pos.offset, 1)), - } + }; + log::trace!("{}", e); + e } fn report_error(&mut self, pos: Pos, msg: String) { @@ -661,6 +675,7 @@ impl TermEnv { env.collect_term_sigs(tyenv, defs); env.collect_enum_variant_terms(tyenv); + tyenv.return_errors()?; env.collect_constructors(tyenv, defs); env.collect_extractor_templates(tyenv, defs); tyenv.return_errors()?; @@ -769,6 +784,7 @@ impl TermEnv { fn collect_constructors(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { for def in &defs.defs { + log::debug!("collect_constructors from def: {:?}", def); match def { &ast::Def::Rule(ref rule) => { let pos = rule.pos; @@ -811,39 +827,68 @@ impl TermEnv { } fn collect_extractor_templates(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { + let mut extractor_call_graph = HashMap::new(); + for def in &defs.defs { - match def { - &ast::Def::Extractor(ref ext) => { - let sym = tyenv.intern_mut(&ext.term); - let term = match self.term_map.get(&sym) { - Some(x) => x, - None => { - tyenv.report_error( - ext.pos, - "Extractor macro body definition on a non-existent term" - .to_string(), - ); - return; - } - }; - let termdata = &mut self.terms[term.index()]; - let template = ext.template.make_macro_template(&ext.args[..]); - log::trace!("extractor def: {:?} becomes template {:?}", def, template); - match &termdata.kind { - &TermKind::Declared => { - termdata.kind = TermKind::InternalExtractor { template }; - } - _ => { - tyenv.report_error( - ext.pos, - "Extractor macro body defined on term of incorrect kind" - .to_string(), - ); - continue; - } + if let &ast::Def::Extractor(ref ext) = def { + let sym = tyenv.intern_mut(&ext.term); + let term = match self.term_map.get(&sym) { + Some(x) => x, + None => { + tyenv.report_error( + ext.pos, + "Extractor macro body definition on a non-existent term".to_string(), + ); + return; + } + }; + let termdata = &mut self.terms[term.index()]; + let template = ext.template.make_macro_template(&ext.args[..]); + log::trace!("extractor def: {:?} becomes template {:?}", def, template); + + let mut callees = HashSet::new(); + template.terms(&mut |t| { + let t = tyenv.intern_mut(t); + callees.insert(t); + }); + extractor_call_graph.insert(sym, callees); + + match &termdata.kind { + &TermKind::Declared => { + termdata.kind = TermKind::InternalExtractor { template }; + } + _ => { + tyenv.report_error( + ext.pos, + "Extractor macro body defined on term of incorrect kind".to_string(), + ); + continue; } } - _ => {} + } + } + + // Check for cycles in the extractor call graph. + let mut seen = HashSet::new(); + let mut stack = vec![]; + 'outer: for root in extractor_call_graph.keys().copied() { + seen.clear(); + stack.clear(); + stack.push(root); + + while let Some(caller) = stack.pop() { + let already_seen = seen.insert(caller); + if already_seen { + let term = self.term_map[&caller]; + let pos = match &self.terms[term.index()].kind { + TermKind::InternalExtractor { template } => template.pos(), + _ => unreachable!(), + }; + tyenv.report_error(pos, "extractor definition is recursive".into()); + continue 'outer; + } else { + stack.extend(extractor_call_graph[&caller].iter().copied()); + } } } } @@ -858,20 +903,18 @@ impl TermEnv { vars: vec![], }; - let (lhs, ty) = - match self.translate_pattern(tyenv, &rule.pattern, None, &mut bindings) { - Some(x) => x, - None => { - // Keep going to collect more errors. - continue; - } - }; - let rhs = match self.translate_expr(tyenv, &rule.expr, ty, &mut bindings) { - Some(x) => x, - None => { - continue; - } - }; + let (lhs, ty) = unwrap_or_continue!(self.translate_pattern( + tyenv, + &rule.pattern, + None, + &mut bindings + )); + let rhs = unwrap_or_continue!(self.translate_expr( + tyenv, + &rule.expr, + ty, + &mut bindings + )); let rid = RuleId(self.rules.len()); self.rules.push(Rule { @@ -1021,14 +1064,12 @@ impl TermEnv { let mut expected_ty = expected_ty; let mut children = vec![]; for subpat in subpats { - let (subpat, ty) = - match self.translate_pattern(tyenv, &*subpat, expected_ty, bindings) { - Some(x) => x, - None => { - // Try to keep going for more errors. - continue; - } - }; + let (subpat, ty) = unwrap_or_continue!(self.translate_pattern( + tyenv, + &*subpat, + expected_ty, + bindings + )); expected_ty = expected_ty.or(Some(ty)); children.push(subpat); } @@ -1188,7 +1229,7 @@ impl TermEnv { macro_args.push(sub_ast.clone()); } log::trace!("internal extractor macro args = {:?}", args); - let pat = template.subst_macro_args(¯o_args[..]); + let pat = template.subst_macro_args(¯o_args[..])?; return self.translate_pattern(tyenv, &pat, expected_ty, bindings); } &TermKind::ExternalConstructor { .. } | &TermKind::InternalConstructor => { @@ -1205,20 +1246,15 @@ impl TermEnv { // Resolve subpatterns. let mut subpats = vec![]; for (i, arg) in args.iter().enumerate() { - let arg_ty = self.terms[tid.index()].arg_tys[i]; - let (subpat, _) = match self.translate_pattern_term_arg( + let term = unwrap_or_continue!(self.terms.get(tid.index())); + let arg_ty = unwrap_or_continue!(term.arg_tys.get(i).copied()); + let (subpat, _) = unwrap_or_continue!(self.translate_pattern_term_arg( tyenv, pos, arg, Some(arg_ty), bindings, - ) { - Some(x) => x, - None => { - // Try to keep going for more errors. - continue; - } - }; + )); subpats.push(subpat); } @@ -1305,13 +1341,10 @@ impl TermEnv { // Resolve subexpressions. let mut subexprs = vec![]; for (i, arg) in args.iter().enumerate() { - let arg_ty = self.terms[tid.index()].arg_tys[i]; - let subexpr = match self.translate_expr(tyenv, arg, arg_ty, bindings) { - Some(s) => s, - None => { - continue; - } - }; + 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)); subexprs.push(subexpr); } @@ -1406,13 +1439,9 @@ impl TermEnv { }; // Evaluate the variable's value. - let val = Box::new(match self.translate_expr(tyenv, &def.val, tid, bindings) { - Some(e) => e, - None => { - // Keep going for more errors. - continue; - } - }); + let val = Box::new(unwrap_or_continue!( + self.translate_expr(tyenv, &def.val, tid, bindings) + )); // Bind the var with the given type. let id = VarId(bindings.next_var); From ce207ee9531007b10ddb9985dc0f83e764b21f32 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 30 Sep 2021 09:49:23 -0700 Subject: [PATCH 57/95] Check that integer literals are a primitive type --- cranelift/isle/isle/src/sema.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index d1d8213edb..9a6641fb61 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -1376,7 +1376,19 @@ impl TermEnv { Some(Expr::Var(bv.ty, bv.id)) } - &ast::Expr::ConstInt { val, .. } => Some(Expr::ConstInt(ty, val)), + &ast::Expr::ConstInt { val, pos } => { + if !tyenv.types[ty.index()].is_prim() { + tyenv.report_error( + pos, + format!( + "expected non-primitive type {}, but found integer literal '{}'", + tyenv.types[ty.index()].name(tyenv), + val, + ), + ); + } + Some(Expr::ConstInt(ty, val)) + } &ast::Expr::ConstPrim { ref val, pos } => { let val = tyenv.intern_mut(val); let const_ty = match tyenv.const_types.get(&val) { From d2bd07c24699b758771688ccf266ccf443822520 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 30 Sep 2021 09:51:09 -0700 Subject: [PATCH 58/95] Check that integer literals are primitive types in patterns --- cranelift/isle/isle/src/sema.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 9a6641fb61..b3d0a6f543 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -1034,6 +1034,16 @@ impl TermEnv { return None; } }; + if !tyenv.types[ty.index()].is_prim() { + tyenv.report_error( + pos, + format!( + "expected non-primitive type {}, but found integer literal '{}'", + tyenv.types[ty.index()].name(tyenv), + val, + ), + ); + } Some((Pattern::ConstInt(ty, val), ty)) } &ast::Pattern::ConstPrim { ref val, pos } => { From 0e082e8d6f623429151641b509e54f4ed185f233 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 30 Sep 2021 12:28:22 -0700 Subject: [PATCH 59/95] Make sure that every `decl` has a definition at the end of type checking --- cranelift/isle/isle/src/sema.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index b3d0a6f543..516560b5d1 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -269,6 +269,10 @@ impl Term { } } + fn is_declared(&self) -> bool { + matches!(self.kind, TermKind::Declared) + } + /// Is this term external? pub fn is_external(&self) -> bool { match &self.kind { @@ -680,6 +684,7 @@ impl TermEnv { env.collect_extractor_templates(tyenv, defs); tyenv.return_errors()?; env.collect_rules(tyenv, defs); + env.check_for_undefined_decls(tyenv, defs); tyenv.return_errors()?; Ok(env) @@ -1012,6 +1017,24 @@ impl TermEnv { } } + fn check_for_undefined_decls(&self, tyenv: &mut TypeEnv, defs: &ast::Defs) { + for def in &defs.defs { + if let ast::Def::Decl(decl) = def { + let sym = tyenv.intern_mut(&decl.term); + let term = self.term_map[&sym]; + if self.terms[term.index()].is_declared() { + tyenv.report_error( + decl.pos, + format!( + "no rules, extractor, or external definition for declaration '{}'", + decl.term.0 + ), + ); + } + } + } + } + fn translate_pattern( &self, tyenv: &mut TypeEnv, From bffaccde1fb686b72578db67847c4091b4be1d30 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 1 Oct 2021 14:12:16 -0700 Subject: [PATCH 60/95] Add a tutorial to the README --- cranelift/isle/README.md | 422 ++++++++++++++++++++- cranelift/isle/isle_examples/tutorial.isle | 96 +++++ 2 files changed, 509 insertions(+), 9 deletions(-) create mode 100644 cranelift/isle/isle_examples/tutorial.isle diff --git a/cranelift/isle/README.md b/cranelift/isle/README.md index a17a547b52..17a9fe79c0 100644 --- a/cranelift/isle/README.md +++ b/cranelift/isle/README.md @@ -1,5 +1,15 @@ # ISLE: Instruction Selection/Lowering Expressions DSL +## Table of Contents + +* [Introduction](#introduction) +* [Example Usage](#example-usage) +* [Tutorial](#tutorial) +* [Implementation](#implementation) +* [Sketch of Instruction Selector](#sketch-of-instruction-selector) + +## Introduction + ISLE is a DSL that allows one to write instruction-lowering rules for a compiler backend. It is based on a "term-rewriting" paradigm in which the input -- some sort of compiler IR -- is, conceptually, a tree of terms, and we have a @@ -19,21 +29,415 @@ level, the rules can be seen as simple equivalences between values in two languages, and so should be translatable to formal constraints or other logical specification languages. -Some more details are in [BA RFC +Some more details and motivation are in [BA RFC #15](https://github.com/bytecodealliance/rfcs/pull/15); additional documentation will eventually be added to carefully specify the language semantics. +## Example Usage + +Build `islec`, the ISLE compiler: + +```shell +$ cargo build --release +``` + +Compile a `.isle` source file into Rust code: + +```shell +$ target/release/islec -i isle_examples/test.isle -o isle_examples/test.rs +``` + +Include that Rust code in your crate and compile it: + +```shell +$ rustc isle_examples/test_main.rs +``` + +## Tutorial + +This tutorial walks through defining an instruction selection and lowering pass +for a simple, RISC-y, high-level IR down to low-level, CISC-y machine +instructions. It is intentionally somewhat similar to CLIF to MachInst lowering, +although it restricts the input and output languages to only adds, loads, and +constants so that we can focus on ISLE itself. + +> The full ISLE source code for this tutorial is available at +> `isle_examples/tutorial.isle`. + +The ISLE language is based around rules for translating a term (i.e. expression) +into another term. Terms are typed, so before we can write rules for translating +some type of term into another type of term, we have to define those types: + +```lisp +;; Declare that we are using the `i32` primitive type from Rust. +(type i32 (primitive i32)) + +;; Our high-level, RISC-y input IR. +(type HighLevelInst + (enum (Add (a Value) (b Value)) + (Load (addr Value)) + (Const (c i32)))) + +;; A value in our high-level IR is a Rust `Copy` type. Values are either defined +;; by an instruction, or are a basic block argument. +(type Value (primitive Value)) + +;; Our low-level, CISC-y machine instructions. +(type LowLevelInst + (enum (Add (mode AddrMode)) + (Load (offset i32) (addr Reg)) + (Const (c i32)))) + +;; Different kinds of addressing modes for operands to our low-level machine +;; instructions. +(type AddrMode + (enum + ;; Both operands in registers. + (RegReg (a Reg) (b Reg)) + ;; The destination/first operand is a register; the second operand is in + ;; memory at `[b + offset]`. + (RegMem (a Reg) (b Reg) (offset i32)) + ;; The destination/first operand is a register, second operand is an + ;; immediate. + (RegImm (a Reg) (imm i32)))) + +;; The register type is a Rust `Copy` type. +(type Reg (primitive Reg)) +``` + +Now we can start writing some basic lowering rules! We declare the top-level +lowering function (a "constructor term" in ISLE terminology) and attach rules to +it. The simplest case is matching a high-level `Const` instruction and lowering +that to a low-level `Const` instruction, since there isn't any translation we +really have to do. + +```lisp +;; Declare our top-level lowering function. We will attach rules to this +;; declaration for lowering various patterns of `HighLevelInst` inputs. +(decl lower (HighLevelInst) LowLevelInst) + +;; Simple rule for lowering constants. +(rule (lower (HighLevelInst.Const c)) + (LowLevelInst.Const c)) +``` + +Each rule has the form `(rule )`. The +left-hand side (LHS) is a *pattern* and the right-hand side (RHS) is an +*expression*. When the LHS pattern matches the input, then we evaluate the RHS +expression. The LHS pattern can bind variables from the input that are then +available in the right-hand side. For example, in our `Const`-lowering rule, the +variable `c` is bound from the LHS and then reused in the RHS. + +Now we can compile this code by running + +```shell +$ islec isle_examples/tutorial.isle +``` + +and we'll get the following output (ignoring any minor code generation +changes in the future): + +```rust +// GENERATED BY ISLE. DO NOT EDIT! +// +// Generated automatically from the instruction-selection DSL code in: +// - isle_examples/tutorial.isle + +// [Type and `Context` definitions removed for brevity...] + +// Generated as internal constructor for term lower. +pub fn constructor_lower(ctx: &mut C, arg0: &HighLevelInst) -> Option { + let pattern0_0 = arg0; + if let &HighLevelInst::Const { c: pattern1_0 } = pattern0_0 { + // Rule at isle_examples/tutorial.isle line 45. + let expr0_0 = LowLevelInst::Const { + c: pattern1_0, + }; + return Some(expr0_0); + } + return None; +} +``` + +There are a few things to notice about this generated Rust code: + +* The `lower` constructor term becomes the `constructor_lower` function in the + generated code. + +* The function returns a value of type `Option` and returns `None` + when it doesn't know how to lower an input `HighLevelInst`. This is useful for + incrementally porting hand-written lowering code to ISLE. + +* There is a helpful comment documenting where in the ISLE source code a rule + was defined. The goal is to ISLE more transparent and less magical. + +* The code is parameterized by a type that implements a `Context` + trait. Implementing this trait is how you glue the generated code into your + compiler. Right now this is an empty trait; more on `Context` later. + +* Lastly, and most importantly, this generated Rust code is basically what we + would have written by hand to do the same thing, other than things like + variable names. It checks if the input is a `Const`, and if so, translates it + into a `LowLevelInst::Const`. + +Okay, one rule isn't very impressive, but in order to start writing more rules +we need to be able to put the result of a lowered instruction into a `Reg`. This +might internally have to do arbitrary things like update use counts or anything +else that Cranelift's existing `LowerCtx::put_input_in_reg` does for different +target architectures. To allow for plugging in this kind of arbitrary logic, +ISLE supports *external constructors*. These end up as methods of the `Context` +trait in the generated Rust code, and you can implement them however you want +with custom Rust code. + +Here is how we declare an external helper to put a value into a register: + +```lisp +;; Declare an external constructor that puts a high-level `Value` into a +;; low-level `Reg`. +(decl put_in_reg (Value) Reg) +(extern constructor put_in_reg put_in_reg) +``` + +If we rerun `islec` on our ISLE source, instead of an empty `Context` trait, now +we will get this trait definition: + +```rust +pub trait Context { + fn put_in_reg(&mut self, arg0: Value) -> (Reg,); +} +``` + +With the `put_in_reg` helper available, we can define rules for lowering loads +and adds: + +```lisp +;; Simple rule for lowering adds. +(rule (lower (HighLevelInst.Add a b)) + (LowLevelInst.Add + (AddrMode.RegReg (put_in_reg a) (put_in_reg b)))) + +;; Simple rule for lowering loads. +(rule (lower (HighLevelInst.Load addr)) + (LowLevelInst.Load 0 (put_in_reg addr))) +``` + +If we compile our ISLE source into Rust code once again, the generated code for +`lower` now looks like this: + +```rust +// Generated as internal constructor for term lower. +pub fn constructor_lower(ctx: &mut C, arg0: &HighLevelInst) -> Option { + let pattern0_0 = arg0; + match pattern0_0 { + &HighLevelInst::Const { c: pattern1_0 } => { + // Rule at isle_examples/tutorial.isle line 45. + let expr0_0 = LowLevelInst::Const { + c: pattern1_0, + }; + return Some(expr0_0); + } + &HighLevelInst::Load { addr: pattern1_0 } => { + // Rule at isle_examples/tutorial.isle line 59. + let expr0_0: i32 = 0; + let expr1_0 = C::put_in_reg(ctx, pattern1_0); + let expr2_0 = LowLevelInst::Load { + offset: expr0_0, + addr: expr1_0, + }; + return Some(expr2_0); + } + &HighLevelInst::Add { a: pattern1_0, b: pattern1_1 } => { + // Rule at isle_examples/tutorial.isle line 54. + let expr0_0 = C::put_in_reg(ctx, pattern1_0); + let expr1_0 = C::put_in_reg(ctx, pattern1_1); + let expr2_0 = AddrMode::RegReg { + a: expr0_0, + b: expr1_0, + }; + let expr3_0 = LowLevelInst::Add { + mode: expr2_0, + }; + return Some(expr3_0); + } + _ => {} + } + return None; +} +``` + +As you can see, each of our rules was collapsed into a single, efficient `match` +expression. Just like we would have otherwise written by hand. And wherever we +need to get a high-level operand as a low-level register, there is a call to the +`Context::put_in_reg` trait method, allowing us to hook whatever arbitrary logic +we need to when putting a value into a register when we implement the `Context` +trait. + +Things start to get more interesting when we want to do things like sink a load +into the add's addressing mode. This is only desirable when our add is the only +use of the loaded value. Furthermore, it is only valid to do when there isn't +any store that might write to the same address we are loading from in between +the load and the add. Otherwise, moving the load across the store could result +in a miscompilation where we load the wrong value to add: + +```text +x = load addr +store 42 -> addr +y = add x, 1 + +==/==> + +store 42 -> addr +x = load addr +y = add x, 1 +``` + +We can encode these kinds of preconditions in an *external extractor*. An +extractor is like our regular constructor functions, but it is used inside LHS +patterns, rather than RHS expressions, and its arguments and results flipped +around: instead of taking arguments and producing results, it takes a result and +(fallibly) produces the arguments. This allows us to write custom preconditions +for matching code. + +Let's make this more clear with a concrete example. Here is the declaration of +an external extractor to match on the high-level instruction that defined a +given operand `Value`, along with a new rule to sink loads into adds: + +```lisp +;; Declare an external extractor for extracting the instruction that defined a +;; given operand value. +(decl inst_result (HighLevelInst) Value) +(extern extractor inst_result inst_result) + +;; Rule to sink loads into adds. +(rule (lower (HighLevelInst.Add a (inst_result (HighLevelInst.Load addr)))) + (LowLevelInst.Add + (AddrMode.RegMem (put_in_reg a) + (put_in_reg addr) + 0))) +``` + +Note that the operand `Value` passed into this extractor might be a basic block +parameter, in which case there is no such instruction. Or there might be a store +or function call instruction in between the current instruction and the +instruction that defines the given operand value, in which case we want to +"hide" the instruction so that we don't illegally sink loads into adds they +shouldn't be sunk into. So this extractor might fail to return an instruction +for a given operand `Value`. + +If we recompile our ISLE source into Rust code once again, we see a new +`inst_result` method defined on our `Context` trait, we notice that its +arguments and returns are flipped around from the `decl` in the ISLE source +because it is an extractor, and finally that it returns an `Option` because it +isn't guaranteed that we can extract a defining instruction for the given +operand `Value`: + +```rust +pub trait Context { + fn put_in_reg(&mut self, arg0: Value) -> (Reg,); + fn inst_result(&mut self, arg0: Value) -> Option<(HighLevelInst,)>; +} +``` + +And if we look at the generated code for our `lower` function, there is a new, +nested case for sinking loads into adds that uses the `Context::inst_result` +trait method to see if our new rule can be applied: + +```rust +// Generated as internal constructor for term lower. +pub fn constructor_lower(ctx: &mut C, arg0: &HighLevelInst) -> Option { + let pattern0_0 = arg0; + match pattern0_0 { + &HighLevelInst::Const { c: pattern1_0 } => { + // [...] + } + &HighLevelInst::Load { addr: pattern1_0 } => { + // [...] + } + &HighLevelInst::Add { a: pattern1_0, b: pattern1_1 } => { + if let Some((pattern2_0,)) = C::inst_result(ctx, pattern1_1) { + if let &HighLevelInst::Load { addr: pattern3_0 } = &pattern2_0 { + // Rule at isle_examples/tutorial.isle line 68. + let expr0_0 = C::put_in_reg(ctx, pattern1_0); + let expr1_0 = C::put_in_reg(ctx, pattern3_0); + let expr2_0: i32 = 0; + let expr3_0 = AddrMode::RegMem { + a: expr0_0, + b: expr1_0, + offset: expr2_0, + }; + let expr4_0 = LowLevelInst::Add { + mode: expr3_0, + }; + return Some(expr4_0); + } + } + // Rule at isle_examples/tutorial.isle line 54. + let expr0_0 = C::put_in_reg(ctx, pattern1_0); + let expr1_0 = C::put_in_reg(ctx, pattern1_1); + let expr2_0 = AddrMode::RegReg { + a: expr0_0, + b: expr1_0, + }; + let expr3_0 = LowLevelInst::Add { + mode: expr2_0, + }; + return Some(expr3_0); + } + _ => {} + } + return None; +} +``` + +Once again, this is pretty much the code you would have otherwise written by +hand to sink the load into the add. + +At this point we can start defining a whole bunch of even-more-complicated +lowering rules that do things like take advantage of folding static offsets into +loads into adds: + +```lisp +;; Rule to sink a load of a base address with a static offset into a single add. +(rule (lower (HighLevelInst.Add + a + (inst_result (HighLevelInst.Load + (inst_result (HighLevelInst.Add + base + (inst_result (HighLevelInst.Const offset)))))))) + (LowLevelInst.Add + (AddrMode.RegMem (put_in_reg a) + (put_in_reg base) + offset))) + +;; Rule for sinking an immediate into an add. +(rule (lower (HighLevelInst.Add a (inst_result (HighLevelInst.Const c)))) + (LowLevelInst.Add + (AddrMode.RegImm (put_in_reg a) c))) + +;; Rule for lowering loads of a base address with a static offset. +(rule (lower (HighLevelInst.Load + (inst_result (HighLevelInst.Add + base + (inst_result (HighLevelInst.Const offset)))))) + (LowLevelInst.Load offset (put_in_reg base))) +``` + +I'm not going to show the generated Rust code for these new rules here because +it is starting to get a bit too big. But you can compile +`isle_examples/tutorial.isle` and verify yourself that it generates the code you +expect it to. + +In conclusion, adding new lowering rules is easy with ISLE. And you still get +that efficient, compact tree of `match` expressions in the generated Rust code +that you would otherwise write by hand. + +## Implementation + +TODO + ## Sketch of Instruction Selector Please see [this Cranelift branch](https://github.com/cfallin/wasmtime/tree/isle) for an ongoing sketch of an instruction selector backend in Cranelift that uses ISLE. - -## Example Usage - -```plain - $ cargo build --release - $ target/release/isle -i isle_examples/test.isle -o isle_examples/test.rs - $ rustc isle_examples/test_main.rs -``` diff --git a/cranelift/isle/isle_examples/tutorial.isle b/cranelift/isle/isle_examples/tutorial.isle new file mode 100644 index 0000000000..d040ceeedb --- /dev/null +++ b/cranelift/isle/isle_examples/tutorial.isle @@ -0,0 +1,96 @@ +;;;; Type Definitions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Declare that we are using the `i32` primitive type from Rust. +(type i32 (primitive i32)) + +;; Our high-level, RISC-y input IR. +(type HighLevelInst + (enum (Add (a Value) (b Value)) + (Load (addr Value)) + (Const (c i32)))) + +;; A value in our high-level IR is a Rust `Copy` type. Values are either defined +;; by an instruction, or are a basic block argument. +(type Value (primitive Value)) + +;; Our low-level, CISC-y machine instructions. +(type LowLevelInst + (enum (Add (mode AddrMode)) + (Load (offset i32) (addr Reg)) + (Const (c i32)))) + +;; Different kinds of addressing modes for operands to our low-level machine +;; instructions. +(type AddrMode + (enum + ;; Both operands in registers. + (RegReg (a Reg) (b Reg)) + ;; The destination/first operand is a register; the second operand is in + ;; memory at `[b + offset]`. + (RegMem (a Reg) (b Reg) (offset i32)) + ;; The destination/first operand is a register, second operand is an + ;; immediate. + (RegImm (a Reg) (imm i32)))) + +;; The register type is a Rust `Copy` type. +(type Reg (primitive Reg)) + +;;;; Rules ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Declare our top-level lowering function. We will attach rules to this +;; declaration for lowering various patterns of `HighLevelInst` inputs. +(decl lower (HighLevelInst) LowLevelInst) + +;; Simple rule for lowering constants. +(rule (lower (HighLevelInst.Const c)) + (LowLevelInst.Const c)) + +;; Declare an external constructor that puts a high-level `Value` into a +;; low-level `Reg`. +(decl put_in_reg (Value) Reg) +(extern constructor put_in_reg put_in_reg) + +;; Simple rule for lowering adds. +(rule (lower (HighLevelInst.Add a b)) + (LowLevelInst.Add + (AddrMode.RegReg (put_in_reg a) (put_in_reg b)))) + +;; Simple rule for lowering loads. +(rule (lower (HighLevelInst.Load addr)) + (LowLevelInst.Load 0 (put_in_reg addr))) + +;; Declare an external extractor for extracting the instruction that defined a +;; given operand value. +(decl inst_result (HighLevelInst) Value) +(extern extractor inst_result inst_result) + +;; Rule to sink loads into adds. +(rule (lower (HighLevelInst.Add a (inst_result (HighLevelInst.Load addr)))) + (LowLevelInst.Add + (AddrMode.RegMem (put_in_reg a) + (put_in_reg addr) + 0))) + +;; Rule to sink a load of a base address with a static offset into a single add. +(rule (lower (HighLevelInst.Add + a + (inst_result (HighLevelInst.Load + (inst_result (HighLevelInst.Add + base + (inst_result (HighLevelInst.Const offset)))))))) + (LowLevelInst.Add + (AddrMode.RegMem (put_in_reg a) + (put_in_reg base) + offset))) + +;; Rule for sinking an immediate into an add. +(rule (lower (HighLevelInst.Add a (inst_result (HighLevelInst.Const c)))) + (LowLevelInst.Add + (AddrMode.RegImm (put_in_reg a) c))) + +;; Rule for lowering loads of a base address with a static offset. +(rule (lower (HighLevelInst.Load + (inst_result (HighLevelInst.Add + base + (inst_result (HighLevelInst.Const offset)))))) + (LowLevelInst.Load offset (put_in_reg base))) From 381dadadd00c6bcbadbf531c792102877c4bb222 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 4 Oct 2021 13:48:20 -0700 Subject: [PATCH 61/95] Move trie construction out to its own module --- cranelift/isle/isle/src/codegen.rs | 513 +------------------------- cranelift/isle/isle/src/compile.rs | 5 +- cranelift/isle/isle/src/lexer.rs | 32 +- cranelift/isle/isle/src/lib.rs | 1 + cranelift/isle/isle/src/sema.rs | 6 +- cranelift/isle/isle/src/trie.rs | 557 +++++++++++++++++++++++++++++ 6 files changed, 592 insertions(+), 522 deletions(-) create mode 100644 cranelift/isle/isle/src/trie.rs diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 576fc0aa75..f12d743b7b 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -1,507 +1,22 @@ //! Generate Rust code from a series of Sequences. -use crate::ir::{lower_rule, ExprInst, ExprSequence, InstId, PatternInst, PatternSequence, Value}; +use crate::ir::{ExprInst, InstId, PatternInst, Value}; use crate::sema::ExternalSig; -use crate::sema::{RuleId, TermEnv, TermId, Type, TypeEnv, TypeId, Variant}; +use crate::sema::{TermEnv, TermId, Type, TypeEnv, TypeId, Variant}; +use crate::trie::{TrieEdge, TrieNode, TrieSymbol}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; /// Emit Rust source code for the given type and term environments. -pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv) -> String { - Codegen::compile(typeenv, termenv).generate_rust() -} - -/// One "input symbol" for the decision tree that handles matching on -/// a term. Each symbol represents one step: we either run a match op, -/// or we finish the match. -/// -/// Note that in the original Peepmatic scheme, the input-symbol to -/// the FSM was specified slightly differently. The automaton -/// responded to alphabet symbols that corresponded only to match -/// results, and the "extra state" was used at each automaton node to -/// represent the op to run next. This extra state differentiated -/// nodes that would otherwise be merged together by -/// deduplication. That scheme works well enough, but the "extra -/// state" is slightly confusing and diverges slightly from a pure -/// automaton. -/// -/// Instead, here, we imagine that the user of the automaton/trie can -/// query the possible transition edges out of the current state. Each -/// of these edges corresponds to one possible match op to run. After -/// running a match op, we reach a new state corresponding to -/// successful matches up to that point. -/// -/// However, it's a bit more subtle than this. Consider the -/// prioritization problem. We want to give the DSL user the ability -/// to change the order in which rules apply, for example to have a -/// tier of "fallback rules" that apply only if more custom rules do -/// not match. -/// -/// A somewhat simplistic answer to this problem is "more specific -/// rule wins". However, this implies the existence of a total -/// ordering of linearized match sequences that may not fully capture -/// the intuitive meaning of "more specific". Consider three left-hand -/// sides: -/// -/// - (A _ _) -/// - (A (B _) _) -/// - (A _ (B _)) -/// -/// Intuitively, the first is the least specific. Given the input `(A -/// (B 1) (B 2))`, we can say for sure that the first should not be -/// chosen, because either the second or third would match "more" of -/// the input tree. But which of the second and third should be -/// chosen? A "lexicographic ordering" rule would say that we sort -/// left-hand sides such that the `(B _)` sub-pattern comes before the -/// wildcard `_`, so the second rule wins. But that is arbitrarily -/// privileging one over the other based on the order of the -/// arguments. -/// -/// Instead, we can accept explicit priorities from the user to allow -/// either choice. So we need a data structure that can associate -/// matching inputs *with priorities* to outputs. -/// -/// Next, we build a decision tree rather than an FSM. Why? Because -/// we're compiling to a structured language, Rust, and states become -/// *program points* rather than *data*, we cannot easily support a -/// DAG structure. In other words, we are not producing a FSM that we -/// can interpret at runtime; rather we are compiling code in which -/// each state corresponds to a sequence of statements and -/// control-flow that branches to a next state, we naturally need -/// nesting; we cannot codegen arbitrary state transitions in an -/// efficient manner. We could support a limited form of DAG that -/// reifies "diamonds" (two alternate paths that reconverge), but -/// supporting this in a way that lets the output refer to values from -/// either side is very complex (we need to invent phi-nodes), and the -/// cases where we want to do this rather than invoke a sub-term (that -/// is compiled to a separate function) are rare. Finally, note that -/// one reason to deduplicate nodes and turn a tree back into a DAG -- -/// "output-suffix sharing" as some other instruction-rewriter -/// engines, such as Peepmatic, do -- is not done, because all -/// "output" occurs at leaf nodes; this is necessary because we do not -/// want to start invoking external constructors until we are sure of -/// the match. Some of the code-sharing advantages of the "suffix -/// sharing" scheme can be obtained in a more flexible and -/// user-controllable way (with less understanding of internal -/// compiler logic needed) by factoring logic into different internal -/// terms, which become different compiled functions. This is likely -/// to happen anyway as part of good software engineering practice. -/// -/// We prepare for codegen by building a "prioritized trie", where the -/// trie associates input strings with priorities to output values. -/// Each input string is a sequence of match operators followed by an -/// "end of match" token, and each output is a sequence of ops that -/// build the output expression. Each input-output mapping is -/// associated with a priority. The goal of the trie is to generate a -/// decision-tree procedure that lets us execute match ops in a -/// deterministic way, eventually landing at a state that corresponds -/// to the highest-priority matching rule and can produce the output. -/// -/// To build this trie, we construct nodes with edges to child nodes; -/// each edge consists of (i) one input token (a `PatternInst` or -/// EOM), and (ii) the minimum and maximum priorities of rules along -/// this edge. In a way this resembles an interval tree, though the -/// intervals of children need not be disjoint. -/// -/// To add a rule to this trie, we perform the usual trie-insertion -/// logic, creating edges and subnodes where necessary, and updating -/// the priority-range of each edge that we traverse to include the -/// priority of the inserted rule. -/// -/// However, we need to be a little bit careful, because with only -/// priority ranges in place and the potential for overlap, we have -/// something that resembles an NFA. For example, consider the case -/// where we reach a node in the trie and have two edges with two -/// match ops, one corresponding to a rule with priority 10, and the -/// other corresponding to two rules, with priorities 20 and 0. The -/// final match could lie along *either* path, so we have to traverse -/// both. -/// -/// So, to avoid this, we perform a sort of moral equivalent to the -/// NFA-to-DFA conversion "on the fly" as we insert nodes by -/// duplicating subtrees. At any node, when inserting with a priority -/// P and when outgoing edges lie in a range [P_lo, P_hi] such that P -/// >= P_lo and P <= P_hi, we "priority-split the edges" at priority -/// P. -/// -/// To priority-split the edges in a node at priority P: -/// -/// - For each out-edge with priority [P_lo, P_hi] s.g. P \in [P_lo, -/// P_hi], and token T: -/// - Trim the subnode at P, yielding children C_lo and C_hi. -/// - Both children must be non-empty (have at least one leaf) -/// because the original node must have had a leaf at P_lo -/// and a leaf at P_hi. -/// - Replace the one edge with two edges, one for each child, with -/// the original match op, and with ranges calculated according to -/// the trimmed children. -/// -/// To trim a node into range [P_lo, P_hi]: -/// -/// - For a decision node: -/// - If any edges have a range outside the bounds of the trimming -/// range, trim the bounds of the edge, and trim the subtree under the -/// edge into the trimmed edge's range. If the subtree is trimmed -/// to `None`, remove the edge. -/// - If all edges are removed, the decision node becomes `None`. -/// - For a leaf node: -/// - If the priority is outside the range, the node becomes `None`. -/// -/// As we descend a path to insert a leaf node, we (i) priority-split -/// if any edges' priority ranges overlap the insertion priority -/// range, and (ii) expand priority ranges on edges to include the new -/// leaf node's priority. -/// -/// As long as we do this, we ensure the two key priority-trie -/// invariants: -/// -/// 1. At a given node, no two edges exist with priority ranges R_1, -/// R_2 such that R_1 ∩ R_2 ≠ ∅, unless R_1 and R_2 are unit ranges -/// ([x, x]) and are on edges with different match-ops. -/// 2. Along the path from the root to any leaf node with priority P, -/// each edge has a priority range R such that P ∈ R. -/// -/// Note that this means that multiple edges with a single match-op -/// may exist, with different priorities. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -enum TrieSymbol { - Match { op: PatternInst }, - EndOfMatch, -} - -impl TrieSymbol { - fn is_eom(&self) -> bool { - match self { - TrieSymbol::EndOfMatch => true, - _ => false, - } - } -} - -type Prio = i64; - -#[derive(Clone, Copy, Debug)] -struct PrioRange(Prio, Prio); - -impl PrioRange { - fn contains(&self, prio: Prio) -> bool { - prio >= self.0 && prio <= self.1 - } - - fn is_unit(&self) -> bool { - self.0 == self.1 - } - - fn overlaps(&self, other: PrioRange) -> bool { - // This can be derived via DeMorgan: !(self.begin > other.end - // OR other.begin > self.end). - self.0 <= other.1 && other.0 <= self.1 - } - - fn intersect(&self, other: PrioRange) -> PrioRange { - PrioRange( - std::cmp::max(self.0, other.0), - std::cmp::min(self.1, other.1), - ) - } - - fn union(&self, other: PrioRange) -> PrioRange { - PrioRange( - std::cmp::min(self.0, other.0), - std::cmp::max(self.1, other.1), - ) - } - - fn split_at(&self, prio: Prio) -> (PrioRange, PrioRange) { - assert!(self.contains(prio)); - assert!(!self.is_unit()); - if prio == self.0 { - (PrioRange(self.0, self.0), PrioRange(self.0 + 1, self.1)) - } else { - (PrioRange(self.0, prio - 1), PrioRange(prio, self.1)) - } - } -} - -#[derive(Clone, Debug)] -struct TrieEdge { - range: PrioRange, - symbol: TrieSymbol, - node: TrieNode, -} - -#[derive(Clone, Debug)] -enum TrieNode { - Decision { edges: Vec }, - Leaf { prio: Prio, output: ExprSequence }, - Empty, -} - -impl TrieNode { - fn is_empty(&self) -> bool { - matches!(self, &TrieNode::Empty) - } - - fn insert( - &mut self, - prio: Prio, - mut input: impl Iterator, - output: ExprSequence, - ) -> bool { - // Take one input symbol. There must be *at least* one, EOM if - // nothing else. - let op = input - .next() - .expect("Cannot insert into trie with empty input sequence"); - let is_last = op.is_eom(); - - // If we are empty, turn into a decision node. - if self.is_empty() { - *self = TrieNode::Decision { edges: vec![] }; - } - - // We must be a decision node. - let edges = match self { - &mut TrieNode::Decision { ref mut edges } => edges, - _ => panic!("insert on leaf node!"), - }; - - // Do we need to split? - let needs_split = edges - .iter() - .any(|edge| edge.range.contains(prio) && !edge.range.is_unit()); - - // If so, pass over all edges/subnodes and split each. - if needs_split { - let mut new_edges = vec![]; - for edge in std::mem::take(edges) { - if !edge.range.contains(prio) || edge.range.is_unit() { - new_edges.push(edge); - continue; - } - - let (lo_range, hi_range) = edge.range.split_at(prio); - let lo = edge.node.trim(lo_range); - let hi = edge.node.trim(hi_range); - if let Some((node, range)) = lo { - new_edges.push(TrieEdge { - range, - symbol: edge.symbol.clone(), - node, - }); - } - if let Some((node, range)) = hi { - new_edges.push(TrieEdge { - range, - symbol: edge.symbol, - node, - }); - } - } - *edges = new_edges; - } - - // Now find or insert the appropriate edge. - let mut edge: Option = None; - let mut last_edge_with_op: Option = None; - let mut last_edge_with_op_prio: Option = None; - for i in 0..(edges.len() + 1) { - if i == edges.len() || prio > edges[i].range.1 { - // We've passed all edges with overlapping priority - // ranges. Maybe the last edge we saw with the op - // we're inserting can have its range expanded, - // however. - if last_edge_with_op.is_some() { - // Move it to the end of the run of equal-unit-range ops. - edges.swap(last_edge_with_op.unwrap(), i - 1); - edge = Some(i - 1); - edges[i - 1].range.1 = prio; - break; - } - edges.insert( - i, - TrieEdge { - range: PrioRange(prio, prio), - symbol: op.clone(), - node: TrieNode::Empty, - }, - ); - edge = Some(i); - break; - } - if i == edges.len() { - break; - } - if edges[i].symbol == op { - last_edge_with_op = Some(i); - last_edge_with_op_prio = Some(edges[i].range.1); - } - if last_edge_with_op_prio.is_some() - && last_edge_with_op_prio.unwrap() < edges[i].range.1 - { - last_edge_with_op = None; - last_edge_with_op_prio = None; - } - if edges[i].range.contains(prio) && edges[i].symbol == op { - edge = Some(i); - break; - } - } - let edge = edge.expect("Must have found an edge at least at last iter"); - let edge = &mut edges[edge]; - - if is_last { - if !edge.node.is_empty() { - // If a leaf node already exists at an overlapping - // prio for this op, there are two competing rules, so - // we can't insert this one. - return false; - } - edge.node = TrieNode::Leaf { prio, output }; - true - } else { - edge.node.insert(prio, input, output) - } - } - - fn trim(&self, range: PrioRange) -> Option<(TrieNode, PrioRange)> { - match self { - &TrieNode::Empty => None, - &TrieNode::Leaf { prio, ref output } => { - if range.contains(prio) { - Some(( - TrieNode::Leaf { - prio, - output: output.clone(), - }, - PrioRange(prio, prio), - )) - } else { - None - } - } - &TrieNode::Decision { ref edges } => { - let edges = edges - .iter() - .filter_map(|edge| { - if !edge.range.overlaps(range) { - None - } else { - let range = range.intersect(edge.range); - if let Some((node, range)) = edge.node.trim(range) { - Some(TrieEdge { - range, - symbol: edge.symbol.clone(), - node, - }) - } else { - None - } - } - }) - .collect::>(); - - if edges.is_empty() { - None - } else { - let range = edges - .iter() - .map(|edge| edge.range) - .reduce(|a, b| a.union(b)) - .expect("reduce on non-empty vec must not return None"); - Some((TrieNode::Decision { edges }, range)) - } - } - } - } -} - -/// Builder context for one function in generated code corresponding -/// to one root input term. -/// -/// A `TermFunctionBuilder` can correspond to the matching -/// control-flow and operations that we execute either when evaluating -/// *forward* on a term, trying to match left-hand sides against it -/// and transforming it into another term; or *backward* on a term, -/// trying to match another rule's left-hand side against an input to -/// produce the term in question (when the term is used in the LHS of -/// the calling term). -#[derive(Debug)] -struct TermFunctionBuilder { - trie: TrieNode, -} - -impl TermFunctionBuilder { - fn new() -> Self { - TermFunctionBuilder { - trie: TrieNode::Empty, - } - } - - fn add_rule(&mut self, prio: Prio, pattern_seq: PatternSequence, expr_seq: ExprSequence) { - let symbols = pattern_seq - .insts - .into_iter() - .map(|op| TrieSymbol::Match { op }) - .chain(std::iter::once(TrieSymbol::EndOfMatch)); - self.trie.insert(prio, symbols, expr_seq); - } -} - -#[derive(Debug)] -struct TermFunctionsBuilder<'a> { - typeenv: &'a TypeEnv, - termenv: &'a TermEnv, - builders_by_term: HashMap, -} - -impl<'a> TermFunctionsBuilder<'a> { - fn new(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Self { - log::trace!("typeenv: {:?}", typeenv); - log::trace!("termenv: {:?}", termenv); - Self { - builders_by_term: HashMap::new(), - typeenv, - termenv, - } - } - - fn build(&mut self) { - for rule in 0..self.termenv.rules.len() { - let rule = RuleId(rule); - let prio = self.termenv.rules[rule.index()].prio.unwrap_or(0); - - let (pattern, expr) = lower_rule(self.typeenv, self.termenv, rule); - let root_term = self.termenv.rules[rule.index()].lhs.root_term().unwrap(); - - log::trace!( - "build:\n- rule {:?}\n- pattern {:?}\n- expr {:?}", - self.termenv.rules[rule.index()], - pattern, - expr - ); - self.builders_by_term - .entry(root_term) - .or_insert_with(|| TermFunctionBuilder::new()) - .add_rule(prio, pattern.clone(), expr.clone()); - } - } - - fn finalize(self) -> HashMap { - let functions_by_term = self - .builders_by_term - .into_iter() - .map(|(term, builder)| (term, builder.trie)) - .collect::>(); - functions_by_term - } +pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv, tries: &HashMap) -> String { + Codegen::compile(typeenv, termenv, tries).generate_rust() } #[derive(Clone, Debug)] struct Codegen<'a> { typeenv: &'a TypeEnv, termenv: &'a TermEnv, - functions_by_term: HashMap, + functions_by_term: &'a HashMap, } #[derive(Clone, Debug, Default)] @@ -511,15 +26,15 @@ struct BodyContext { } impl<'a> Codegen<'a> { - fn compile(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Codegen<'a> { - let mut builder = TermFunctionsBuilder::new(typeenv, termenv); - builder.build(); - log::trace!("builder: {:?}", builder); - let functions_by_term = builder.finalize(); + fn compile( + typeenv: &'a TypeEnv, + termenv: &'a TermEnv, + tries: &'a HashMap, + ) -> Codegen<'a> { Codegen { typeenv, termenv, - functions_by_term, + functions_by_term: tries, } } @@ -721,7 +236,7 @@ impl<'a> Codegen<'a> { } fn generate_internal_term_constructors(&self, code: &mut String) { - for (&termid, trie) in &self.functions_by_term { + for (&termid, trie) in self.functions_by_term { let termdata = &self.termenv.terms[termid.index()]; // Skip terms that are enum variants or that have external @@ -1156,7 +671,7 @@ impl<'a> Codegen<'a> { // variants in order to create a `match` rather than a // chain of if-lets. let mut edges = edges.clone(); - edges.sort_by(|e1, e2| (-e1.range.0, &e1.symbol).cmp(&(-e2.range.0, &e2.symbol))); + edges.sort_by(|e1, e2| (-e1.range.min, &e1.symbol).cmp(&(-e2.range.min, &e2.symbol))); let mut i = 0; while i < edges.len() { diff --git a/cranelift/isle/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs index c33b740674..8d9a1bb754 100644 --- a/cranelift/isle/isle/src/compile.rs +++ b/cranelift/isle/isle/src/compile.rs @@ -1,11 +1,12 @@ //! Compilation process, from AST to Sema to Sequences of Insts. use crate::error::Result; -use crate::{ast, codegen, sema}; +use crate::{ast, codegen, sema, trie}; /// Compile the given AST definitions into Rust source code. pub fn compile(defs: &ast::Defs) -> Result { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; - Ok(codegen::codegen(&typeenv, &termenv)) + let tries = trie::build_tries(&typeenv, &termenv); + Ok(codegen::codegen(&typeenv, &termenv, &tries)) } diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index f123b1a9de..b4b6bea3e4 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -319,15 +319,22 @@ impl Token { mod test { use super::*; + fn lex(s: &str, file: &str) -> Vec { + let mut toks = vec![]; + let mut lexer = Lexer::from_str(s, file).unwrap(); + while let Some((_, tok)) = lexer.next().unwrap() { + toks.push(tok); + } + toks + } + #[test] fn lexer_basic() { assert_eq!( - Lexer::from_str( + lex( ";; comment\n; another\r\n \t(one two three 23 -568 )\n", - "test" - ) - .map(|(_, tok)| tok) - .collect::>(), + "lexer_basic" + ), vec![ Token::LParen, Token::Symbol("one".to_string()), @@ -343,29 +350,20 @@ mod test { #[test] fn ends_with_sym() { assert_eq!( - Lexer::from_str("asdf", "test") - .map(|(_, tok)| tok) - .collect::>(), + lex("asdf", "ends_with_sym"), vec![Token::Symbol("asdf".to_string()),] ); } #[test] fn ends_with_num() { - assert_eq!( - Lexer::from_str("23", "test") - .map(|(_, tok)| tok) - .collect::>(), - vec![Token::Int(23)], - ); + assert_eq!(lex("23", "ends_with_num"), vec![Token::Int(23)],); } #[test] fn weird_syms() { assert_eq!( - Lexer::from_str("(+ [] => !! _test!;comment\n)", "test") - .map(|(_, tok)| tok) - .collect::>(), + lex("(+ [] => !! _test!;comment\n)", "weird_syms"), vec![ Token::LParen, Token::Symbol("+".to_string()), diff --git a/cranelift/isle/isle/src/lib.rs b/cranelift/isle/isle/src/lib.rs index b32fa349ff..c516a78d9e 100644 --- a/cranelift/isle/isle/src/lib.rs +++ b/cranelift/isle/isle/src/lib.rs @@ -26,3 +26,4 @@ pub mod ir; pub mod lexer; pub mod parser; pub mod sema; +pub mod trie; diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 516560b5d1..9c31016023 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -1518,7 +1518,7 @@ mod test { use super::*; use crate::ast::Ident; use crate::lexer::Lexer; - use crate::parser::Parser; + use crate::parser::parse; #[test] fn build_type_env() { @@ -1526,9 +1526,7 @@ mod test { (type u32 (primitive u32)) (type A extern (enum (B (f1 u32) (f2 u32)) (C (f1 u32)))) "; - let ast = Parser::new(Lexer::from_str(text, "file.isle")) - .parse_defs() - .expect("should parse"); + let ast = parse(Lexer::from_str(text, "file.isle").unwrap()).expect("should parse"); let tyenv = TypeEnv::from_ast(&ast).expect("should not have type-definition errors"); let sym_a = tyenv diff --git a/cranelift/isle/isle/src/trie.rs b/cranelift/isle/isle/src/trie.rs new file mode 100644 index 0000000000..62c37172ea --- /dev/null +++ b/cranelift/isle/isle/src/trie.rs @@ -0,0 +1,557 @@ +//! Trie construction. + +use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence}; +use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; +use std::collections::HashMap; + +/// Construct the tries for each term. +pub fn build_tries(typeenv: &TypeEnv, termenv: &TermEnv) -> HashMap { + let mut builder = TermFunctionsBuilder::new(typeenv, termenv); + builder.build(); + log::trace!("builder: {:?}", builder); + builder.finalize() +} + +/// One "input symbol" for the decision tree that handles matching on +/// a term. Each symbol represents one step: we either run a match op, +/// or we finish the match. +/// +/// Note that in the original Peepmatic scheme, the input-symbol to +/// the FSM was specified slightly differently. The automaton +/// responded to alphabet symbols that corresponded only to match +/// results, and the "extra state" was used at each automaton node to +/// represent the op to run next. This extra state differentiated +/// nodes that would otherwise be merged together by +/// deduplication. That scheme works well enough, but the "extra +/// state" is slightly confusing and diverges slightly from a pure +/// automaton. +/// +/// Instead, here, we imagine that the user of the automaton/trie can +/// query the possible transition edges out of the current state. Each +/// of these edges corresponds to one possible match op to run. After +/// running a match op, we reach a new state corresponding to +/// successful matches up to that point. +/// +/// However, it's a bit more subtle than this. Consider the +/// prioritization problem. We want to give the DSL user the ability +/// to change the order in which rules apply, for example to have a +/// tier of "fallback rules" that apply only if more custom rules do +/// not match. +/// +/// A somewhat simplistic answer to this problem is "more specific +/// rule wins". However, this implies the existence of a total +/// ordering of linearized match sequences that may not fully capture +/// the intuitive meaning of "more specific". Consider three left-hand +/// sides: +/// +/// - (A _ _) +/// - (A (B _) _) +/// - (A _ (B _)) +/// +/// Intuitively, the first is the least specific. Given the input `(A +/// (B 1) (B 2))`, we can say for sure that the first should not be +/// chosen, because either the second or third would match "more" of +/// the input tree. But which of the second and third should be +/// chosen? A "lexicographic ordering" rule would say that we sort +/// left-hand sides such that the `(B _)` sub-pattern comes before the +/// wildcard `_`, so the second rule wins. But that is arbitrarily +/// privileging one over the other based on the order of the +/// arguments. +/// +/// Instead, we can accept explicit priorities from the user to allow +/// either choice. So we need a data structure that can associate +/// matching inputs *with priorities* to outputs. +/// +/// Next, we build a decision tree rather than an FSM. Why? Because +/// we're compiling to a structured language, Rust, and states become +/// *program points* rather than *data*, we cannot easily support a +/// DAG structure. In other words, we are not producing a FSM that we +/// can interpret at runtime; rather we are compiling code in which +/// each state corresponds to a sequence of statements and +/// control-flow that branches to a next state, we naturally need +/// nesting; we cannot codegen arbitrary state transitions in an +/// efficient manner. We could support a limited form of DAG that +/// reifies "diamonds" (two alternate paths that reconverge), but +/// supporting this in a way that lets the output refer to values from +/// either side is very complex (we need to invent phi-nodes), and the +/// cases where we want to do this rather than invoke a sub-term (that +/// is compiled to a separate function) are rare. Finally, note that +/// one reason to deduplicate nodes and turn a tree back into a DAG -- +/// "output-suffix sharing" as some other instruction-rewriter +/// engines, such as Peepmatic, do -- is not done, because all +/// "output" occurs at leaf nodes; this is necessary because we do not +/// want to start invoking external constructors until we are sure of +/// the match. Some of the code-sharing advantages of the "suffix +/// sharing" scheme can be obtained in a more flexible and +/// user-controllable way (with less understanding of internal +/// compiler logic needed) by factoring logic into different internal +/// terms, which become different compiled functions. This is likely +/// to happen anyway as part of good software engineering practice. +/// +/// We prepare for codegen by building a "prioritized trie", where the +/// trie associates input strings with priorities to output values. +/// Each input string is a sequence of match operators followed by an +/// "end of match" token, and each output is a sequence of ops that +/// build the output expression. Each input-output mapping is +/// associated with a priority. The goal of the trie is to generate a +/// decision-tree procedure that lets us execute match ops in a +/// deterministic way, eventually landing at a state that corresponds +/// to the highest-priority matching rule and can produce the output. +/// +/// To build this trie, we construct nodes with edges to child nodes; +/// each edge consists of (i) one input token (a `PatternInst` or +/// EOM), and (ii) the minimum and maximum priorities of rules along +/// this edge. In a way this resembles an interval tree, though the +/// intervals of children need not be disjoint. +/// +/// To add a rule to this trie, we perform the usual trie-insertion +/// logic, creating edges and subnodes where necessary, and updating +/// the priority-range of each edge that we traverse to include the +/// priority of the inserted rule. +/// +/// However, we need to be a little bit careful, because with only +/// priority ranges in place and the potential for overlap, we have +/// something that resembles an NFA. For example, consider the case +/// where we reach a node in the trie and have two edges with two +/// match ops, one corresponding to a rule with priority 10, and the +/// other corresponding to two rules, with priorities 20 and 0. The +/// final match could lie along *either* path, so we have to traverse +/// both. +/// +/// So, to avoid this, we perform a sort of moral equivalent to the +/// NFA-to-DFA conversion "on the fly" as we insert nodes by +/// duplicating subtrees. At any node, when inserting with a priority +/// P and when outgoing edges lie in a range [P_lo, P_hi] such that P +/// >= P_lo and P <= P_hi, we "priority-split the edges" at priority +/// P. +/// +/// To priority-split the edges in a node at priority P: +/// +/// - For each out-edge with priority [P_lo, P_hi] s.g. P \in [P_lo, +/// P_hi], and token T: +/// - Trim the subnode at P, yielding children C_lo and C_hi. +/// - Both children must be non-empty (have at least one leaf) +/// because the original node must have had a leaf at P_lo +/// and a leaf at P_hi. +/// - Replace the one edge with two edges, one for each child, with +/// the original match op, and with ranges calculated according to +/// the trimmed children. +/// +/// To trim a node into range [P_lo, P_hi]: +/// +/// - For a decision node: +/// - If any edges have a range outside the bounds of the trimming +/// range, trim the bounds of the edge, and trim the subtree under the +/// edge into the trimmed edge's range. If the subtree is trimmed +/// to `None`, remove the edge. +/// - If all edges are removed, the decision node becomes `None`. +/// - For a leaf node: +/// - If the priority is outside the range, the node becomes `None`. +/// +/// As we descend a path to insert a leaf node, we (i) priority-split +/// if any edges' priority ranges overlap the insertion priority +/// range, and (ii) expand priority ranges on edges to include the new +/// leaf node's priority. +/// +/// As long as we do this, we ensure the two key priority-trie +/// invariants: +/// +/// 1. At a given node, no two edges exist with priority ranges R_1, +/// R_2 such that R_1 ∩ R_2 ≠ ∅, unless R_1 and R_2 are unit ranges +/// ([x, x]) and are on edges with different match-ops. +/// 2. Along the path from the root to any leaf node with priority P, +/// each edge has a priority range R such that P ∈ R. +/// +/// Note that this means that multiple edges with a single match-op +/// may exist, with different priorities. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum TrieSymbol { + /// Run a match operation to continue matching a LHS. + Match { + /// The match operation to run. + op: PatternInst, + }, + /// We successfully matched a LHS. + EndOfMatch, +} + +impl TrieSymbol { + fn is_eom(&self) -> bool { + match self { + TrieSymbol::EndOfMatch => true, + _ => false, + } + } +} + +/// A priority. +pub type Prio = i64; + +/// An inclusive range of priorities. +#[derive(Clone, Copy, Debug)] +pub struct PrioRange { + /// The minimum of this range. + pub min: Prio, + /// The maximum of this range. + pub max: Prio, +} + +impl PrioRange { + fn contains(&self, prio: Prio) -> bool { + prio >= self.min && prio <= self.max + } + + fn is_unit(&self) -> bool { + self.min == self.max + } + + fn overlaps(&self, other: PrioRange) -> bool { + // This can be derived via DeMorgan: !(self.begin > other.end + // OR other.begin > self.end). + self.min <= other.max && other.min <= self.max + } + + fn intersect(&self, other: PrioRange) -> PrioRange { + PrioRange { + min: std::cmp::max(self.min, other.min), + max: std::cmp::min(self.max, other.max), + } + } + + fn union(&self, other: PrioRange) -> PrioRange { + PrioRange { + min: std::cmp::min(self.min, other.min), + max: std::cmp::max(self.max, other.max), + } + } + + fn split_at(&self, prio: Prio) -> (PrioRange, PrioRange) { + assert!(self.contains(prio)); + assert!(!self.is_unit()); + if prio == self.min { + ( + PrioRange { + min: self.min, + max: self.min, + }, + PrioRange { + min: self.min + 1, + max: self.max, + }, + ) + } else { + ( + PrioRange { + min: self.min, + max: prio - 1, + }, + PrioRange { + min: prio, + max: self.max, + }, + ) + } + } +} + +/// An edge in our term trie. +#[derive(Clone, Debug)] +pub struct TrieEdge { + /// The priority range for this edge's sub-trie. + pub range: PrioRange, + /// The match operation to perform for this edge. + pub symbol: TrieSymbol, + /// This edge's sub-trie. + pub node: TrieNode, +} + +/// A node in the term trie. +#[derive(Clone, Debug)] +pub enum TrieNode { + /// One or more patterns could match. + /// + /// Maybe one pattern already has matched, but there are more (higher + /// priority and/or same priority but more specific) patterns that could + /// still match. + Decision { + /// The child sub-tries that we can match from this point on. + edges: Vec, + }, + + /// The successful match of an LHS pattern, and here is its RHS expression. + Leaf { + /// The priority of this rule. + prio: Prio, + /// The RHS expression to evaluate upon a successful LHS pattern match. + output: ExprSequence, + }, + + /// No LHS pattern matches. + Empty, +} + +impl TrieNode { + fn is_empty(&self) -> bool { + matches!(self, &TrieNode::Empty) + } + + fn insert( + &mut self, + prio: Prio, + mut input: impl Iterator, + output: ExprSequence, + ) -> bool { + // Take one input symbol. There must be *at least* one, EOM if + // nothing else. + let op = input + .next() + .expect("Cannot insert into trie with empty input sequence"); + let is_last = op.is_eom(); + + // If we are empty, turn into a decision node. + if self.is_empty() { + *self = TrieNode::Decision { edges: vec![] }; + } + + // We must be a decision node. + let edges = match self { + &mut TrieNode::Decision { ref mut edges } => edges, + _ => panic!("insert on leaf node!"), + }; + + // Do we need to split? + let needs_split = edges + .iter() + .any(|edge| edge.range.contains(prio) && !edge.range.is_unit()); + + // If so, pass over all edges/subnodes and split each. + if needs_split { + let mut new_edges = vec![]; + for edge in std::mem::take(edges) { + if !edge.range.contains(prio) || edge.range.is_unit() { + new_edges.push(edge); + continue; + } + + let (lo_range, hi_range) = edge.range.split_at(prio); + let lo = edge.node.trim(lo_range); + let hi = edge.node.trim(hi_range); + if let Some((node, range)) = lo { + new_edges.push(TrieEdge { + range, + symbol: edge.symbol.clone(), + node, + }); + } + if let Some((node, range)) = hi { + new_edges.push(TrieEdge { + range, + symbol: edge.symbol, + node, + }); + } + } + *edges = new_edges; + } + + // Now find or insert the appropriate edge. + let mut edge: Option = None; + let mut last_edge_with_op: Option = None; + let mut last_edge_with_op_prio: Option = None; + for i in 0..(edges.len() + 1) { + if i == edges.len() || prio > edges[i].range.max { + // We've passed all edges with overlapping priority + // ranges. Maybe the last edge we saw with the op + // we're inserting can have its range expanded, + // however. + if last_edge_with_op.is_some() { + // Move it to the end of the run of equal-unit-range ops. + edges.swap(last_edge_with_op.unwrap(), i - 1); + edge = Some(i - 1); + edges[i - 1].range.max = prio; + break; + } + edges.insert( + i, + TrieEdge { + range: PrioRange { + min: prio, + max: prio, + }, + symbol: op.clone(), + node: TrieNode::Empty, + }, + ); + edge = Some(i); + break; + } + if i == edges.len() { + break; + } + if edges[i].symbol == op { + last_edge_with_op = Some(i); + last_edge_with_op_prio = Some(edges[i].range.max); + } + if last_edge_with_op_prio.is_some() + && last_edge_with_op_prio.unwrap() < edges[i].range.max + { + last_edge_with_op = None; + last_edge_with_op_prio = None; + } + if edges[i].range.contains(prio) && edges[i].symbol == op { + edge = Some(i); + break; + } + } + let edge = edge.expect("Must have found an edge at least at last iter"); + let edge = &mut edges[edge]; + + if is_last { + if !edge.node.is_empty() { + // If a leaf node already exists at an overlapping + // prio for this op, there are two competing rules, so + // we can't insert this one. + return false; + } + edge.node = TrieNode::Leaf { prio, output }; + true + } else { + edge.node.insert(prio, input, output) + } + } + + fn trim(&self, range: PrioRange) -> Option<(TrieNode, PrioRange)> { + match self { + &TrieNode::Empty => None, + &TrieNode::Leaf { prio, ref output } => { + if range.contains(prio) { + Some(( + TrieNode::Leaf { + prio, + output: output.clone(), + }, + PrioRange { + min: prio, + max: prio, + }, + )) + } else { + None + } + } + &TrieNode::Decision { ref edges } => { + let edges = edges + .iter() + .filter_map(|edge| { + if !edge.range.overlaps(range) { + None + } else { + let range = range.intersect(edge.range); + if let Some((node, range)) = edge.node.trim(range) { + Some(TrieEdge { + range, + symbol: edge.symbol.clone(), + node, + }) + } else { + None + } + } + }) + .collect::>(); + + if edges.is_empty() { + None + } else { + let range = edges + .iter() + .map(|edge| edge.range) + .reduce(|a, b| a.union(b)) + .expect("reduce on non-empty vec must not return None"); + Some((TrieNode::Decision { edges }, range)) + } + } + } + } +} + +/// Builder context for one function in generated code corresponding +/// to one root input term. +/// +/// A `TermFunctionBuilder` can correspond to the matching +/// control-flow and operations that we execute either when evaluating +/// *forward* on a term, trying to match left-hand sides against it +/// and transforming it into another term; or *backward* on a term, +/// trying to match another rule's left-hand side against an input to +/// produce the term in question (when the term is used in the LHS of +/// the calling term). +#[derive(Debug)] +struct TermFunctionBuilder { + trie: TrieNode, +} + +impl TermFunctionBuilder { + fn new() -> Self { + TermFunctionBuilder { + trie: TrieNode::Empty, + } + } + + fn add_rule(&mut self, prio: Prio, pattern_seq: PatternSequence, expr_seq: ExprSequence) { + let symbols = pattern_seq + .insts + .into_iter() + .map(|op| TrieSymbol::Match { op }) + .chain(std::iter::once(TrieSymbol::EndOfMatch)); + self.trie.insert(prio, symbols, expr_seq); + } +} + +#[derive(Debug)] +struct TermFunctionsBuilder<'a> { + typeenv: &'a TypeEnv, + termenv: &'a TermEnv, + builders_by_term: HashMap, +} + +impl<'a> TermFunctionsBuilder<'a> { + fn new(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Self { + log::trace!("typeenv: {:?}", typeenv); + log::trace!("termenv: {:?}", termenv); + Self { + builders_by_term: HashMap::new(), + typeenv, + termenv, + } + } + + fn build(&mut self) { + for rule in 0..self.termenv.rules.len() { + let rule = RuleId(rule); + let prio = self.termenv.rules[rule.index()].prio.unwrap_or(0); + + let (pattern, expr) = lower_rule(self.typeenv, self.termenv, rule); + let root_term = self.termenv.rules[rule.index()].lhs.root_term().unwrap(); + + log::trace!( + "build:\n- rule {:?}\n- pattern {:?}\n- expr {:?}", + self.termenv.rules[rule.index()], + pattern, + expr + ); + self.builders_by_term + .entry(root_term) + .or_insert_with(|| TermFunctionBuilder::new()) + .add_rule(prio, pattern.clone(), expr.clone()); + } + } + + fn finalize(self) -> HashMap { + let functions_by_term = self + .builders_by_term + .into_iter() + .map(|(term, builder)| (term, builder.trie)) + .collect::>(); + functions_by_term + } +} From 31d1cf38082249ae79dbaf4a6c3af31be8533994 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 4 Oct 2021 14:11:22 -0700 Subject: [PATCH 62/95] Fill out implementation overview in README --- cranelift/isle/README.md | 89 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/cranelift/isle/README.md b/cranelift/isle/README.md index 17a9fe79c0..635fe863f8 100644 --- a/cranelift/isle/README.md +++ b/cranelift/isle/README.md @@ -434,7 +434,94 @@ that you would otherwise write by hand. ## Implementation -TODO +This is an overview of `islec`'s passes and data structures: + +```text + +------------------+ + | ISLE Source Text | + +------------------+ + | + | Lex + V + +--------+ + | Tokens | + +--------+ + | + | Parse + V + +----------------------+ + | Abstract Syntax Tree | + +----------------------+ + | + | Semantic Analysis + V ++----------------------------+ +| Term and Type Environments | ++----------------------------+ + | + | Trie Construction + V + +-----------+ + | Term Trie | + +-----------+ + | + | Code Generation + V + +------------------+ + | Rust Source Code | + +------------------+ +``` + +### Lexing + +Lexing breaks up the input ISLE source text into a stream of tokens. Our lexer +is pull-based, meaning that we don't eagerly construct the full stream of +tokens. Instead, we wait until the next token is requested, at which point we +lazily lex it. + +Relevant source files: + +* `isle/src/lexer.rs` + +### Parsing + +Parsing translates the stream of tokens into an abstract syntax tree (AST). Our +parser is a simple, hand-written, recursive-descent parser. + +Relevant source files: + +* `isle/src/ast.rs` +* `isle/src/parser.rs` + +### Semantic Analysis + +Semantic analysis performs type checking, figures out which rules apply to which +terms, etc. It creates a type environment and a term environment that we can use +to get information about our terms throughout the rest of the pipeline. + +Relevant source files: + +* `isle/src/sema.rs` + +### Trie Construction + +The trie construction phase linearizes each rule's LHS pattern and inserts them +into a trie that maps LHS patterns to RHS expressions. This trie is the skeleton +of the decision tree that will be emitted during code generation. + +Relevant source files: + +* `isle/src/ir.rs` +* `isle/src/trie.rs` + +### Code Generation + +Code generation takes in the term trie and emits Rust source code that +implements it. + +Relevant source files: + +* `isle/src/codegen.rs` ## Sketch of Instruction Selector From 9af23bf061ed4f3b73ef8162e8d110b785fde295 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 5 Oct 2021 14:40:51 -0700 Subject: [PATCH 63/95] Report the recursive calls when an extractor is recursive --- cranelift/isle/isle/src/sema.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 9c31016023..14062ea3f0 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -879,20 +879,36 @@ impl TermEnv { 'outer: for root in extractor_call_graph.keys().copied() { seen.clear(); stack.clear(); - stack.push(root); + stack.push((root, vec![root])); - while let Some(caller) = stack.pop() { - let already_seen = seen.insert(caller); - if already_seen { + while let Some((caller, path)) = stack.pop() { + let is_new = seen.insert(caller); + if is_new { + if let Some(callees) = extractor_call_graph.get(&caller) { + stack.extend(callees.iter().map(|callee| { + let mut path = path.clone(); + path.push(*callee); + (*callee, path) + })); + } + } else { let term = self.term_map[&caller]; let pos = match &self.terms[term.index()].kind { TermKind::InternalExtractor { template } => template.pos(), _ => unreachable!(), }; - tyenv.report_error(pos, "extractor definition is recursive".into()); + + let path: Vec<_> = path + .iter() + .map(|sym| tyenv.syms[sym.index()].as_str()) + .collect(); + let msg = format!( + "`{}` extractor definition is recursive: {}", + tyenv.syms[root.index()], + path.join(" -> ") + ); + tyenv.report_error(pos, msg); continue 'outer; - } else { - stack.extend(extractor_call_graph[&caller].iter().copied()); } } } @@ -957,7 +973,7 @@ impl TermEnv { pos, format!( "Constructor defined on term of improper type '{}'", - term.0 + term.0, ), ); } From fddff6ee2d87b92015759660e39ce37f4401e833 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 5 Oct 2021 16:35:07 -0700 Subject: [PATCH 64/95] Fix a panic when checking the call graph for an extractor with type errors in its definition --- cranelift/isle/isle/src/ast.rs | 6 +++--- cranelift/isle/isle/src/sema.rs | 26 +++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index 5af7eaf6d7..4b2aa84ca0 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -128,10 +128,10 @@ impl Pattern { } /// Call `f` for each of the terms in this pattern. - pub fn terms(&self, f: &mut dyn FnMut(&Ident)) { + pub fn terms(&self, f: &mut dyn FnMut(Pos, &Ident)) { match self { - Pattern::Term { sym, args, .. } => { - f(sym); + Pattern::Term { sym, args, pos } => { + f(*pos, sym); for arg in args { if let TermArgPattern::Pattern(p) = arg { p.terms(f); diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 14062ea3f0..f437a23279 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -847,17 +847,29 @@ impl TermEnv { return; } }; - let termdata = &mut self.terms[term.index()]; + let template = ext.template.make_macro_template(&ext.args[..]); log::trace!("extractor def: {:?} becomes template {:?}", def, template); let mut callees = HashSet::new(); - template.terms(&mut |t| { + template.terms(&mut |pos, t| { let t = tyenv.intern_mut(t); callees.insert(t); + + if !self.term_map.contains_key(&t) { + tyenv.report_error( + pos, + format!( + "`{}` extractor definition references unknown term `{}`", + ext.term.0, + tyenv.syms[t.index()] + ), + ); + } }); extractor_call_graph.insert(sym, callees); + let termdata = &mut self.terms[term.index()]; match &termdata.kind { &TermKind::Declared => { termdata.kind = TermKind::InternalExtractor { template }; @@ -892,7 +904,15 @@ impl TermEnv { })); } } else { - let term = self.term_map[&caller]; + let term = match self.term_map.get(&caller) { + Some(t) => t, + None => { + // Some other error must have already been recorded + // if we don't have the caller's term data. + assert!(!tyenv.errors.is_empty()); + continue 'outer; + } + }; let pos = match &self.terms[term.index()].kind { TermKind::InternalExtractor { template } => template.pos(), _ => unreachable!(), From 7f8cb75e549da156ce6b0e701fecabd217913648 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 7 Oct 2021 16:38:47 -0700 Subject: [PATCH 65/95] Allow terms to have both extractors and constructors Fixes #4 --- cranelift/isle/isle/src/codegen.rs | 20 +- cranelift/isle/isle/src/ir.rs | 60 ++- cranelift/isle/isle/src/sema.rs | 439 ++++++++++++++---- .../isle_examples/construct-and-extract.isle | 17 + 4 files changed, 420 insertions(+), 116 deletions(-) create mode 100644 cranelift/isle/isle_examples/construct-and-extract.isle diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index f12d743b7b..32e291a4dc 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -117,8 +117,12 @@ impl<'a> Codegen<'a> { .unwrap(); writeln!(code, "pub trait Context {{").unwrap(); for term in &self.termenv.terms { - if term.is_external() { - let ext_sig = term.to_sig(self.typeenv).unwrap(); + if term.has_external_extractor() { + let ext_sig = term.extractor_sig(self.typeenv).unwrap(); + self.generate_trait_sig(code, " ", &ext_sig); + } + if term.has_external_constructor() { + let ext_sig = term.constructor_sig(self.typeenv).unwrap(); self.generate_trait_sig(code, " ", &ext_sig); } } @@ -241,11 +245,11 @@ impl<'a> Codegen<'a> { // Skip terms that are enum variants or that have external // constructors/extractors. - if !termdata.is_constructor() || termdata.is_external() { + if !termdata.has_constructor() || termdata.has_external_constructor() { continue; } - let sig = termdata.to_sig(self.typeenv).unwrap(); + let sig = termdata.constructor_sig(self.typeenv).unwrap(); let args = sig .param_tys @@ -393,7 +397,7 @@ impl<'a> Codegen<'a> { }; let outputname = self.value_name(&output); let termdata = &self.termenv.terms[term.index()]; - let sig = termdata.to_sig(self.typeenv).unwrap(); + let sig = termdata.constructor_sig(self.typeenv).unwrap(); assert_eq!(input_exprs.len(), sig.param_tys.len()); let fallible_try = if infallible { "" } else { "?" }; writeln!( @@ -529,7 +533,7 @@ impl<'a> Codegen<'a> { .. } => { let termdata = &self.termenv.terms[term.index()]; - let sig = termdata.to_sig(self.typeenv).unwrap(); + let sig = termdata.extractor_sig(self.typeenv).unwrap(); let input_values = inputs .iter() @@ -671,7 +675,9 @@ impl<'a> Codegen<'a> { // variants in order to create a `match` rather than a // chain of if-lets. let mut edges = edges.clone(); - edges.sort_by(|e1, e2| (-e1.range.min, &e1.symbol).cmp(&(-e2.range.min, &e2.symbol))); + edges.sort_by(|e1, e2| { + (-e1.range.min, &e1.symbol).cmp(&(-e2.range.min, &e2.symbol)) + }); let mut i = 0; while i < edges.len() { diff --git a/cranelift/isle/isle/src/ir.rs b/cranelift/isle/isle/src/ir.rs index 3138537eb8..cd197f7946 100644 --- a/cranelift/isle/isle/src/ir.rs +++ b/cranelift/isle/isle/src/ir.rs @@ -400,12 +400,9 @@ impl PatternSequence { let termdata = &termenv.terms[term.index()]; let arg_tys = &termdata.arg_tys[..]; match &termdata.kind { - &TermKind::Declared => { - panic!("Pattern invocation of undefined term body"); - } - &TermKind::EnumVariant { variant } => { + TermKind::EnumVariant { variant } => { let arg_values = - self.add_match_variant(input, ty, arg_tys, variant); + self.add_match_variant(input, ty, arg_tys, *variant); for (subpat, value) in args.iter().zip(arg_values.into_iter()) { let subpat = match subpat { &TermArgPattern::Pattern(ref pat) => pat, @@ -420,16 +417,25 @@ impl PatternSequence { ); } } - &TermKind::InternalConstructor - | &TermKind::ExternalConstructor { .. } => { - panic!("Should not invoke constructor in pattern"); + TermKind::Decl { + extractor_kind: None, + .. + } => { + panic!("Pattern invocation of undefined term body") } - &TermKind::InternalExtractor { .. } => { - panic!("Should have been expanded away"); + TermKind::Decl { + extractor_kind: Some(ExtractorKind::InternalExtractor { .. }), + .. + } => { + panic!("Should have been expanded away") } - &TermKind::ExternalExtractor { - ref arg_polarity, - infallible, + TermKind::Decl { + extractor_kind: + Some(ExtractorKind::ExternalExtractor { + ref arg_polarity, + infallible, + .. + }), .. } => { // Evaluate all `input` args. @@ -469,8 +475,13 @@ impl PatternSequence { } // Invoke the extractor. - let arg_values = self - .add_extract(inputs, input_tys, output_tys, term, infallible); + let arg_values = self.add_extract( + inputs, + input_tys, + output_tys, + term, + *infallible, + ); for (pat, &val) in output_pats.iter().zip(arg_values.iter()) { self.gen_pattern( @@ -594,10 +605,13 @@ impl ExprSequence { .push((self.gen_expr(typeenv, termenv, &*arg_expr, &vars), arg_ty)); } match &termdata.kind { - &TermKind::EnumVariant { variant } => { - self.add_create_variant(&arg_values_tys[..], ty, variant) + TermKind::EnumVariant { variant } => { + self.add_create_variant(&arg_values_tys[..], ty, *variant) } - &TermKind::InternalConstructor => { + TermKind::Decl { + constructor_kind: Some(ConstructorKind::InternalConstructor), + .. + } => { self.add_construct( &arg_values_tys[..], ty, @@ -605,7 +619,10 @@ impl ExprSequence { /* infallible = */ false, ) } - &TermKind::ExternalConstructor { .. } => { + TermKind::Decl { + constructor_kind: Some(ConstructorKind::ExternalConstructor { .. }), + .. + } => { self.add_construct( &arg_values_tys[..], ty, @@ -613,7 +630,10 @@ impl ExprSequence { /* infallible = */ true, ) } - otherwise => panic!("Should have been caught by typechecking: {:?}", otherwise), + TermKind::Decl { + constructor_kind: None, + .. + } => panic!("Should have been caught by typechecking"), } } } diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index f437a23279..ae87981b65 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -210,9 +210,31 @@ pub enum TermKind { /// `(A.A1 ...)` then the variant ID corresponds to `A1`. variant: VariantId, }, + /// A term declared via a `(decl ...)` form. + Decl { + /// The kind of this term's constructor, if any. + constructor_kind: Option, + /// The kind of this term's extractor, if any. + extractor_kind: Option, + }, +} + +/// The kind of a constructor for a term. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ConstructorKind { /// A term with "internal" rules that work in the forward direction. Becomes /// a compiled Rust function in the generated code. InternalConstructor, + /// A term defined solely by an external constructor function. + ExternalConstructor { + /// The external name of the constructor function. + name: Sym, + }, +} + +/// The kind of an extractor for a term. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ExtractorKind { /// A term that defines an "extractor macro" in the LHS of a pattern. Its /// arguments take patterns and are simply substituted with the given /// patterns when used. @@ -228,14 +250,9 @@ pub enum TermKind { arg_polarity: Vec, /// Is the external extractor infallible? infallible: bool, + /// The position where this external extractor was declared. + pos: Pos, }, - /// A term defined solely by an external constructor function. - ExternalConstructor { - /// The external name of the constructor function. - name: Sym, - }, - /// Declared but no body or externs associated (yet). - Declared, } pub use crate::ast::ArgPolarity; @@ -261,40 +278,69 @@ impl Term { self.ret_ty } - /// Is this term a constructor? - pub fn is_constructor(&self) -> bool { - match &self.kind { - &TermKind::InternalConstructor { .. } | &TermKind::ExternalConstructor { .. } => true, - _ => false, - } + /// Is this term an enum variant? + pub fn is_enum_variant(&self) -> bool { + matches!(self.kind, TermKind::EnumVariant { .. }) } - fn is_declared(&self) -> bool { - matches!(self.kind, TermKind::Declared) + /// Does this term have a constructor? + pub fn has_constructor(&self) -> bool { + matches!( + self.kind, + TermKind::EnumVariant { .. } + | TermKind::Decl { + constructor_kind: Some(_), + .. + } + ) } - /// Is this term external? - pub fn is_external(&self) -> bool { - match &self.kind { - &TermKind::ExternalExtractor { .. } | &TermKind::ExternalConstructor { .. } => true, - _ => false, - } + /// Does this term have an extractor? + pub fn has_extractor(&self) -> bool { + matches!( + self.kind, + TermKind::EnumVariant { .. } + | TermKind::Decl { + extractor_kind: Some(_), + .. + } + ) } - /// Get this term's external function signature, if any. - pub fn to_sig(&self, tyenv: &TypeEnv) -> Option { + /// Is this term's extractor external? + pub fn has_external_extractor(&self) -> bool { + matches!( + self.kind, + TermKind::Decl { + extractor_kind: Some(ExtractorKind::ExternalExtractor { .. }), + .. + } + ) + } + + /// Is this term's constructor external? + pub fn has_external_constructor(&self) -> bool { + matches!( + self.kind, + TermKind::Decl { + constructor_kind: Some(ConstructorKind::ExternalConstructor { .. }), + .. + } + ) + } + + /// Get this term's extractor's external function signature, if any. + pub fn extractor_sig(&self, tyenv: &TypeEnv) -> Option { match &self.kind { - &TermKind::ExternalConstructor { name } => 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, - }), - &TermKind::ExternalExtractor { - name, - ref arg_polarity, - infallible, + TermKind::Decl { + extractor_kind: + Some(ExtractorKind::ExternalExtractor { + name, + ref arg_polarity, + infallible, + .. + }), + .. } => { let mut arg_tys = vec![]; let mut ret_tys = vec![]; @@ -314,10 +360,30 @@ impl Term { full_name: format!("C::{}", tyenv.syms[name.index()]), param_tys: arg_tys, ret_tys, - infallible, + infallible: *infallible, }) } - &TermKind::InternalConstructor { .. } => { + _ => None, + } + } + + /// Get this term's constructor's external function signature, if any. + pub fn constructor_sig(&self, tyenv: &TypeEnv) -> Option { + match &self.kind { + TermKind::Decl { + constructor_kind: Some(ConstructorKind::ExternalConstructor { name }), + .. + } => 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, + }), + TermKind::Decl { + constructor_kind: Some(ConstructorKind::InternalConstructor { .. }), + .. + } => { let name = format!("constructor_{}", tyenv.syms[self.name.index()]); Some(ExternalSig { func_name: name.clone(), @@ -740,7 +806,10 @@ impl TermEnv { name, arg_tys, ret_ty, - kind: TermKind::Declared, + kind: TermKind::Decl { + constructor_kind: None, + extractor_kind: None, + }, }); } _ => {} @@ -813,15 +882,35 @@ impl TermEnv { } }; let termdata = &mut self.terms[term.index()]; - match &termdata.kind { - &TermKind::Declared => { - termdata.kind = TermKind::InternalConstructor; + match &mut termdata.kind { + TermKind::Decl { + constructor_kind, .. + } => { + match constructor_kind { + None => { + *constructor_kind = Some(ConstructorKind::InternalConstructor); + } + Some(ConstructorKind::InternalConstructor) => { + // OK, no error; multiple rules can apply to + // one internal constructor term. + } + Some(ConstructorKind::ExternalConstructor { .. }) => { + tyenv.report_error( + pos, + "Rule LHS root term is incorrect kind; cannot \ + be external constructor" + .to_string(), + ); + continue; + } + } } - &TermKind::InternalConstructor => { - // OK, no error; multiple rules can apply to one internal constructor term. - } - _ => { - tyenv.report_error(pos, "Rule LHS root term is incorrect kind; cannot be internal constructor".to_string()); + TermKind::EnumVariant { .. } => { + tyenv.report_error( + pos, + "Rule LHS root term is incorrect kind; cannot be enum variant" + .to_string(), + ); continue; } } @@ -870,17 +959,36 @@ impl TermEnv { extractor_call_graph.insert(sym, callees); let termdata = &mut self.terms[term.index()]; - match &termdata.kind { - &TermKind::Declared => { - termdata.kind = TermKind::InternalExtractor { template }; - } - _ => { + match &mut termdata.kind { + TermKind::EnumVariant { .. } => { tyenv.report_error( ext.pos, - "Extractor macro body defined on term of incorrect kind".to_string(), + "Extractor macro body defined on term of incorrect kind; cannot be an \ + enum variant" + .into(), ); continue; } + TermKind::Decl { extractor_kind, .. } => match extractor_kind { + None => { + *extractor_kind = Some(ExtractorKind::InternalExtractor { template }); + } + Some(ext_kind) => { + tyenv.report_error( + ext.pos, + "Duplicate extractor definition".to_string(), + ); + let pos = match ext_kind { + ExtractorKind::InternalExtractor { template } => template.pos(), + ExtractorKind::ExternalExtractor { pos, .. } => *pos, + }; + tyenv.report_error( + pos, + "Extractor was already defined here".to_string(), + ); + continue; + } + }, } } } @@ -914,8 +1022,16 @@ impl TermEnv { } }; let pos = match &self.terms[term.index()].kind { - TermKind::InternalExtractor { template } => template.pos(), - _ => unreachable!(), + TermKind::Decl { + extractor_kind: Some(ExtractorKind::InternalExtractor { template }), + .. + } => template.pos(), + _ => { + // Again, there must have already been errors + // recorded. + assert!(!tyenv.errors.is_empty()); + continue 'outer; + } }; let path: Vec<_> = path @@ -944,11 +1060,37 @@ impl TermEnv { vars: vec![], }; + let rule_term = match rule.pattern.root_term() { + Some(name) => { + let sym = tyenv.intern_mut(name); + match self.term_map.get(&sym) { + Some(term) => *term, + None => { + tyenv.report_error( + pos, + "Cannot define a rule for an unknown term".to_string(), + ); + continue; + } + } + } + None => { + tyenv.report_error( + pos, + "Rule does not have a term at the root of its left-hand side" + .to_string(), + ); + continue; + } + }; + let (lhs, ty) = unwrap_or_continue!(self.translate_pattern( tyenv, + rule_term, &rule.pattern, None, - &mut bindings + &mut bindings, + /* is_root = */ true, )); let rhs = unwrap_or_continue!(self.translate_expr( tyenv, @@ -984,15 +1126,35 @@ impl TermEnv { } }; let termdata = &mut self.terms[term_id.index()]; - match &termdata.kind { - &TermKind::Declared => { - termdata.kind = TermKind::ExternalConstructor { name: func_sym }; - } - _ => { + match &mut termdata.kind { + TermKind::Decl { + constructor_kind, .. + } => match constructor_kind { + None => { + *constructor_kind = + Some(ConstructorKind::ExternalConstructor { name: func_sym }); + } + Some(ConstructorKind::InternalConstructor) => { + tyenv.report_error( + pos, + format!( + "External constructor declared on term that already has rules: {}", + term.0, + ), + ); + } + Some(ConstructorKind::ExternalConstructor { .. }) => { + tyenv.report_error( + pos, + "Duplicate external constructor definition".to_string(), + ); + } + }, + TermKind::EnumVariant { .. } => { tyenv.report_error( pos, format!( - "Constructor defined on term of improper type '{}'", + "External constructor cannot be defined on enum variant: {}", term.0, ), ); @@ -1031,18 +1193,45 @@ impl TermEnv { vec![ArgPolarity::Output; termdata.arg_tys.len()] }; - match &termdata.kind { - &TermKind::Declared => { - termdata.kind = TermKind::ExternalExtractor { - name: func_sym, - arg_polarity, - infallible, - }; - } - _ => { + match &mut termdata.kind { + TermKind::Decl { extractor_kind, .. } => match extractor_kind { + None => { + *extractor_kind = Some(ExtractorKind::ExternalExtractor { + name: func_sym, + arg_polarity, + infallible, + pos, + }); + } + Some(ExtractorKind::ExternalExtractor { pos: pos2, .. }) => { + tyenv.report_error( + pos, + "Duplicate external extractor definition".to_string(), + ); + tyenv.report_error( + *pos2, + "External extractor already defined".to_string(), + ); + continue; + } + Some(ExtractorKind::InternalExtractor { template }) => { + tyenv.report_error( + pos, + "Cannot define external extractor for term that already has an \ + internal extractor macro body defined" + .to_string(), + ); + tyenv.report_error( + template.pos(), + "Internal extractor macro body already defined".to_string(), + ); + continue; + } + }, + TermKind::EnumVariant { .. } => { tyenv.report_error( pos, - format!("Extractor defined on term of improper type '{}'", term.0), + format!("Cannot define extractor for enum variant '{}'", term.0), ); continue; } @@ -1058,7 +1247,8 @@ impl TermEnv { if let ast::Def::Decl(decl) = def { let sym = tyenv.intern_mut(&decl.term); let term = self.term_map[&sym]; - if self.terms[term.index()].is_declared() { + let term = &self.terms[term.index()]; + if !term.has_constructor() && !term.has_extractor() { tyenv.report_error( decl.pos, format!( @@ -1074,9 +1264,11 @@ impl TermEnv { fn translate_pattern( &self, tyenv: &mut TypeEnv, + rule_term: TermId, pat: &ast::Pattern, expected_ty: Option, bindings: &mut Bindings, + is_root: bool, ) -> Option<(Pattern, TypeId)> { log::trace!("translate_pattern: {:?}", pat); log::trace!("translate_pattern: bindings = {:?}", bindings); @@ -1135,9 +1327,11 @@ impl TermEnv { for subpat in subpats { let (subpat, ty) = unwrap_or_continue!(self.translate_pattern( tyenv, + rule_term, &*subpat, expected_ty, - bindings + bindings, + /* is_root = */ false, )); expected_ty = expected_ty.or(Some(ty)); children.push(subpat); @@ -1155,8 +1349,14 @@ impl TermEnv { pos, } => { // Do the subpattern first so we can resolve the type for sure. - let (subpat, ty) = - self.translate_pattern(tyenv, &*subpat, expected_ty, bindings)?; + let (subpat, ty) = self.translate_pattern( + tyenv, + rule_term, + &*subpat, + expected_ty, + bindings, + /* is_root = */ false, + )?; let name = tyenv.intern_mut(var); if bindings.vars.iter().any(|bv| bv.name == name) { @@ -1254,15 +1454,43 @@ impl TermEnv { let termdata = &self.terms[tid.index()]; match &termdata.kind { - &TermKind::EnumVariant { .. } => { + TermKind::Decl { + constructor_kind: Some(ConstructorKind::InternalConstructor), + .. + } if is_root && *tid == rule_term => { + // This is just the `(foo ...)` pseudo-pattern inside a + // `(rule (foo ...) ...)` form. Just keep checking the + // sub-patterns. for arg in args { - if let &ast::TermArgPattern::Expr(_) = arg { - tyenv.report_error(pos, format!("Term in pattern '{}' cannot have an injected expr, because it is an enum variant", sym.0)); + if let ast::TermArgPattern::Expr(e) = arg { + tyenv.report_error( + e.pos(), + "cannot use output-polarity expression with top-level rules" + .to_string(), + ); } } } - &TermKind::ExternalExtractor { - ref arg_polarity, .. + TermKind::EnumVariant { .. } => { + for arg in args { + if let &ast::TermArgPattern::Expr(_) = arg { + tyenv.report_error( + pos, + format!( + "Term in pattern '{}' cannot have an injected expr, because \ + it is an enum variant", + sym.0 + ) + ); + } + } + } + TermKind::Decl { + extractor_kind: + Some(ExtractorKind::ExternalExtractor { + ref arg_polarity, .. + }), + .. } => { for (arg, pol) in args.iter().zip(arg_polarity.iter()) { match (arg, pol) { @@ -1276,12 +1504,20 @@ impl TermEnv { } (_, &ArgPolarity::Output) => {} (&ast::TermArgPattern::Pattern(ref p), &ArgPolarity::Input) => { - tyenv.report_error(p.pos(), "Non-expression used in pattern but expression required for input-polarity extractor arg".to_string()); + tyenv.report_error( + p.pos(), + "Non-expression used in pattern but expression required for \ + input-polarity extractor arg" + .to_string() + ); } } } } - &TermKind::InternalExtractor { ref template } => { + TermKind::Decl { + extractor_kind: Some(ExtractorKind::InternalExtractor { ref template }), + .. + } => { // Expand the extractor macro! We create a map // from macro args to AST pattern trees and // then evaluate the template with these @@ -1291,7 +1527,12 @@ impl TermEnv { let sub_ast = match template_arg { &ast::TermArgPattern::Pattern(ref pat) => pat.clone(), &ast::TermArgPattern::Expr(_) => { - tyenv.report_error(pos, "Cannot expand an extractor macro with an expression in a macro argument".to_string()); + tyenv.report_error( + pos, + "Cannot expand an extractor macro with an expression in a \ + macro argument" + .to_string(), + ); return None; } }; @@ -1299,15 +1540,26 @@ impl TermEnv { } log::trace!("internal extractor macro args = {:?}", args); let pat = template.subst_macro_args(¯o_args[..])?; - return self.translate_pattern(tyenv, &pat, expected_ty, bindings); + return self.translate_pattern( + tyenv, + rule_term, + &pat, + expected_ty, + bindings, + /* is_root = */ false, + ); } - &TermKind::ExternalConstructor { .. } | &TermKind::InternalConstructor => { - // OK. - } - &TermKind::Declared => { + TermKind::Decl { + extractor_kind: None, + .. + } => { tyenv.report_error( pos, - format!("Declared but undefined term '{}' used", sym.0), + format!( + "Cannot use term '{}' that does not have a defined extractor in a \ + left-hand side pattern", + sym.0 + ), ); } } @@ -1319,6 +1571,7 @@ impl TermEnv { let arg_ty = unwrap_or_continue!(term.arg_tys.get(i).copied()); let (subpat, _) = unwrap_or_continue!(self.translate_pattern_term_arg( tyenv, + rule_term, pos, arg, Some(arg_ty), @@ -1336,6 +1589,7 @@ impl TermEnv { fn translate_pattern_term_arg( &self, tyenv: &mut TypeEnv, + rule_term: TermId, pos: Pos, pat: &ast::TermArgPattern, expected_ty: Option, @@ -1343,7 +1597,14 @@ impl TermEnv { ) -> Option<(TermArgPattern, TypeId)> { match pat { &ast::TermArgPattern::Pattern(ref pat) => { - let (subpat, ty) = self.translate_pattern(tyenv, pat, expected_ty, bindings)?; + let (subpat, ty) = self.translate_pattern( + tyenv, + rule_term, + pat, + expected_ty, + bindings, + /* is_root = */ false, + )?; Some((TermArgPattern::Pattern(subpat), ty)) } &ast::TermArgPattern::Expr(ref expr) => { diff --git a/cranelift/isle/isle_examples/construct-and-extract.isle b/cranelift/isle/isle_examples/construct-and-extract.isle new file mode 100644 index 0000000000..a0951edd41 --- /dev/null +++ b/cranelift/isle/isle_examples/construct-and-extract.isle @@ -0,0 +1,17 @@ +(type i32 (primitive i32)) + +(type B (enum (B (x i32) (y i32)))) + +;; `isub` has a constructor and extractor. +(decl isub (i32 i32) B) +(rule (isub x y) + (B.B x y)) +(extractor (isub x y) + (B.B x y)) + +;; `value_array_2` has both an external extractor and an external constructor. +(type Value (primitive Value)) +(type ValueArray2 extern (enum)) +(decl value_array_2 (Value Value) ValueArray2) +(extern extractor infallible value_array_2 unpack_value_array_2) +(extern constructor value_array_2 pack_value_array_2) From fe836a2302d5b7129666f004e623b0a5a999d329 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 11 Oct 2021 11:30:43 -0700 Subject: [PATCH 66/95] Yield source positions from lexer that have file-relative offsets They were previously all source texts concatenated-relative offsets, which would cause `miette` to error out when trying to find the line/col when displaying errors. Not good to have errors when displaying errors! Gets very confusing when trying to track down ISLE type errors and things. --- cranelift/isle/isle/src/lexer.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index b4b6bea3e4..a8f9f9d931 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -135,14 +135,14 @@ impl<'a> Lexer<'a> { Ok(l) } - /// Get the lexer's current file offset. - pub fn offset(&self) -> usize { - self.pos.offset - } - /// Get the lexer's current source position. pub fn pos(&self) -> Pos { - self.pos + Pos { + file: self.pos.file, + offset: self.pos.offset - self.file_starts[self.pos.file], + line: self.pos.line, + col: self.pos.file, + } } fn advance_pos(&mut self) { @@ -169,7 +169,7 @@ impl<'a> Lexer<'a> { self.filenames[pos.file].clone(), self.file_texts[pos.file].clone(), ), - span: miette::SourceSpan::from((pos.offset, 1)), + span: miette::SourceSpan::from((self.pos().offset, 1)), } } @@ -208,7 +208,7 @@ impl<'a> Lexer<'a> { return Ok(None); } - let char_pos = self.pos; + let char_pos = self.pos(); match self.buf[self.pos.offset] { b'(' => { self.advance_pos(); @@ -228,7 +228,7 @@ impl<'a> Lexer<'a> { } c if is_sym_first_char(c) => { let start = self.pos.offset; - let start_pos = self.pos; + let start_pos = self.pos(); while self.pos.offset < self.buf.len() && is_sym_other_char(self.buf[self.pos.offset]) { @@ -241,7 +241,7 @@ impl<'a> Lexer<'a> { Ok(Some((start_pos, Token::Symbol(s.to_string())))) } c if (c >= b'0' && c <= b'9') || c == b'-' => { - let start_pos = self.pos; + let start_pos = self.pos(); let neg = if c == b'-' { self.advance_pos(); true From efe7df7f45f585278feb41a13ce73d3916d24b4c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 11 Oct 2021 11:32:49 -0700 Subject: [PATCH 67/95] Keep track of where primitives are defined for better error messages --- cranelift/isle/isle/src/codegen.rs | 4 ++-- cranelift/isle/isle/src/sema.rs | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 32e291a4dc..ca983d922a 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -173,7 +173,7 @@ impl<'a> Codegen<'a> { fn type_name(&self, typeid: TypeId, by_ref: bool) -> String { match &self.typeenv.types[typeid.index()] { - &Type::Primitive(_, sym) => self.typeenv.syms[sym.index()].clone(), + &Type::Primitive(_, sym, _) => self.typeenv.syms[sym.index()].clone(), &Type::Enum { name, .. } => { let r = if by_ref { "&" } else { "" }; format!("{}{}", r, self.typeenv.syms[name.index()]) @@ -229,7 +229,7 @@ impl<'a> Codegen<'a> { fn const_int(&self, val: i64, ty: TypeId) -> String { let is_bool = match &self.typeenv.types[ty.index()] { - &Type::Primitive(_, name) => &self.typeenv.syms[name.index()] == "bool", + &Type::Primitive(_, name, _) => &self.typeenv.syms[name.index()] == "bool", _ => unreachable!(), }; if is_bool { diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index ae87981b65..9b6e8dfde7 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -94,7 +94,7 @@ pub enum Type { /// /// These are always defined externally, and we allow literals of these /// types to pass through from ISLE source code to the emitted Rust code. - Primitive(TypeId, Sym), + Primitive(TypeId, Sym, Pos), /// A sum type. /// @@ -120,7 +120,14 @@ impl Type { /// Get the name of this `Type`. pub fn name<'a>(&self, tyenv: &'a TypeEnv) -> &'a str { match self { - Self::Primitive(_, name) | Self::Enum { name, .. } => &tyenv.syms[name.index()], + Self::Primitive(_, name, _) | Self::Enum { name, .. } => &tyenv.syms[name.index()], + } + } + + /// Get the position where this type was defined. + pub fn pos(&self) -> Pos { + match self { + Self::Primitive(_, _, pos) | Self::Enum { pos, .. } => *pos, } } @@ -551,13 +558,20 @@ impl TypeEnv { &ast::Def::Type(ref td) => { let tid = TypeId(tyenv.type_map.len()); let name = tyenv.intern_mut(&td.name); - if tyenv.type_map.contains_key(&name) { + + if let Some(existing) = tyenv.type_map.get(&name).copied() { tyenv.report_error( td.pos, - format!("Type name defined more than once: '{}'", td.name.0), + format!("Type with name '{}' defined more than once", td.name.0), + ); + let pos = unwrap_or_continue!(tyenv.types.get(existing.index())).pos(); + tyenv.report_error( + pos, + format!("Type with name '{}' already defined here", td.name.0), ); continue; } + tyenv.type_map.insert(name, tid); } _ => {} @@ -619,7 +633,7 @@ impl TypeEnv { let name = self.intern(&ty.name).unwrap(); match &ty.ty { &ast::TypeValue::Primitive(ref id, ..) => { - Some(Type::Primitive(tid, self.intern_mut(id))) + Some(Type::Primitive(tid, self.intern_mut(id), ty.pos)) } &ast::TypeValue::Enum(ref ty_variants, ..) => { let mut variants = vec![]; From 0e02ec4cbac32c45693b80a477c3538cafc5e514 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 12 Oct 2021 11:04:17 -0700 Subject: [PATCH 68/95] Use `BTree{Map,Set}` instead of `Hash{Map,Set}` to ensure deterministic output Fixes #8 --- cranelift/isle/isle/src/codegen.rs | 12 +-- cranelift/isle/isle/src/ir.rs | 8 +- cranelift/isle/isle/src/sema.rs | 121 ++++++++++++++++------------- cranelift/isle/isle/src/trie.rs | 12 +-- 4 files changed, 82 insertions(+), 71 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index ca983d922a..ba168803bb 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -4,11 +4,11 @@ use crate::ir::{ExprInst, InstId, PatternInst, Value}; use crate::sema::ExternalSig; use crate::sema::{TermEnv, TermId, Type, TypeEnv, TypeId, Variant}; use crate::trie::{TrieEdge, TrieNode, TrieSymbol}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; /// Emit Rust source code for the given type and term environments. -pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv, tries: &HashMap) -> String { +pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv, tries: &BTreeMap) -> String { Codegen::compile(typeenv, termenv, tries).generate_rust() } @@ -16,20 +16,20 @@ pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv, tries: &HashMap { typeenv: &'a TypeEnv, termenv: &'a TermEnv, - functions_by_term: &'a HashMap, + functions_by_term: &'a BTreeMap, } #[derive(Clone, Debug, Default)] struct BodyContext { /// For each value: (is_ref, ty). - values: HashMap, + values: BTreeMap, } impl<'a> Codegen<'a> { fn compile( typeenv: &'a TypeEnv, termenv: &'a TermEnv, - tries: &'a HashMap, + tries: &'a BTreeMap, ) -> Codegen<'a> { Codegen { typeenv, @@ -682,7 +682,7 @@ impl<'a> Codegen<'a> { let mut i = 0; while i < edges.len() { let mut last = i; - let mut adjacent_variants = HashSet::new(); + let mut adjacent_variants = BTreeSet::new(); let mut adjacent_variant_input = None; log::trace!("edge: {:?}", edges[i]); while last < edges.len() { diff --git a/cranelift/isle/isle/src/ir.rs b/cranelift/isle/isle/src/ir.rs index cd197f7946..b3daa2e7b3 100644 --- a/cranelift/isle/isle/src/ir.rs +++ b/cranelift/isle/isle/src/ir.rs @@ -2,7 +2,7 @@ use crate::lexer::Pos; use crate::sema::*; -use std::collections::HashMap; +use std::collections::BTreeMap; declare_id!( /// The id of an instruction in a `PatternSequence`. @@ -333,7 +333,7 @@ impl PatternSequence { typeenv: &TypeEnv, termenv: &TermEnv, pat: &Pattern, - vars: &mut HashMap, + vars: &mut BTreeMap, ) { match pat { &Pattern::BindPattern(_ty, var, ref subpat) => { @@ -578,7 +578,7 @@ impl ExprSequence { typeenv: &TypeEnv, termenv: &TermEnv, expr: &Expr, - vars: &HashMap, + vars: &BTreeMap, ) -> Value { log::trace!("gen_expr: expr {:?}", expr); match expr { @@ -651,7 +651,7 @@ pub fn lower_rule( expr_seq.pos = termenv.rules[rule.index()].pos; let ruledata = &termenv.rules[rule.index()]; - let mut vars = HashMap::new(); + let mut vars = BTreeMap::new(); let root_term = ruledata .lhs .root_term() diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 9b6e8dfde7..3e650ad3f6 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -16,8 +16,8 @@ use crate::ast; use crate::error::*; use crate::lexer::Pos; -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::sync::Arc; declare_id!( @@ -70,7 +70,7 @@ pub struct TypeEnv { pub syms: Vec, /// Map of already-interned symbol names to their `Sym` ids. - pub sym_map: HashMap, + pub sym_map: BTreeMap, /// Arena of type definitions. /// @@ -78,10 +78,10 @@ pub struct TypeEnv { pub types: Vec, /// A map from a type name symbol to its `TypeId`. - pub type_map: HashMap, + pub type_map: BTreeMap, /// The types of constant symbols. - pub const_types: HashMap, + pub const_types: BTreeMap, /// Type errors that we've found so far during type checking. pub errors: Vec, @@ -181,7 +181,7 @@ pub struct TermEnv { pub terms: Vec, /// A map from am interned `Term`'s name to its `TermId`. - pub term_map: HashMap, + pub term_map: BTreeMap, /// Arena of interned rules defined in this ISLE program. /// @@ -544,10 +544,10 @@ impl TypeEnv { filenames: defs.filenames.clone(), file_texts: defs.file_texts.clone(), syms: vec![], - sym_map: HashMap::new(), + sym_map: BTreeMap::new(), types: vec![], - type_map: HashMap::new(), - const_types: HashMap::new(), + type_map: BTreeMap::new(), + const_types: BTreeMap::new(), errors: vec![], }; @@ -753,7 +753,7 @@ impl TermEnv { pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result { let mut env = TermEnv { terms: vec![], - term_map: HashMap::new(), + term_map: BTreeMap::new(), rules: vec![], }; @@ -935,7 +935,7 @@ impl TermEnv { } fn collect_extractor_templates(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { - let mut extractor_call_graph = HashMap::new(); + let mut extractor_call_graph = BTreeMap::new(); for def in &defs.defs { if let &ast::Def::Extractor(ref ext) = def { @@ -954,7 +954,7 @@ impl TermEnv { let template = ext.template.make_macro_template(&ext.args[..]); log::trace!("extractor def: {:?} becomes template {:?}", def, template); - let mut callees = HashSet::new(); + let mut callees = BTreeSet::new(); template.terms(&mut |pos, t| { let t = tyenv.intern_mut(t); callees.insert(t); @@ -1008,7 +1008,7 @@ impl TermEnv { } // Check for cycles in the extractor call graph. - let mut seen = HashSet::new(); + let mut seen = BTreeSet::new(); let mut stack = vec![]; 'outer: for root in extractor_call_graph.keys().copied() { seen.clear(); @@ -1828,7 +1828,7 @@ impl TermEnv { mod test { use super::*; use crate::ast::Ident; - use crate::lexer::Lexer; + use crate::lexer::{Lexer, Pos}; use crate::parser::parse; #[test] @@ -1868,51 +1868,62 @@ mod test { assert_eq!(tyenv.type_map.get(&sym_u32).unwrap(), &TypeId(0)); assert_eq!(tyenv.type_map.get(&sym_a).unwrap(), &TypeId(1)); - assert_eq!( - tyenv.types, - vec![ - Type::Primitive(TypeId(0), sym_u32), - Type::Enum { - name: sym_a, - id: TypeId(1), - is_extern: true, - variants: vec![ - Variant { - name: sym_b, - fullname: sym_a_b, - id: VariantId(0), - fields: vec![ - Field { - name: sym_f1, - id: FieldId(0), - ty: TypeId(0), - }, - Field { - name: sym_f2, - id: FieldId(1), - ty: TypeId(0), - }, - ], - }, - Variant { - name: sym_c, - fullname: sym_a_c, - id: VariantId(1), - fields: vec![Field { + let expected_types = vec![ + Type::Primitive( + TypeId(0), + sym_u32, + Pos { + file: 0, + offset: 19, + line: 2, + col: 0, + }, + ), + Type::Enum { + name: sym_a, + id: TypeId(1), + is_extern: true, + variants: vec![ + Variant { + name: sym_b, + fullname: sym_a_b, + id: VariantId(0), + fields: vec![ + Field { name: sym_f1, id: FieldId(0), ty: TypeId(0), - },], - }, - ], - pos: Pos { - file: 0, - offset: 58, - line: 3, - col: 18, + }, + Field { + name: sym_f2, + id: FieldId(1), + ty: TypeId(0), + }, + ], }, + Variant { + name: sym_c, + fullname: sym_a_c, + id: VariantId(1), + fields: vec![Field { + name: sym_f1, + id: FieldId(0), + ty: TypeId(0), + }], + }, + ], + pos: Pos { + file: 0, + offset: 58, + line: 3, + col: 0, }, - ] - ); + }, + ]; + + assert_eq!(tyenv.types.len(), expected_types.len()); + for (i, (actual, expected)) in tyenv.types.iter().zip(&expected_types).enumerate() { + assert_eq!(expected, actual, "`{}`th type is not equal!", i); + } } } diff --git a/cranelift/isle/isle/src/trie.rs b/cranelift/isle/isle/src/trie.rs index 62c37172ea..768212b277 100644 --- a/cranelift/isle/isle/src/trie.rs +++ b/cranelift/isle/isle/src/trie.rs @@ -2,10 +2,10 @@ use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence}; use crate::sema::{RuleId, TermEnv, TermId, TypeEnv}; -use std::collections::HashMap; +use std::collections::BTreeMap; /// Construct the tries for each term. -pub fn build_tries(typeenv: &TypeEnv, termenv: &TermEnv) -> HashMap { +pub fn build_tries(typeenv: &TypeEnv, termenv: &TermEnv) -> BTreeMap { let mut builder = TermFunctionsBuilder::new(typeenv, termenv); builder.build(); log::trace!("builder: {:?}", builder); @@ -511,7 +511,7 @@ impl TermFunctionBuilder { struct TermFunctionsBuilder<'a> { typeenv: &'a TypeEnv, termenv: &'a TermEnv, - builders_by_term: HashMap, + builders_by_term: BTreeMap, } impl<'a> TermFunctionsBuilder<'a> { @@ -519,7 +519,7 @@ impl<'a> TermFunctionsBuilder<'a> { log::trace!("typeenv: {:?}", typeenv); log::trace!("termenv: {:?}", termenv); Self { - builders_by_term: HashMap::new(), + builders_by_term: BTreeMap::new(), typeenv, termenv, } @@ -546,12 +546,12 @@ impl<'a> TermFunctionsBuilder<'a> { } } - fn finalize(self) -> HashMap { + fn finalize(self) -> BTreeMap { let functions_by_term = self .builders_by_term .into_iter() .map(|(term, builder)| (term, builder.trie)) - .collect::>(); + .collect::>(); functions_by_term } } From e015a49270e3d3993eed088c6fcbcb45ff424ad0 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 13 Oct 2021 13:46:45 -0700 Subject: [PATCH 69/95] Do not use 1-member tuples in external constructor/extractor trait methods Just use the single member type directly. --- cranelift/isle/isle/src/codegen.rs | 36 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index ba168803bb..5f4619e479 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -78,22 +78,24 @@ impl<'a> Codegen<'a> { fn generate_trait_sig(&self, code: &mut String, indent: &str, sig: &ExternalSig) { writeln!( code, - "{}fn {}(&mut self, {}) -> {}({},){};", - indent, - sig.func_name, - sig.param_tys + "{indent}fn {name}(&mut self, {params}) -> {opt_start}{open_paren}{rets}{close_paren}{opt_end};", + indent = indent, + name = sig.func_name, + params = sig.param_tys .iter() .enumerate() .map(|(i, &ty)| format!("arg{}: {}", i, self.type_name(ty, /* by_ref = */ true))) .collect::>() .join(", "), - if sig.infallible { "" } else { "Option<" }, - sig.ret_tys + opt_start = if sig.infallible { "" } else { "Option<" }, + open_paren = if sig.ret_tys.len() != 1 { "(" } else { "" }, + rets = sig.ret_tys .iter() .map(|&ty| self.type_name(ty, /* by_ref = */ false)) .collect::>() .join(", "), - if sig.infallible { "" } else { ">" }, + close_paren = if sig.ret_tys.len() != 1 { ")" } else { "" }, + opt_end = if sig.infallible { "" } else { ">" }, ) .unwrap(); } @@ -386,8 +388,12 @@ impl<'a> Codegen<'a> { .. } => { let mut input_exprs = vec![]; - for (input_value, _) in inputs { - let value_expr = self.value_by_val(input_value, ctx); + for (input_value, input_ty) in inputs { + let value_expr = if self.typeenv.types[input_ty.index()].is_prim() { + self.value_by_val(input_value, ctx) + } else { + self.value_by_ref(input_value, ctx) + }; input_exprs.push(value_expr); } @@ -555,11 +561,13 @@ impl<'a> Codegen<'a> { if infallible { writeln!( code, - "{}let ({},) = {}(ctx, {});", - indent, - output_binders.join(", "), - sig.full_name, - input_values.join(", "), + "{indent}let {open_paren}{vars}{close_paren} = {name}(ctx, {args});", + indent = indent, + open_paren = if output_binders.len() == 1 { "" } else { "(" }, + vars = output_binders.join(", "), + close_paren = if output_binders.len() == 1 { "" } else { ")" }, + name = sig.full_name, + args = input_values.join(", "), ) .unwrap(); true From 0c6956376ba63b76a2f4fb6b4aad3b18b2d35573 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 22 Oct 2021 14:37:20 -0700 Subject: [PATCH 70/95] Allow irrefutable let patterns in the generated code --- cranelift/isle/isle/src/codegen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 5f4619e479..1b63ea3fc9 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -71,6 +71,7 @@ impl<'a> Codegen<'a> { "#![allow(unused_imports, unused_variables, non_snake_case)]" ) .unwrap(); + writeln!(code, "#[allow(irrefutable_let_patterns)]").unwrap(); writeln!(code, "\nuse super::*; // Pulls in all external types.").unwrap(); } From 7fab7c5eab1033e99e444a2ed12953d572e53044 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 22 Oct 2021 14:37:43 -0700 Subject: [PATCH 71/95] Emit `if let`s against `Option` instead of `Option<(x,)>` --- cranelift/isle/isle/src/codegen.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 1b63ea3fc9..844d5c8b49 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -575,11 +575,13 @@ impl<'a> Codegen<'a> { } else { writeln!( code, - "{}if let Some(({},)) = {}(ctx, {}) {{", - indent, - output_binders.join(", "), - sig.full_name, - input_values.join(", "), + "{indent}if let Some({open_paren}{vars}{close_paren}) = {name}(ctx, {args}) {{", + indent = indent, + open_paren = if output_binders.len() == 1 { "" } else { "(" }, + vars = output_binders.join(", "), + close_paren = if output_binders.len() == 1 { "" } else { ")" }, + name = sig.full_name, + args = input_values.join(", "), ) .unwrap(); false From 0411b208713ab44f1f4683db742e982e7f4ca45b Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 22 Oct 2021 14:40:26 -0700 Subject: [PATCH 72/95] Fix `allow` declaration --- cranelift/isle/isle/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 844d5c8b49..6968302286 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -71,7 +71,7 @@ impl<'a> Codegen<'a> { "#![allow(unused_imports, unused_variables, non_snake_case)]" ) .unwrap(); - writeln!(code, "#[allow(irrefutable_let_patterns)]").unwrap(); + writeln!(code, "#![allow(irrefutable_let_patterns)]").unwrap(); writeln!(code, "\nuse super::*; // Pulls in all external types.").unwrap(); } From 6c6b7a2b787fac372b014395d325aac2d66d24d0 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 26 Oct 2021 13:33:01 -0700 Subject: [PATCH 73/95] Add a method to pretty-print a trie --- cranelift/isle/isle/src/trie.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cranelift/isle/isle/src/trie.rs b/cranelift/isle/isle/src/trie.rs index 768212b277..5acb08b0ab 100644 --- a/cranelift/isle/isle/src/trie.rs +++ b/cranelift/isle/isle/src/trie.rs @@ -473,6 +473,36 @@ impl TrieNode { } } } + + /// Get a pretty-printed version of this trie, for debugging. + pub fn pretty(&self) -> String { + let mut s = String::new(); + pretty_rec(&mut s, self, ""); + return s; + + fn pretty_rec(s: &mut String, node: &TrieNode, indent: &str) { + match node { + TrieNode::Decision { edges } => { + s.push_str(indent); + s.push_str("TrieNode::Decision:\n"); + + let new_indent = indent.to_owned() + " "; + for edge in edges { + s.push_str(indent); + s.push_str(&format!( + " edge: range = {:?}, symbol: {:?}\n", + edge.range, edge.symbol + )); + pretty_rec(s, &edge.node, &new_indent); + } + } + TrieNode::Empty | TrieNode::Leaf { .. } => { + s.push_str(indent); + s.push_str(&format!("{:?}\n", node)); + } + } + } + } } /// Builder context for one function in generated code corresponding From 037db8aab695d3f6fb1c8818ee4520bd9baf220d Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 26 Oct 2021 14:32:22 -0700 Subject: [PATCH 74/95] Make sure that infallible match operations come after fallible ones Eventually we will probably want to sort by a partial ordering based on pattern subsumption to break ties between rules with the same priority. This local heuristic of making sure infallibilty comes last is good enough for now and handles the cases we care about thus far. Fixes #10 --- cranelift/isle/isle/src/codegen.rs | 23 +++++++++++---- cranelift/isle/isle/src/ir.rs | 45 +++++++++++++++++------------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 6968302286..103c58eda7 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -651,7 +651,7 @@ impl<'a> Codegen<'a> { indent: &str, ctx: &mut BodyContext, ) -> bool { - log::trace!("generate_body: trie {:?}", trie); + log::trace!("generate_body:\n{}", trie.pretty()); let mut returned = false; match trie { &TrieNode::Empty => {} @@ -692,10 +692,16 @@ impl<'a> Codegen<'a> { let mut i = 0; while i < edges.len() { + // Gather adjacent match variants so that we can turn these + // into a `match` rather than a sequence of `if let`s. let mut last = i; let mut adjacent_variants = BTreeSet::new(); let mut adjacent_variant_input = None; - log::trace!("edge: {:?}", edges[i]); + log::trace!( + "edge: range = {:?}, symbol = {:?}", + edges[i].range, + edges[i].symbol + ); while last < edges.len() { match &edges[last].symbol { &TrieSymbol::Match { @@ -719,11 +725,11 @@ impl<'a> Codegen<'a> { } } - // edges[i..last] is a run of adjacent - // MatchVariants (possibly an empty one). Only use - // a `match` form if there are at least two - // adjacent options. + // Now `edges[i..last]` is a run of adjacent `MatchVariants` + // (possibly an empty one). Only use a `match` form if there + // are at least two adjacent options. if last - i > 1 { + eprintln!("FITZGEN: generating body matches"); self.generate_body_matches(code, depth, &edges[i..last], indent, ctx); i = last; continue; @@ -738,8 +744,13 @@ impl<'a> Codegen<'a> { match symbol { &TrieSymbol::EndOfMatch => { returned = self.generate_body(code, depth + 1, node, indent, ctx); + eprintln!( + "FITZGEN: generated end-of-match; returned = {:?}", + returned + ); } &TrieSymbol::Match { ref op } => { + eprintln!("FITZGEN: generating [if] let"); let id = InstId(depth); let infallible = self.generate_pattern_inst(code, id, op, indent, ctx); diff --git a/cranelift/isle/isle/src/ir.rs b/cranelift/isle/isle/src/ir.rs index b3daa2e7b3..caa0090864 100644 --- a/cranelift/isle/isle/src/ir.rs +++ b/cranelift/isle/isle/src/ir.rs @@ -31,15 +31,6 @@ pub enum Value { /// A single Pattern instruction. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PatternInst { - /// Get the Nth input argument, which corresponds to the Nth field - /// of the root term. - Arg { - /// The index of the argument to get. - index: usize, - /// The type of the argument. - ty: TypeId, - }, - /// Match a value as equal to another value. Produces no values. MatchEqual { /// The first value. @@ -83,6 +74,21 @@ pub enum PatternInst { variant: VariantId, }, + /// Evaluate an expression and provide the given value as the result of this + /// match instruction. The expression has access to the pattern-values up to + /// this point in the sequence. + Expr { + /// The expression to evaluate. + seq: ExprSequence, + /// The value produced by the expression. + output: Value, + /// The type of the output value. + output_ty: TypeId, + }, + + // NB: this has to come second-to-last, because it might be infallible, for + // the same reasons that `Arg` has to be last. + // /// Invoke an extractor, taking the given values as input (the first is the /// value to extract, the other are the `Input`-polarity extractor args) and /// producing an output value for each `Output`-polarity extractor arg. @@ -99,16 +105,17 @@ pub enum PatternInst { infallible: bool, }, - /// Evaluate an expression and provide the given value as the result of this - /// match instruction. The expression has access to the pattern-values up to - /// this point in the sequence. - Expr { - /// The expression to evaluate. - seq: ExprSequence, - /// The value produced by the expression. - output: Value, - /// The type of the output value. - output_ty: TypeId, + // NB: This has to go last, since it is infallible, so that when we sort + // edges in the trie, we visit infallible edges after first having tried the + // more-specific fallible options. + // + /// Get the Nth input argument, which corresponds to the Nth field + /// of the root term. + Arg { + /// The index of the argument to get. + index: usize, + /// The type of the argument. + ty: TypeId, }, } From 22e18a9d985173cfe4f5a5bf59a5fab960a48c6c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 26 Oct 2021 14:35:04 -0700 Subject: [PATCH 75/95] Remove `eprintln!`s that shouldn't have been committed --- cranelift/isle/isle/src/codegen.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs index 103c58eda7..74874b6a1d 100644 --- a/cranelift/isle/isle/src/codegen.rs +++ b/cranelift/isle/isle/src/codegen.rs @@ -729,7 +729,6 @@ impl<'a> Codegen<'a> { // (possibly an empty one). Only use a `match` form if there // are at least two adjacent options. if last - i > 1 { - eprintln!("FITZGEN: generating body matches"); self.generate_body_matches(code, depth, &edges[i..last], indent, ctx); i = last; continue; @@ -744,13 +743,8 @@ impl<'a> Codegen<'a> { match symbol { &TrieSymbol::EndOfMatch => { returned = self.generate_body(code, depth + 1, node, indent, ctx); - eprintln!( - "FITZGEN: generated end-of-match; returned = {:?}", - returned - ); } &TrieSymbol::Match { ref op } => { - eprintln!("FITZGEN: generating [if] let"); let id = InstId(depth); let infallible = self.generate_pattern_inst(code, id, op, indent, ctx); From 8f5dffab72c0824ee3738d1cdbb846014615d392 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 1 Nov 2021 13:55:28 -0700 Subject: [PATCH 76/95] Add better error messages for duplicate term declarations --- cranelift/isle/isle/src/sema.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 3e650ad3f6..0aae69a2b9 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -198,6 +198,8 @@ pub struct TermEnv { pub struct Term { /// This term's id. pub id: TermId, + /// The source position where this term was declared. + pub decl_pos: Pos, /// The name of this term. pub name: Sym, /// The parameter types to this term. @@ -776,11 +778,15 @@ impl TermEnv { &ast::Def::Decl(ref decl) => { let tid = TermId(self.terms.len()); let name = tyenv.intern_mut(&decl.term); - if self.term_map.contains_key(&name) { + if let Some(tid) = self.term_map.get(&name) { tyenv.report_error( decl.pos, format!("Duplicate decl for '{}'", decl.term.0), ); + tyenv.report_error( + self.terms[tid.index()].decl_pos, + format!("Duplicate decl for '{}'", decl.term.0), + ); } self.term_map.insert(name, tid); @@ -817,6 +823,7 @@ impl TermEnv { self.terms.push(Term { id: tid, + decl_pos: decl.pos, name, arg_tys, ret_ty, @@ -855,6 +862,7 @@ impl TermEnv { let ret_ty = id; self.terms.push(Term { id: tid, + decl_pos: pos, name: variant.fullname, arg_tys, ret_ty, From bd8c7faf1222320560083664b259a1070884287c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 2 Nov 2021 15:15:07 -0700 Subject: [PATCH 77/95] Add support for hex integer literals --- cranelift/isle/isle/src/lexer.rs | 47 +++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index a8f9f9d931..e9a4e7b063 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -248,21 +248,48 @@ impl<'a> Lexer<'a> { } else { false }; - let mut num = 0_i64; - while self.pos.offset < self.buf.len() - && (self.buf[self.pos.offset] >= b'0' && self.buf[self.pos.offset] <= b'9') + + let mut radix = 10; + + // Check for hex literals. + if self.buf.get(self.pos.offset).copied() == Some(b'0') + && self.buf.get(self.pos.offset + 1).copied() == Some(b'x') { - let base = num - .checked_mul(10) - .ok_or_else(|| self.error(start_pos, "integer literal too large"))?; - num = base - .checked_add((self.buf[self.pos.offset] - b'0') as i64) - .ok_or_else(|| self.error(start_pos, "integer literal too large"))?; self.advance_pos(); + self.advance_pos(); + radix = 16; } + // Find the range in the buffer for this integer literal. We'll + // pass this range to `i64::from_str_radix` to do the actual + // string-to-integer conversion. + let start_offset = self.pos.offset; + while self.pos.offset < self.buf.len() + && ((radix == 10 + && self.buf[self.pos.offset] >= b'0' + && self.buf[self.pos.offset] <= b'9') + || (radix == 16 + && ((self.buf[self.pos.offset] >= b'0' + && self.buf[self.pos.offset] <= b'9') + || (self.buf[self.pos.offset] >= b'a' + && self.buf[self.pos.offset] <= b'f') + || (self.buf[self.pos.offset] >= b'A' + && self.buf[self.pos.offset] <= b'F')))) + { + self.advance_pos(); + } + let end_offset = self.pos.offset; + + let num = i64::from_str_radix( + std::str::from_utf8(&self.buf[start_offset..end_offset]).unwrap(), + radix, + ) + .map_err(|e| self.error(start_pos, e.to_string()))?; + let tok = if neg { - Token::Int(-num) + Token::Int(num.checked_neg().ok_or_else(|| { + self.error(start_pos, "integer literal cannot fit in i64") + })?) } else { Token::Int(num) }; From e23564700da8eec36910d885cffff739ba3fc8b9 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 4 Nov 2021 13:30:56 -0700 Subject: [PATCH 78/95] Add myself as a second author --- cranelift/isle/isle/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index dec3145407..0e684bc56c 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "isle" version = "0.1.0" -authors = ["Chris Fallin "] +authors = ["Chris Fallin ", "Nick Fitzgerald "] edition = "2018" license = "Apache-2.0 WITH LLVM-exception" From 4c61c96c3ff921a69c715bce0ccacf441d3e6ecc Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 4 Nov 2021 13:33:26 -0700 Subject: [PATCH 79/95] Add Cargo.toml metadata --- cranelift/isle/isle/Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 0e684bc56c..5c7acee3a4 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -1,9 +1,12 @@ [package] -name = "isle" -version = "0.1.0" authors = ["Chris Fallin ", "Nick Fitzgerald "] +description = "ISLE: Instruction Selection and Lowering Expressions. A domain-specific language for instruction selection in Cranelift." edition = "2018" license = "Apache-2.0 WITH LLVM-exception" +name = "isle" +readme = "../README.md" +repository = "https://github.com/cfallin.isle" +version = "0.1.0" [dependencies] log = "0.4" From f2276995362c784a8f64508fc17fd6ef5f581845 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Thu, 11 Nov 2021 16:00:12 -0800 Subject: [PATCH 80/95] Make ISLE part of the root Cargo workspace. --- Cargo.lock | 169 +++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 + cranelift/isle/Cargo.toml | 6 -- 3 files changed, 159 insertions(+), 18 deletions(-) delete mode 100644 cranelift/isle/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index aa5d349c9a..2f8b1a4333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,7 +462,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -1094,6 +1094,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "log", +] + [[package]] name = "errno" version = "0.2.7" @@ -1412,6 +1421,42 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + +[[package]] +name = "isle" +version = "0.1.0" +dependencies = [ + "log", + "miette", + "thiserror", +] + +[[package]] +name = "isle-fuzz" +version = "0.0.0" +dependencies = [ + "env_logger 0.9.0", + "isle", + "libfuzzer-sys", + "log", +] + +[[package]] +name = "islec" +version = "0.1.0" +dependencies = [ + "env_logger 0.8.3", + "isle", + "log", + "miette", + "structopt", +] + [[package]] name = "itertools" version = "0.9.0" @@ -1598,6 +1643,36 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" +[[package]] +name = "miette" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec47e61dc212c43f44dcd1f2841ccba79c6ec10da357cab7a7859b5f87bd27a9" +dependencies = [ + "atty", + "backtrace", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "term_size", + "textwrap 0.14.2", + "thiserror", +] + +[[package]] +name = "miette-derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0f0b6f999b9a9f7e86322125583a437cf015054b7aaa9926dff0ff13005b7e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1801,9 +1876,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "oorandom" @@ -1855,6 +1930,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "owo-colors" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a61765925aec40abdb23812a3a1a01fafc6ffb9da22768b2ce665a9e84e527c" + [[package]] name = "p256" version = "0.9.0" @@ -2664,6 +2745,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "souper-ir" version = "2.1.0" @@ -2708,9 +2795,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" dependencies = [ "clap", "lazy_static", @@ -2719,9 +2806,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error", @@ -2736,6 +2823,34 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +[[package]] +name = "supports-color" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" +dependencies = [ + "atty", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" +dependencies = [ + "atty", +] + +[[package]] +name = "supports-unicode" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" +dependencies = [ + "atty", +] + [[package]] name = "syn" version = "1.0.72" @@ -2802,6 +2917,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -2850,19 +2975,30 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.25" +name = "textwrap" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.25" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -2989,6 +3125,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + [[package]] name = "unicode-segmentation" version = "1.7.1" diff --git a/Cargo.toml b/Cargo.toml index 5f0565abb3..e6a6af12d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,8 @@ opt-level = 0 resolver = '2' members = [ "cranelift", + "cranelift/isle/fuzz", + "cranelift/isle/islec", "cranelift/serde", "crates/bench-api", "crates/c-api", diff --git a/cranelift/isle/Cargo.toml b/cranelift/isle/Cargo.toml deleted file mode 100644 index d1cf4b88ec..0000000000 --- a/cranelift/isle/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[workspace] -members = [ - "./fuzz", - "./isle", - "./islec", -] From d377b665c6ec2d6ad190c1e501e98123180a0a4f Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 12 Oct 2021 17:11:58 -0700 Subject: [PATCH 81/95] Initial ISLE integration with the x64 backend On the build side, this commit introduces two things: 1. The automatic generation of various ISLE definitions for working with CLIF. Specifically, it generates extern type definitions for clif opcodes and the clif instruction data `enum`, as well as extractors for matching each clif instructions. This happens inside the `cranelift-codegen-meta` crate. 2. The compilation of ISLE DSL sources to Rust code, that can be included in the main `cranelift-codegen` compilation. Next, this commit introduces the integration glue code required to get ISLE-generated Rust code hooked up in clif-to-x64 lowering. When lowering a clif instruction, we first try to use the ISLE code path. If it succeeds, then we are done lowering this instruction. If it fails, then we proceed along the existing hand-written code path for lowering. Finally, this commit ports many lowering rules over from hand-written, open-coded Rust to ISLE. In the process of supporting ISLE, this commit also makes the x64 `Inst` capable of expressing SSA by supporting 3-operand forms for all of the existing instructions that only have a 2-operand form encoding: dst = src1 op src2 Rather than only the typical x86-64 2-operand form: dst = dst op src This allows `MachInst` to be in SSA form, since `dst` and `src1` are disentangled. ("3-operand" and "2-operand" are a little bit of a misnomer since not all operations are binary operations, but we do the same thing for, e.g., unary operations by disentangling the sole operand from the result.) There are two motivations for this change: 1. To allow ISLE lowering code to have value-equivalence semantics. We want ISLE lowering to translate a CLIF expression that evaluates to some value into a `MachInst` expression that evaluates to the same value. We want both the lowering itself and the resulting `MachInst` to be pure and referentially transparent. This is both a nice paradigm for compiler writers that are authoring and maintaining lowering rules and is a prerequisite to any sort of formal verification of our lowering rules in the future. 2. Better align `MachInst` with `regalloc2`'s API, which requires that the input be in SSA form. --- .gitattributes | 7 + .github/workflows/main.yml | 15 + Cargo.lock | 2 + cranelift/codegen/Cargo.toml | 5 + cranelift/codegen/build.rs | 116 +- cranelift/codegen/meta/Cargo.toml | 3 + cranelift/codegen/meta/src/gen_inst.rs | 254 ++ cranelift/codegen/meta/src/lib.rs | 6 +- cranelift/codegen/meta/src/srcgen.rs | 1 + cranelift/codegen/src/clif.isle | 1635 ++++++++ cranelift/codegen/src/isa/x64/inst.isle | 933 +++++ cranelift/codegen/src/isa/x64/inst/args.rs | 26 +- cranelift/codegen/src/isa/x64/inst/emit.rs | 166 +- .../codegen/src/isa/x64/inst/emit_tests.rs | 111 +- cranelift/codegen/src/isa/x64/inst/mod.rs | 741 +++- cranelift/codegen/src/isa/x64/lower.isle | 890 +++++ cranelift/codegen/src/isa/x64/lower.rs | 675 +--- cranelift/codegen/src/isa/x64/lower/isle.rs | 428 ++ .../src/isa/x64/lower/isle/generated_code.rs | 3496 +++++++++++++++++ cranelift/codegen/src/isle.rs | 22 + cranelift/codegen/src/machinst/lower.rs | 195 +- cranelift/codegen/src/prelude.isle | 202 + cranelift/entity/src/list.rs | 4 +- .../filetests/filetests/isa/x64/i128.clif | 174 +- cranelift/isle/fuzz/README.md | 4 + cranelift/isle/isle/Cargo.toml | 2 +- cranelift/isle/islec/Cargo.toml | 3 +- deny.toml | 1 + scripts/publish.rs | 1 + 29 files changed, 9086 insertions(+), 1032 deletions(-) create mode 100644 cranelift/codegen/src/clif.isle create mode 100644 cranelift/codegen/src/isa/x64/inst.isle create mode 100644 cranelift/codegen/src/isa/x64/lower.isle create mode 100644 cranelift/codegen/src/isa/x64/lower/isle.rs create mode 100644 cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs create mode 100644 cranelift/codegen/src/isle.rs create mode 100644 cranelift/codegen/src/prelude.isle create mode 100644 cranelift/isle/fuzz/README.md diff --git a/.gitattributes b/.gitattributes index df94c94722..928feac126 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,10 @@ *.png binary *.ico binary *.wasm binary + +# ISLE should use lisp syntax highlighting. +*.isle linguist-language=lisp + +# Tell GitHub this is generated code, and doesn't need to be shown in diffs by +# default. +cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs linguist-generated diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3f7b2f01c9..b880bfafb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -189,6 +189,21 @@ jobs: working-directory: ./fuzz - run: cargo fuzz build --dev + rebuild_isle: + name: Rebuild ISLE + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: ./.github/actions/install-rust + - name: Rebuild ISLE DSL files + run: cargo build -p cranelift-codegen --features "rebuild-isle" + - name: Reformat + run: cargo fmt -p cranelift-codegen + - name: Check that the ISLE DSL files are up-to-date + run: git diff --exit-code + rebuild_peephole_optimizers: name: Rebuild Peephole Optimizers runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 2f8b1a4333..1e6102c16a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -552,7 +552,9 @@ dependencies = [ "criterion", "gimli", "hashbrown", + "isle", "log", + "miette", "peepmatic", "peepmatic-runtime", "peepmatic-traits", diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index 5bc01a92ce..85a7dc3789 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -39,6 +39,8 @@ criterion = "0.3" [build-dependencies] cranelift-codegen-meta = { path = "meta", version = "0.78.0" } +isle = { path = "../isle/isle", version = "0.1.0", optional = true } +miette = { version = "3", features = ["fancy"] } [features] default = ["std", "unwind"] @@ -98,6 +100,9 @@ enable-peepmatic = ["peepmatic-runtime", "peepmatic-traits", "serde"] # Enable support for the Souper harvester. souper-harvest = ["souper-ir", "souper-ir/stringify"] +# Recompile ISLE DSL source files into their generated Rust code. +rebuild-isle = ["isle", "cranelift-codegen-meta/rebuild-isle"] + [badges] maintenance = { status = "experimental" } diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index 51fcf4cbaa..5932030fc3 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -46,9 +46,12 @@ fn main() { isa_targets }; + let cur_dir = env::current_dir().expect("Can't access current working directory"); + let crate_dir = cur_dir.as_path(); + println!("cargo:rerun-if-changed=build.rs"); - if let Err(err) = meta::generate(&isas, &out_dir) { + if let Err(err) = meta::generate(&isas, &out_dir, crate_dir) { eprintln!("Error: {}", err); process::exit(1); } @@ -74,6 +77,19 @@ fn main() { .unwrap() } + #[cfg(feature = "rebuild-isle")] + { + if let Err(e) = rebuild_isle(crate_dir) { + eprintln!("Error building ISLE files: {:?}", e); + let mut source = e.source(); + while let Some(e) = source { + eprintln!("{:?}", e); + source = e.source(); + } + std::process::abort(); + } + } + let pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); let mut cmd = std::process::Command::new("git"); cmd.arg("rev-parse") @@ -110,3 +126,101 @@ fn main() { ) .unwrap(); } + +/// Rebuild ISLE DSL source text into generated Rust code. +/// +/// NB: This must happen *after* the `cranelift-codegen-meta` functions, since +/// it consumes files generated by them. +#[cfg(feature = "rebuild-isle")] +fn rebuild_isle(crate_dir: &std::path::Path) -> Result<(), Box> { + use std::sync::Once; + static SET_MIETTE_HOOK: Once = Once::new(); + SET_MIETTE_HOOK.call_once(|| { + let _ = miette::set_hook(Box::new(|_| { + Box::new( + miette::MietteHandlerOpts::new() + // `miette` mistakenly uses braille-optimized output for emacs's + // `M-x shell`. + .force_graphical(true) + .build(), + ) + })); + }); + + let clif_isle = crate_dir.join("src").join("clif.isle"); + let prelude_isle = crate_dir.join("src").join("prelude.isle"); + let src_isa_x64 = crate_dir.join("src").join("isa").join("x64"); + + // This is a set of ISLE compilation units. + // + // The format of each entry is: + // + // (output Rust code file, input ISLE source files) + // + // There should be one entry for each backend that uses ISLE for lowering, + // and if/when we replace our peephole optimization passes with ISLE, there + // should be an entry for each of those as well. + let isle_compilations = vec![ + // The x86-64 instruction selector. + ( + src_isa_x64 + .join("lower") + .join("isle") + .join("generated_code.rs"), + vec![ + clif_isle, + prelude_isle, + src_isa_x64.join("inst.isle"), + src_isa_x64.join("lower.isle"), + ], + ), + ]; + + let cur_dir = std::env::current_dir()?; + for (out_file, mut files) in isle_compilations { + for file in files.iter_mut() { + println!("cargo:rerun-if-changed={}", file.display()); + + // Strip the current directory from the file paths, because `islec` + // includes them in the generated source, and this helps us maintain + // deterministic builds that don't include those local file paths. + if let Ok(suffix) = file.strip_prefix(&cur_dir) { + *file = suffix.to_path_buf(); + } + } + + let code = (|| { + let lexer = isle::lexer::Lexer::from_files(files)?; + let defs = isle::parser::parse(lexer)?; + isle::compile::compile(&defs) + })() + .map_err(|e| { + // Make sure to include the source snippets location info along with + // the error messages. + + let report = miette::Report::new(e); + return DebugReport(report); + + struct DebugReport(miette::Report); + + impl std::fmt::Display for DebugReport { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.handler().debug(&*self.0, f) + } + } + + impl std::fmt::Debug for DebugReport { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } + } + + impl std::error::Error for DebugReport {} + })?; + + println!("Writing ISLE-generated Rust code to {}", out_file.display()); + std::fs::write(out_file, code)?; + } + + Ok(()) +} diff --git a/cranelift/codegen/meta/Cargo.toml b/cranelift/codegen/meta/Cargo.toml index 374f4f63db..041d59befd 100644 --- a/cranelift/codegen/meta/Cargo.toml +++ b/cranelift/codegen/meta/Cargo.toml @@ -17,3 +17,6 @@ cranelift-codegen-shared = { path = "../shared", version = "0.78.0" } [badges] maintenance = { status = "experimental" } + +[features] +rebuild-isle = [] diff --git a/cranelift/codegen/meta/src/gen_inst.rs b/cranelift/codegen/meta/src/gen_inst.rs index 5e28e0b24b..1817bb7937 100644 --- a/cranelift/codegen/meta/src/gen_inst.rs +++ b/cranelift/codegen/meta/src/gen_inst.rs @@ -1,5 +1,6 @@ //! Generate instruction data (including opcodes, formats, builders, etc.). use std::fmt; +use std::path::Path; use cranelift_codegen_shared::constant_hash; @@ -1084,6 +1085,243 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo fmtln!(fmt, "}") } +#[cfg(feature = "rebuild-isle")] +fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: &mut Formatter) { + use std::collections::BTreeSet; + use std::fmt::Write; + + fmt.multi_line( + r#" +;; GENERATED BY `gen_isle`. DO NOT EDIT!!! +;; +;; This ISLE file defines all the external type declarations for Cranelift's +;; data structures that ISLE will process, such as `InstructionData` and +;; `Opcode`. + "#, + ); + fmt.empty_line(); + + // Generate all the extern type declarations we need for various immediates. + fmt.line(";;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); + fmt.empty_line(); + let imm_tys: BTreeSet<_> = formats + .iter() + .flat_map(|f| { + f.imm_fields + .iter() + .map(|i| i.kind.rust_type.rsplit("::").next().unwrap()) + .collect::>() + }) + .collect(); + for ty in imm_tys { + fmtln!(fmt, "(type {} (primitive {}))", ty, ty); + } + fmt.empty_line(); + + // Generate all of the value arrays we need for `InstructionData` as well as + // the constructors and extractors for them. + fmt.line(";;;; Value Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); + fmt.empty_line(); + let value_array_arities: BTreeSet<_> = formats + .iter() + .filter(|f| f.typevar_operand.is_some() && !f.has_value_list && f.num_value_operands != 1) + .map(|f| f.num_value_operands) + .collect(); + for n in value_array_arities { + fmtln!(fmt, ";; ISLE representation of `[Value; {}]`.", n); + fmtln!(fmt, "(type ValueArray{} extern (enum))", n); + fmt.empty_line(); + + fmtln!( + fmt, + "(decl value_array_{} ({}) ValueArray{})", + n, + (0..n).map(|_| "Value").collect::>().join(" "), + n + ); + fmtln!( + fmt, + "(extern constructor value_array_{} pack_value_array_{})", + n, + n + ); + fmtln!( + fmt, + "(extern extractor infallible value_array_{} unpack_value_array_{})", + n, + n + ); + fmt.empty_line(); + } + + // Generate the extern type declaration for `Opcode`. + fmt.line(";;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); + fmt.empty_line(); + fmt.line("(type Opcode extern"); + fmt.indent(|fmt| { + fmt.line("(enum"); + fmt.indent(|fmt| { + for inst in instructions { + fmtln!(fmt, "{}", inst.camel_name); + } + }); + fmt.line(")"); + }); + fmt.line(")"); + fmt.empty_line(); + + // Generate the extern type declaration for `InstructionData`. + fmt.line(";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); + fmt.empty_line(); + fmt.line("(type InstructionData extern"); + fmt.indent(|fmt| { + fmt.line("(enum"); + fmt.indent(|fmt| { + for format in formats { + let mut s = format!("({} (opcode Opcode)", format.name); + if format.typevar_operand.is_some() { + if format.has_value_list { + s.push_str(" (args ValueList)"); + } else if format.num_value_operands == 1 { + s.push_str(" (arg Value)"); + } else { + write!(&mut s, " (args ValueArray{})", format.num_value_operands).unwrap(); + } + } + for field in &format.imm_fields { + write!( + &mut s, + " ({} {})", + field.member, + field.kind.rust_type.rsplit("::").next().unwrap() + ) + .unwrap(); + } + s.push(')'); + fmt.line(&s); + } + }); + fmt.line(")"); + }); + fmt.line(")"); + fmt.empty_line(); + + // Generate the helper extractors for each opcode's full instruction. + // + // TODO: if/when we port our peephole optimization passes to ISLE we will + // want helper constructors as well. + fmt.line(";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;"); + fmt.empty_line(); + for inst in instructions { + fmtln!( + fmt, + "(decl {} ({}) Inst)", + inst.name, + inst.operands_in + .iter() + .map(|o| { + let ty = o.kind.rust_type; + if ty == "&[Value]" { + "ValueSlice" + } else { + ty.rsplit("::").next().unwrap() + } + }) + .collect::>() + .join(" ") + ); + fmtln!(fmt, "(extractor"); + fmt.indent(|fmt| { + fmtln!( + fmt, + "({} {})", + inst.name, + inst.operands_in + .iter() + .map(|o| { o.name }) + .collect::>() + .join(" ") + ); + let mut s = format!( + "(inst_data (InstructionData.{} (Opcode.{})", + inst.format.name, inst.camel_name + ); + + // Immediates. + let imm_operands: Vec<_> = inst + .operands_in + .iter() + .filter(|o| !o.is_value() && !o.is_varargs()) + .collect(); + assert_eq!(imm_operands.len(), inst.format.imm_fields.len()); + for op in imm_operands { + write!(&mut s, " {}", op.name).unwrap(); + } + + // Value and varargs operands. + if inst.format.typevar_operand.is_some() { + if inst.format.has_value_list { + // The instruction format uses a value list, but the + // instruction itself might have not only a `&[Value]` + // varargs operand, but also one or more `Value` operands as + // well. If this is the case, then we need to read them off + // the front of the `ValueList`. + let values: Vec<_> = inst + .operands_in + .iter() + .filter(|o| o.is_value()) + .map(|o| o.name) + .collect(); + let varargs = inst + .operands_in + .iter() + .find(|o| o.is_varargs()) + .unwrap() + .name; + if values.is_empty() { + write!(&mut s, " (value_list_slice {})", varargs).unwrap(); + } else { + write!( + &mut s, + " (unwrap_head_value_list_{} {} {})", + values.len(), + values.join(" "), + varargs + ) + .unwrap(); + } + } else if inst.format.num_value_operands == 1 { + write!( + &mut s, + " {}", + inst.operands_in.iter().find(|o| o.is_value()).unwrap().name + ) + .unwrap(); + } else { + let values = inst + .operands_in + .iter() + .filter(|o| o.is_value()) + .map(|o| o.name) + .collect::>(); + assert_eq!(values.len(), inst.format.num_value_operands); + let values = values.join(" "); + write!( + &mut s, + " (value_array_{} {})", + inst.format.num_value_operands, values, + ) + .unwrap(); + } + } + s.push_str("))"); + fmt.line(&s); + }); + fmt.line(")"); + fmt.empty_line(); + } +} + /// Generate a Builder trait with methods for all instructions. fn gen_builder( instructions: &AllInstructions, @@ -1128,7 +1366,9 @@ pub(crate) fn generate( all_inst: &AllInstructions, opcode_filename: &str, inst_builder_filename: &str, + isle_filename: &str, out_dir: &str, + crate_dir: &Path, ) -> Result<(), error::Error> { // Opcodes. let mut fmt = Formatter::new(); @@ -1144,6 +1384,20 @@ pub(crate) fn generate( gen_try_from(all_inst, &mut fmt); fmt.update_file(opcode_filename, out_dir)?; + // ISLE DSL. + #[cfg(feature = "rebuild-isle")] + { + let mut fmt = Formatter::new(); + gen_isle(&formats, all_inst, &mut fmt); + let crate_src_dir = crate_dir.join("src"); + fmt.update_file(isle_filename, &crate_src_dir.display().to_string())?; + } + #[cfg(not(feature = "rebuild-isle"))] + { + // Silence unused variable warnings. + let _ = (isle_filename, crate_dir); + } + // Instruction builder. let mut fmt = Formatter::new(); gen_builder(all_inst, &formats, &mut fmt); diff --git a/cranelift/codegen/meta/src/lib.rs b/cranelift/codegen/meta/src/lib.rs index 30e5acc48c..e688f89b7d 100644 --- a/cranelift/codegen/meta/src/lib.rs +++ b/cranelift/codegen/meta/src/lib.rs @@ -1,5 +1,7 @@ //! This crate generates Rust sources for use by //! [`cranelift_codegen`](../cranelift_codegen/index.html). + +use std::path::Path; #[macro_use] mod cdsl; mod srcgen; @@ -21,7 +23,7 @@ pub fn isa_from_arch(arch: &str) -> Result { } /// Generates all the Rust source files used in Cranelift from the meta-language. -pub fn generate(isas: &[isa::Isa], out_dir: &str) -> Result<(), error::Error> { +pub fn generate(isas: &[isa::Isa], out_dir: &str, crate_dir: &Path) -> Result<(), error::Error> { // Create all the definitions: // - common definitions. let mut shared_defs = shared::define(); @@ -46,7 +48,9 @@ pub fn generate(isas: &[isa::Isa], out_dir: &str) -> Result<(), error::Error> { &shared_defs.all_instructions, "opcodes.rs", "inst_builder.rs", + "clif.isle", &out_dir, + crate_dir, )?; for isa in target_isas { diff --git a/cranelift/codegen/meta/src/srcgen.rs b/cranelift/codegen/meta/src/srcgen.rs index 21e3d5e904..c4fde06623 100644 --- a/cranelift/codegen/meta/src/srcgen.rs +++ b/cranelift/codegen/meta/src/srcgen.rs @@ -100,6 +100,7 @@ impl Formatter { let path_str = format!("{}/{}", directory, filename.as_ref()); let path = path::Path::new(&path_str); + println!("Writing generated file: {}", path.display()); let mut f = fs::File::create(path)?; for l in self.lines.iter().map(|l| l.as_bytes()) { diff --git a/cranelift/codegen/src/clif.isle b/cranelift/codegen/src/clif.isle new file mode 100644 index 0000000000..04601d5ea5 --- /dev/null +++ b/cranelift/codegen/src/clif.isle @@ -0,0 +1,1635 @@ +;; GENERATED BY `gen_isle`. DO NOT EDIT!!! +;; +;; This ISLE file defines all the external type declarations for Cranelift's +;; data structures that ISLE will process, such as `InstructionData` and +;; `Opcode`. + +;;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(type AtomicRmwOp (primitive AtomicRmwOp)) +(type Block (primitive Block)) +(type Constant (primitive Constant)) +(type FloatCC (primitive FloatCC)) +(type FuncRef (primitive FuncRef)) +(type GlobalValue (primitive GlobalValue)) +(type Heap (primitive Heap)) +(type Ieee32 (primitive Ieee32)) +(type Ieee64 (primitive Ieee64)) +(type Imm64 (primitive Imm64)) +(type Immediate (primitive Immediate)) +(type IntCC (primitive IntCC)) +(type JumpTable (primitive JumpTable)) +(type MemFlags (primitive MemFlags)) +(type Offset32 (primitive Offset32)) +(type SigRef (primitive SigRef)) +(type StackSlot (primitive StackSlot)) +(type Table (primitive Table)) +(type TrapCode (primitive TrapCode)) +(type Uimm32 (primitive Uimm32)) +(type Uimm8 (primitive Uimm8)) +(type bool (primitive bool)) + +;;;; Value Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; ISLE representation of `[Value; 2]`. +(type ValueArray2 extern (enum)) + +(decl value_array_2 (Value Value) ValueArray2) +(extern constructor value_array_2 pack_value_array_2) +(extern extractor infallible value_array_2 unpack_value_array_2) + +;; ISLE representation of `[Value; 3]`. +(type ValueArray3 extern (enum)) + +(decl value_array_3 (Value Value Value) ValueArray3) +(extern constructor value_array_3 pack_value_array_3) +(extern extractor infallible value_array_3 unpack_value_array_3) + +;;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(type Opcode extern + (enum + Jump + Brz + Brnz + BrIcmp + Brif + Brff + BrTable + Debugtrap + Trap + Trapz + ResumableTrap + Trapnz + ResumableTrapnz + Trapif + Trapff + Return + FallthroughReturn + Call + CallIndirect + FuncAddr + Splat + Swizzle + Insertlane + Extractlane + Imin + Umin + Imax + Umax + AvgRound + UaddSat + SaddSat + UsubSat + SsubSat + Load + LoadComplex + Store + StoreComplex + Uload8 + Uload8Complex + Sload8 + Sload8Complex + Istore8 + Istore8Complex + Uload16 + Uload16Complex + Sload16 + Sload16Complex + Istore16 + Istore16Complex + Uload32 + Uload32Complex + Sload32 + Sload32Complex + Istore32 + Istore32Complex + Uload8x8 + Uload8x8Complex + Sload8x8 + Sload8x8Complex + Uload16x4 + Uload16x4Complex + Sload16x4 + Sload16x4Complex + Uload32x2 + Uload32x2Complex + Sload32x2 + Sload32x2Complex + StackLoad + StackStore + StackAddr + GlobalValue + SymbolValue + TlsValue + HeapAddr + GetPinnedReg + SetPinnedReg + TableAddr + Iconst + F32const + F64const + Bconst + Vconst + ConstAddr + Shuffle + Null + Nop + Select + Selectif + SelectifSpectreGuard + Bitselect + Copy + IfcmpSp + Vsplit + Vconcat + Vselect + VanyTrue + VallTrue + VhighBits + Icmp + IcmpImm + Ifcmp + IfcmpImm + Iadd + Isub + Ineg + Iabs + Imul + Umulhi + Smulhi + SqmulRoundSat + Udiv + Sdiv + Urem + Srem + IaddImm + ImulImm + UdivImm + SdivImm + UremImm + SremImm + IrsubImm + IaddCin + IaddIfcin + IaddCout + IaddIfcout + IaddCarry + IaddIfcarry + IsubBin + IsubIfbin + IsubBout + IsubIfbout + IsubBorrow + IsubIfborrow + Band + Bor + Bxor + Bnot + BandNot + BorNot + BxorNot + BandImm + BorImm + BxorImm + Rotl + Rotr + RotlImm + RotrImm + Ishl + Ushr + Sshr + IshlImm + UshrImm + SshrImm + Bitrev + Clz + Cls + Ctz + Popcnt + Fcmp + Ffcmp + Fadd + Fsub + Fmul + Fdiv + Sqrt + Fma + Fneg + Fabs + Fcopysign + Fmin + FminPseudo + Fmax + FmaxPseudo + Ceil + Floor + Trunc + Nearest + IsNull + IsInvalid + Trueif + Trueff + Bitcast + RawBitcast + ScalarToVector + Breduce + Bextend + Bint + Bmask + Ireduce + Snarrow + Unarrow + Uunarrow + SwidenLow + SwidenHigh + UwidenLow + UwidenHigh + IaddPairwise + WideningPairwiseDotProductS + Uextend + Sextend + Fpromote + Fdemote + Fvdemote + FvpromoteLow + FcvtToUint + FcvtToUintSat + FcvtToSint + FcvtToSintSat + FcvtFromUint + FcvtFromSint + FcvtLowFromSint + Isplit + Iconcat + AtomicRmw + AtomicCas + AtomicLoad + AtomicStore + Fence + ) +) + +;;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(type InstructionData extern + (enum + (AtomicCas (opcode Opcode) (args ValueArray3) (flags MemFlags)) + (AtomicRmw (opcode Opcode) (args ValueArray2) (flags MemFlags) (op AtomicRmwOp)) + (Binary (opcode Opcode) (args ValueArray2)) + (BinaryImm64 (opcode Opcode) (arg Value) (imm Imm64)) + (BinaryImm8 (opcode Opcode) (arg Value) (imm Uimm8)) + (Branch (opcode Opcode) (args ValueList) (destination Block)) + (BranchFloat (opcode Opcode) (args ValueList) (cond FloatCC) (destination Block)) + (BranchIcmp (opcode Opcode) (args ValueList) (cond IntCC) (destination Block)) + (BranchInt (opcode Opcode) (args ValueList) (cond IntCC) (destination Block)) + (BranchTable (opcode Opcode) (arg Value) (destination Block) (table JumpTable)) + (Call (opcode Opcode) (func_ref FuncRef)) + (CallIndirect (opcode Opcode) (args ValueList) (sig_ref SigRef)) + (CondTrap (opcode Opcode) (arg Value) (code TrapCode)) + (FloatCompare (opcode Opcode) (args ValueArray2) (cond FloatCC)) + (FloatCond (opcode Opcode) (arg Value) (cond FloatCC)) + (FloatCondTrap (opcode Opcode) (arg Value) (cond FloatCC) (code TrapCode)) + (FuncAddr (opcode Opcode) (func_ref FuncRef)) + (HeapAddr (opcode Opcode) (arg Value) (heap Heap) (imm Uimm32)) + (IntCompare (opcode Opcode) (args ValueArray2) (cond IntCC)) + (IntCompareImm (opcode Opcode) (arg Value) (cond IntCC) (imm Imm64)) + (IntCond (opcode Opcode) (arg Value) (cond IntCC)) + (IntCondTrap (opcode Opcode) (arg Value) (cond IntCC) (code TrapCode)) + (IntSelect (opcode Opcode) (args ValueArray3) (cond IntCC)) + (Jump (opcode Opcode) (destination Block)) + (Load (opcode Opcode) (arg Value) (flags MemFlags) (offset Offset32)) + (LoadComplex (opcode Opcode) (flags MemFlags) (offset Offset32)) + (LoadNoOffset (opcode Opcode) (arg Value) (flags MemFlags)) + (MultiAry (opcode Opcode)) + (NullAry (opcode Opcode)) + (Shuffle (opcode Opcode) (args ValueArray2) (imm Immediate)) + (StackLoad (opcode Opcode) (stack_slot StackSlot) (offset Offset32)) + (StackStore (opcode Opcode) (arg Value) (stack_slot StackSlot) (offset Offset32)) + (Store (opcode Opcode) (args ValueArray2) (flags MemFlags) (offset Offset32)) + (StoreComplex (opcode Opcode) (args ValueList) (flags MemFlags) (offset Offset32)) + (StoreNoOffset (opcode Opcode) (args ValueArray2) (flags MemFlags)) + (TableAddr (opcode Opcode) (arg Value) (table Table) (offset Offset32)) + (Ternary (opcode Opcode) (args ValueArray3)) + (TernaryImm8 (opcode Opcode) (args ValueArray2) (imm Uimm8)) + (Trap (opcode Opcode) (code TrapCode)) + (Unary (opcode Opcode) (arg Value)) + (UnaryBool (opcode Opcode) (imm bool)) + (UnaryConst (opcode Opcode) (constant_handle Constant)) + (UnaryGlobalValue (opcode Opcode) (global_value GlobalValue)) + (UnaryIeee32 (opcode Opcode) (imm Ieee32)) + (UnaryIeee64 (opcode Opcode) (imm Ieee64)) + (UnaryImm (opcode Opcode) (imm Imm64)) + ) +) + +;;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;; + +(decl jump (Block ValueSlice) Inst) +(extractor + (jump block args) + (inst_data (InstructionData.Jump (Opcode.Jump) block)) +) + +(decl brz (Value Block ValueSlice) Inst) +(extractor + (brz c block args) + (inst_data (InstructionData.Branch (Opcode.Brz) block (unwrap_head_value_list_1 c args))) +) + +(decl brnz (Value Block ValueSlice) Inst) +(extractor + (brnz c block args) + (inst_data (InstructionData.Branch (Opcode.Brnz) block (unwrap_head_value_list_1 c args))) +) + +(decl br_icmp (IntCC Value Value Block ValueSlice) Inst) +(extractor + (br_icmp Cond x y block args) + (inst_data (InstructionData.BranchIcmp (Opcode.BrIcmp) Cond block (unwrap_head_value_list_2 x y args))) +) + +(decl brif (IntCC Value Block ValueSlice) Inst) +(extractor + (brif Cond f block args) + (inst_data (InstructionData.BranchInt (Opcode.Brif) Cond block (unwrap_head_value_list_1 f args))) +) + +(decl brff (FloatCC Value Block ValueSlice) Inst) +(extractor + (brff Cond f block args) + (inst_data (InstructionData.BranchFloat (Opcode.Brff) Cond block (unwrap_head_value_list_1 f args))) +) + +(decl br_table (Value Block JumpTable) Inst) +(extractor + (br_table x block JT) + (inst_data (InstructionData.BranchTable (Opcode.BrTable) block JT x)) +) + +(decl debugtrap () Inst) +(extractor + (debugtrap ) + (inst_data (InstructionData.NullAry (Opcode.Debugtrap))) +) + +(decl trap (TrapCode) Inst) +(extractor + (trap code) + (inst_data (InstructionData.Trap (Opcode.Trap) code)) +) + +(decl trapz (Value TrapCode) Inst) +(extractor + (trapz c code) + (inst_data (InstructionData.CondTrap (Opcode.Trapz) code c)) +) + +(decl resumable_trap (TrapCode) Inst) +(extractor + (resumable_trap code) + (inst_data (InstructionData.Trap (Opcode.ResumableTrap) code)) +) + +(decl trapnz (Value TrapCode) Inst) +(extractor + (trapnz c code) + (inst_data (InstructionData.CondTrap (Opcode.Trapnz) code c)) +) + +(decl resumable_trapnz (Value TrapCode) Inst) +(extractor + (resumable_trapnz c code) + (inst_data (InstructionData.CondTrap (Opcode.ResumableTrapnz) code c)) +) + +(decl trapif (IntCC Value TrapCode) Inst) +(extractor + (trapif Cond f code) + (inst_data (InstructionData.IntCondTrap (Opcode.Trapif) Cond code f)) +) + +(decl trapff (FloatCC Value TrapCode) Inst) +(extractor + (trapff Cond f code) + (inst_data (InstructionData.FloatCondTrap (Opcode.Trapff) Cond code f)) +) + +(decl return (ValueSlice) Inst) +(extractor + (return rvals) + (inst_data (InstructionData.MultiAry (Opcode.Return))) +) + +(decl fallthrough_return (ValueSlice) Inst) +(extractor + (fallthrough_return rvals) + (inst_data (InstructionData.MultiAry (Opcode.FallthroughReturn))) +) + +(decl call (FuncRef ValueSlice) Inst) +(extractor + (call FN args) + (inst_data (InstructionData.Call (Opcode.Call) FN)) +) + +(decl call_indirect (SigRef Value ValueSlice) Inst) +(extractor + (call_indirect SIG callee args) + (inst_data (InstructionData.CallIndirect (Opcode.CallIndirect) SIG (unwrap_head_value_list_1 callee args))) +) + +(decl func_addr (FuncRef) Inst) +(extractor + (func_addr FN) + (inst_data (InstructionData.FuncAddr (Opcode.FuncAddr) FN)) +) + +(decl splat (Value) Inst) +(extractor + (splat x) + (inst_data (InstructionData.Unary (Opcode.Splat) x)) +) + +(decl swizzle (Value Value) Inst) +(extractor + (swizzle x y) + (inst_data (InstructionData.Binary (Opcode.Swizzle) (value_array_2 x y))) +) + +(decl insertlane (Value Value Uimm8) Inst) +(extractor + (insertlane x y Idx) + (inst_data (InstructionData.TernaryImm8 (Opcode.Insertlane) Idx (value_array_2 x y))) +) + +(decl extractlane (Value Uimm8) Inst) +(extractor + (extractlane x Idx) + (inst_data (InstructionData.BinaryImm8 (Opcode.Extractlane) Idx x)) +) + +(decl imin (Value Value) Inst) +(extractor + (imin x y) + (inst_data (InstructionData.Binary (Opcode.Imin) (value_array_2 x y))) +) + +(decl umin (Value Value) Inst) +(extractor + (umin x y) + (inst_data (InstructionData.Binary (Opcode.Umin) (value_array_2 x y))) +) + +(decl imax (Value Value) Inst) +(extractor + (imax x y) + (inst_data (InstructionData.Binary (Opcode.Imax) (value_array_2 x y))) +) + +(decl umax (Value Value) Inst) +(extractor + (umax x y) + (inst_data (InstructionData.Binary (Opcode.Umax) (value_array_2 x y))) +) + +(decl avg_round (Value Value) Inst) +(extractor + (avg_round x y) + (inst_data (InstructionData.Binary (Opcode.AvgRound) (value_array_2 x y))) +) + +(decl uadd_sat (Value Value) Inst) +(extractor + (uadd_sat x y) + (inst_data (InstructionData.Binary (Opcode.UaddSat) (value_array_2 x y))) +) + +(decl sadd_sat (Value Value) Inst) +(extractor + (sadd_sat x y) + (inst_data (InstructionData.Binary (Opcode.SaddSat) (value_array_2 x y))) +) + +(decl usub_sat (Value Value) Inst) +(extractor + (usub_sat x y) + (inst_data (InstructionData.Binary (Opcode.UsubSat) (value_array_2 x y))) +) + +(decl ssub_sat (Value Value) Inst) +(extractor + (ssub_sat x y) + (inst_data (InstructionData.Binary (Opcode.SsubSat) (value_array_2 x y))) +) + +(decl load (MemFlags Value Offset32) Inst) +(extractor + (load MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Load) MemFlags Offset p)) +) + +(decl load_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (load_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.LoadComplex) MemFlags Offset)) +) + +(decl store (MemFlags Value Value Offset32) Inst) +(extractor + (store MemFlags x p Offset) + (inst_data (InstructionData.Store (Opcode.Store) MemFlags Offset (value_array_2 x p))) +) + +(decl store_complex (MemFlags Value ValueSlice Offset32) Inst) +(extractor + (store_complex MemFlags x args Offset) + (inst_data (InstructionData.StoreComplex (Opcode.StoreComplex) MemFlags Offset (unwrap_head_value_list_1 x args))) +) + +(decl uload8 (MemFlags Value Offset32) Inst) +(extractor + (uload8 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Uload8) MemFlags Offset p)) +) + +(decl uload8_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (uload8_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Uload8Complex) MemFlags Offset)) +) + +(decl sload8 (MemFlags Value Offset32) Inst) +(extractor + (sload8 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Sload8) MemFlags Offset p)) +) + +(decl sload8_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (sload8_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Sload8Complex) MemFlags Offset)) +) + +(decl istore8 (MemFlags Value Value Offset32) Inst) +(extractor + (istore8 MemFlags x p Offset) + (inst_data (InstructionData.Store (Opcode.Istore8) MemFlags Offset (value_array_2 x p))) +) + +(decl istore8_complex (MemFlags Value ValueSlice Offset32) Inst) +(extractor + (istore8_complex MemFlags x args Offset) + (inst_data (InstructionData.StoreComplex (Opcode.Istore8Complex) MemFlags Offset (unwrap_head_value_list_1 x args))) +) + +(decl uload16 (MemFlags Value Offset32) Inst) +(extractor + (uload16 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Uload16) MemFlags Offset p)) +) + +(decl uload16_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (uload16_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Uload16Complex) MemFlags Offset)) +) + +(decl sload16 (MemFlags Value Offset32) Inst) +(extractor + (sload16 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Sload16) MemFlags Offset p)) +) + +(decl sload16_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (sload16_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Sload16Complex) MemFlags Offset)) +) + +(decl istore16 (MemFlags Value Value Offset32) Inst) +(extractor + (istore16 MemFlags x p Offset) + (inst_data (InstructionData.Store (Opcode.Istore16) MemFlags Offset (value_array_2 x p))) +) + +(decl istore16_complex (MemFlags Value ValueSlice Offset32) Inst) +(extractor + (istore16_complex MemFlags x args Offset) + (inst_data (InstructionData.StoreComplex (Opcode.Istore16Complex) MemFlags Offset (unwrap_head_value_list_1 x args))) +) + +(decl uload32 (MemFlags Value Offset32) Inst) +(extractor + (uload32 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Uload32) MemFlags Offset p)) +) + +(decl uload32_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (uload32_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Uload32Complex) MemFlags Offset)) +) + +(decl sload32 (MemFlags Value Offset32) Inst) +(extractor + (sload32 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Sload32) MemFlags Offset p)) +) + +(decl sload32_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (sload32_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Sload32Complex) MemFlags Offset)) +) + +(decl istore32 (MemFlags Value Value Offset32) Inst) +(extractor + (istore32 MemFlags x p Offset) + (inst_data (InstructionData.Store (Opcode.Istore32) MemFlags Offset (value_array_2 x p))) +) + +(decl istore32_complex (MemFlags Value ValueSlice Offset32) Inst) +(extractor + (istore32_complex MemFlags x args Offset) + (inst_data (InstructionData.StoreComplex (Opcode.Istore32Complex) MemFlags Offset (unwrap_head_value_list_1 x args))) +) + +(decl uload8x8 (MemFlags Value Offset32) Inst) +(extractor + (uload8x8 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Uload8x8) MemFlags Offset p)) +) + +(decl uload8x8_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (uload8x8_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Uload8x8Complex) MemFlags Offset)) +) + +(decl sload8x8 (MemFlags Value Offset32) Inst) +(extractor + (sload8x8 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Sload8x8) MemFlags Offset p)) +) + +(decl sload8x8_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (sload8x8_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Sload8x8Complex) MemFlags Offset)) +) + +(decl uload16x4 (MemFlags Value Offset32) Inst) +(extractor + (uload16x4 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Uload16x4) MemFlags Offset p)) +) + +(decl uload16x4_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (uload16x4_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Uload16x4Complex) MemFlags Offset)) +) + +(decl sload16x4 (MemFlags Value Offset32) Inst) +(extractor + (sload16x4 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Sload16x4) MemFlags Offset p)) +) + +(decl sload16x4_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (sload16x4_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Sload16x4Complex) MemFlags Offset)) +) + +(decl uload32x2 (MemFlags Value Offset32) Inst) +(extractor + (uload32x2 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Uload32x2) MemFlags Offset p)) +) + +(decl uload32x2_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (uload32x2_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Uload32x2Complex) MemFlags Offset)) +) + +(decl sload32x2 (MemFlags Value Offset32) Inst) +(extractor + (sload32x2 MemFlags p Offset) + (inst_data (InstructionData.Load (Opcode.Sload32x2) MemFlags Offset p)) +) + +(decl sload32x2_complex (MemFlags ValueSlice Offset32) Inst) +(extractor + (sload32x2_complex MemFlags args Offset) + (inst_data (InstructionData.LoadComplex (Opcode.Sload32x2Complex) MemFlags Offset)) +) + +(decl stack_load (StackSlot Offset32) Inst) +(extractor + (stack_load SS Offset) + (inst_data (InstructionData.StackLoad (Opcode.StackLoad) SS Offset)) +) + +(decl stack_store (Value StackSlot Offset32) Inst) +(extractor + (stack_store x SS Offset) + (inst_data (InstructionData.StackStore (Opcode.StackStore) SS Offset x)) +) + +(decl stack_addr (StackSlot Offset32) Inst) +(extractor + (stack_addr SS Offset) + (inst_data (InstructionData.StackLoad (Opcode.StackAddr) SS Offset)) +) + +(decl global_value (GlobalValue) Inst) +(extractor + (global_value GV) + (inst_data (InstructionData.UnaryGlobalValue (Opcode.GlobalValue) GV)) +) + +(decl symbol_value (GlobalValue) Inst) +(extractor + (symbol_value GV) + (inst_data (InstructionData.UnaryGlobalValue (Opcode.SymbolValue) GV)) +) + +(decl tls_value (GlobalValue) Inst) +(extractor + (tls_value GV) + (inst_data (InstructionData.UnaryGlobalValue (Opcode.TlsValue) GV)) +) + +(decl heap_addr (Heap Value Uimm32) Inst) +(extractor + (heap_addr H p Size) + (inst_data (InstructionData.HeapAddr (Opcode.HeapAddr) H Size p)) +) + +(decl get_pinned_reg () Inst) +(extractor + (get_pinned_reg ) + (inst_data (InstructionData.NullAry (Opcode.GetPinnedReg))) +) + +(decl set_pinned_reg (Value) Inst) +(extractor + (set_pinned_reg addr) + (inst_data (InstructionData.Unary (Opcode.SetPinnedReg) addr)) +) + +(decl table_addr (Table Value Offset32) Inst) +(extractor + (table_addr T p Offset) + (inst_data (InstructionData.TableAddr (Opcode.TableAddr) T Offset p)) +) + +(decl iconst (Imm64) Inst) +(extractor + (iconst N) + (inst_data (InstructionData.UnaryImm (Opcode.Iconst) N)) +) + +(decl f32const (Ieee32) Inst) +(extractor + (f32const N) + (inst_data (InstructionData.UnaryIeee32 (Opcode.F32const) N)) +) + +(decl f64const (Ieee64) Inst) +(extractor + (f64const N) + (inst_data (InstructionData.UnaryIeee64 (Opcode.F64const) N)) +) + +(decl bconst (bool) Inst) +(extractor + (bconst N) + (inst_data (InstructionData.UnaryBool (Opcode.Bconst) N)) +) + +(decl vconst (Constant) Inst) +(extractor + (vconst N) + (inst_data (InstructionData.UnaryConst (Opcode.Vconst) N)) +) + +(decl const_addr (Constant) Inst) +(extractor + (const_addr constant) + (inst_data (InstructionData.UnaryConst (Opcode.ConstAddr) constant)) +) + +(decl shuffle (Value Value Immediate) Inst) +(extractor + (shuffle a b mask) + (inst_data (InstructionData.Shuffle (Opcode.Shuffle) mask (value_array_2 a b))) +) + +(decl null () Inst) +(extractor + (null ) + (inst_data (InstructionData.NullAry (Opcode.Null))) +) + +(decl nop () Inst) +(extractor + (nop ) + (inst_data (InstructionData.NullAry (Opcode.Nop))) +) + +(decl select (Value Value Value) Inst) +(extractor + (select c x y) + (inst_data (InstructionData.Ternary (Opcode.Select) (value_array_3 c x y))) +) + +(decl selectif (IntCC Value Value Value) Inst) +(extractor + (selectif cc flags x y) + (inst_data (InstructionData.IntSelect (Opcode.Selectif) cc (value_array_3 flags x y))) +) + +(decl selectif_spectre_guard (IntCC Value Value Value) Inst) +(extractor + (selectif_spectre_guard cc flags x y) + (inst_data (InstructionData.IntSelect (Opcode.SelectifSpectreGuard) cc (value_array_3 flags x y))) +) + +(decl bitselect (Value Value Value) Inst) +(extractor + (bitselect c x y) + (inst_data (InstructionData.Ternary (Opcode.Bitselect) (value_array_3 c x y))) +) + +(decl copy (Value) Inst) +(extractor + (copy x) + (inst_data (InstructionData.Unary (Opcode.Copy) x)) +) + +(decl ifcmp_sp (Value) Inst) +(extractor + (ifcmp_sp addr) + (inst_data (InstructionData.Unary (Opcode.IfcmpSp) addr)) +) + +(decl vsplit (Value) Inst) +(extractor + (vsplit x) + (inst_data (InstructionData.Unary (Opcode.Vsplit) x)) +) + +(decl vconcat (Value Value) Inst) +(extractor + (vconcat x y) + (inst_data (InstructionData.Binary (Opcode.Vconcat) (value_array_2 x y))) +) + +(decl vselect (Value Value Value) Inst) +(extractor + (vselect c x y) + (inst_data (InstructionData.Ternary (Opcode.Vselect) (value_array_3 c x y))) +) + +(decl vany_true (Value) Inst) +(extractor + (vany_true a) + (inst_data (InstructionData.Unary (Opcode.VanyTrue) a)) +) + +(decl vall_true (Value) Inst) +(extractor + (vall_true a) + (inst_data (InstructionData.Unary (Opcode.VallTrue) a)) +) + +(decl vhigh_bits (Value) Inst) +(extractor + (vhigh_bits a) + (inst_data (InstructionData.Unary (Opcode.VhighBits) a)) +) + +(decl icmp (IntCC Value Value) Inst) +(extractor + (icmp Cond x y) + (inst_data (InstructionData.IntCompare (Opcode.Icmp) Cond (value_array_2 x y))) +) + +(decl icmp_imm (IntCC Value Imm64) Inst) +(extractor + (icmp_imm Cond x Y) + (inst_data (InstructionData.IntCompareImm (Opcode.IcmpImm) Cond Y x)) +) + +(decl ifcmp (Value Value) Inst) +(extractor + (ifcmp x y) + (inst_data (InstructionData.Binary (Opcode.Ifcmp) (value_array_2 x y))) +) + +(decl ifcmp_imm (Value Imm64) Inst) +(extractor + (ifcmp_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.IfcmpImm) Y x)) +) + +(decl iadd (Value Value) Inst) +(extractor + (iadd x y) + (inst_data (InstructionData.Binary (Opcode.Iadd) (value_array_2 x y))) +) + +(decl isub (Value Value) Inst) +(extractor + (isub x y) + (inst_data (InstructionData.Binary (Opcode.Isub) (value_array_2 x y))) +) + +(decl ineg (Value) Inst) +(extractor + (ineg x) + (inst_data (InstructionData.Unary (Opcode.Ineg) x)) +) + +(decl iabs (Value) Inst) +(extractor + (iabs x) + (inst_data (InstructionData.Unary (Opcode.Iabs) x)) +) + +(decl imul (Value Value) Inst) +(extractor + (imul x y) + (inst_data (InstructionData.Binary (Opcode.Imul) (value_array_2 x y))) +) + +(decl umulhi (Value Value) Inst) +(extractor + (umulhi x y) + (inst_data (InstructionData.Binary (Opcode.Umulhi) (value_array_2 x y))) +) + +(decl smulhi (Value Value) Inst) +(extractor + (smulhi x y) + (inst_data (InstructionData.Binary (Opcode.Smulhi) (value_array_2 x y))) +) + +(decl sqmul_round_sat (Value Value) Inst) +(extractor + (sqmul_round_sat x y) + (inst_data (InstructionData.Binary (Opcode.SqmulRoundSat) (value_array_2 x y))) +) + +(decl udiv (Value Value) Inst) +(extractor + (udiv x y) + (inst_data (InstructionData.Binary (Opcode.Udiv) (value_array_2 x y))) +) + +(decl sdiv (Value Value) Inst) +(extractor + (sdiv x y) + (inst_data (InstructionData.Binary (Opcode.Sdiv) (value_array_2 x y))) +) + +(decl urem (Value Value) Inst) +(extractor + (urem x y) + (inst_data (InstructionData.Binary (Opcode.Urem) (value_array_2 x y))) +) + +(decl srem (Value Value) Inst) +(extractor + (srem x y) + (inst_data (InstructionData.Binary (Opcode.Srem) (value_array_2 x y))) +) + +(decl iadd_imm (Value Imm64) Inst) +(extractor + (iadd_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.IaddImm) Y x)) +) + +(decl imul_imm (Value Imm64) Inst) +(extractor + (imul_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.ImulImm) Y x)) +) + +(decl udiv_imm (Value Imm64) Inst) +(extractor + (udiv_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.UdivImm) Y x)) +) + +(decl sdiv_imm (Value Imm64) Inst) +(extractor + (sdiv_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.SdivImm) Y x)) +) + +(decl urem_imm (Value Imm64) Inst) +(extractor + (urem_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.UremImm) Y x)) +) + +(decl srem_imm (Value Imm64) Inst) +(extractor + (srem_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.SremImm) Y x)) +) + +(decl irsub_imm (Value Imm64) Inst) +(extractor + (irsub_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.IrsubImm) Y x)) +) + +(decl iadd_cin (Value Value Value) Inst) +(extractor + (iadd_cin x y c_in) + (inst_data (InstructionData.Ternary (Opcode.IaddCin) (value_array_3 x y c_in))) +) + +(decl iadd_ifcin (Value Value Value) Inst) +(extractor + (iadd_ifcin x y c_in) + (inst_data (InstructionData.Ternary (Opcode.IaddIfcin) (value_array_3 x y c_in))) +) + +(decl iadd_cout (Value Value) Inst) +(extractor + (iadd_cout x y) + (inst_data (InstructionData.Binary (Opcode.IaddCout) (value_array_2 x y))) +) + +(decl iadd_ifcout (Value Value) Inst) +(extractor + (iadd_ifcout x y) + (inst_data (InstructionData.Binary (Opcode.IaddIfcout) (value_array_2 x y))) +) + +(decl iadd_carry (Value Value Value) Inst) +(extractor + (iadd_carry x y c_in) + (inst_data (InstructionData.Ternary (Opcode.IaddCarry) (value_array_3 x y c_in))) +) + +(decl iadd_ifcarry (Value Value Value) Inst) +(extractor + (iadd_ifcarry x y c_in) + (inst_data (InstructionData.Ternary (Opcode.IaddIfcarry) (value_array_3 x y c_in))) +) + +(decl isub_bin (Value Value Value) Inst) +(extractor + (isub_bin x y b_in) + (inst_data (InstructionData.Ternary (Opcode.IsubBin) (value_array_3 x y b_in))) +) + +(decl isub_ifbin (Value Value Value) Inst) +(extractor + (isub_ifbin x y b_in) + (inst_data (InstructionData.Ternary (Opcode.IsubIfbin) (value_array_3 x y b_in))) +) + +(decl isub_bout (Value Value) Inst) +(extractor + (isub_bout x y) + (inst_data (InstructionData.Binary (Opcode.IsubBout) (value_array_2 x y))) +) + +(decl isub_ifbout (Value Value) Inst) +(extractor + (isub_ifbout x y) + (inst_data (InstructionData.Binary (Opcode.IsubIfbout) (value_array_2 x y))) +) + +(decl isub_borrow (Value Value Value) Inst) +(extractor + (isub_borrow x y b_in) + (inst_data (InstructionData.Ternary (Opcode.IsubBorrow) (value_array_3 x y b_in))) +) + +(decl isub_ifborrow (Value Value Value) Inst) +(extractor + (isub_ifborrow x y b_in) + (inst_data (InstructionData.Ternary (Opcode.IsubIfborrow) (value_array_3 x y b_in))) +) + +(decl band (Value Value) Inst) +(extractor + (band x y) + (inst_data (InstructionData.Binary (Opcode.Band) (value_array_2 x y))) +) + +(decl bor (Value Value) Inst) +(extractor + (bor x y) + (inst_data (InstructionData.Binary (Opcode.Bor) (value_array_2 x y))) +) + +(decl bxor (Value Value) Inst) +(extractor + (bxor x y) + (inst_data (InstructionData.Binary (Opcode.Bxor) (value_array_2 x y))) +) + +(decl bnot (Value) Inst) +(extractor + (bnot x) + (inst_data (InstructionData.Unary (Opcode.Bnot) x)) +) + +(decl band_not (Value Value) Inst) +(extractor + (band_not x y) + (inst_data (InstructionData.Binary (Opcode.BandNot) (value_array_2 x y))) +) + +(decl bor_not (Value Value) Inst) +(extractor + (bor_not x y) + (inst_data (InstructionData.Binary (Opcode.BorNot) (value_array_2 x y))) +) + +(decl bxor_not (Value Value) Inst) +(extractor + (bxor_not x y) + (inst_data (InstructionData.Binary (Opcode.BxorNot) (value_array_2 x y))) +) + +(decl band_imm (Value Imm64) Inst) +(extractor + (band_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.BandImm) Y x)) +) + +(decl bor_imm (Value Imm64) Inst) +(extractor + (bor_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.BorImm) Y x)) +) + +(decl bxor_imm (Value Imm64) Inst) +(extractor + (bxor_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.BxorImm) Y x)) +) + +(decl rotl (Value Value) Inst) +(extractor + (rotl x y) + (inst_data (InstructionData.Binary (Opcode.Rotl) (value_array_2 x y))) +) + +(decl rotr (Value Value) Inst) +(extractor + (rotr x y) + (inst_data (InstructionData.Binary (Opcode.Rotr) (value_array_2 x y))) +) + +(decl rotl_imm (Value Imm64) Inst) +(extractor + (rotl_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.RotlImm) Y x)) +) + +(decl rotr_imm (Value Imm64) Inst) +(extractor + (rotr_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.RotrImm) Y x)) +) + +(decl ishl (Value Value) Inst) +(extractor + (ishl x y) + (inst_data (InstructionData.Binary (Opcode.Ishl) (value_array_2 x y))) +) + +(decl ushr (Value Value) Inst) +(extractor + (ushr x y) + (inst_data (InstructionData.Binary (Opcode.Ushr) (value_array_2 x y))) +) + +(decl sshr (Value Value) Inst) +(extractor + (sshr x y) + (inst_data (InstructionData.Binary (Opcode.Sshr) (value_array_2 x y))) +) + +(decl ishl_imm (Value Imm64) Inst) +(extractor + (ishl_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.IshlImm) Y x)) +) + +(decl ushr_imm (Value Imm64) Inst) +(extractor + (ushr_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.UshrImm) Y x)) +) + +(decl sshr_imm (Value Imm64) Inst) +(extractor + (sshr_imm x Y) + (inst_data (InstructionData.BinaryImm64 (Opcode.SshrImm) Y x)) +) + +(decl bitrev (Value) Inst) +(extractor + (bitrev x) + (inst_data (InstructionData.Unary (Opcode.Bitrev) x)) +) + +(decl clz (Value) Inst) +(extractor + (clz x) + (inst_data (InstructionData.Unary (Opcode.Clz) x)) +) + +(decl cls (Value) Inst) +(extractor + (cls x) + (inst_data (InstructionData.Unary (Opcode.Cls) x)) +) + +(decl ctz (Value) Inst) +(extractor + (ctz x) + (inst_data (InstructionData.Unary (Opcode.Ctz) x)) +) + +(decl popcnt (Value) Inst) +(extractor + (popcnt x) + (inst_data (InstructionData.Unary (Opcode.Popcnt) x)) +) + +(decl fcmp (FloatCC Value Value) Inst) +(extractor + (fcmp Cond x y) + (inst_data (InstructionData.FloatCompare (Opcode.Fcmp) Cond (value_array_2 x y))) +) + +(decl ffcmp (Value Value) Inst) +(extractor + (ffcmp x y) + (inst_data (InstructionData.Binary (Opcode.Ffcmp) (value_array_2 x y))) +) + +(decl fadd (Value Value) Inst) +(extractor + (fadd x y) + (inst_data (InstructionData.Binary (Opcode.Fadd) (value_array_2 x y))) +) + +(decl fsub (Value Value) Inst) +(extractor + (fsub x y) + (inst_data (InstructionData.Binary (Opcode.Fsub) (value_array_2 x y))) +) + +(decl fmul (Value Value) Inst) +(extractor + (fmul x y) + (inst_data (InstructionData.Binary (Opcode.Fmul) (value_array_2 x y))) +) + +(decl fdiv (Value Value) Inst) +(extractor + (fdiv x y) + (inst_data (InstructionData.Binary (Opcode.Fdiv) (value_array_2 x y))) +) + +(decl sqrt (Value) Inst) +(extractor + (sqrt x) + (inst_data (InstructionData.Unary (Opcode.Sqrt) x)) +) + +(decl fma (Value Value Value) Inst) +(extractor + (fma x y z) + (inst_data (InstructionData.Ternary (Opcode.Fma) (value_array_3 x y z))) +) + +(decl fneg (Value) Inst) +(extractor + (fneg x) + (inst_data (InstructionData.Unary (Opcode.Fneg) x)) +) + +(decl fabs (Value) Inst) +(extractor + (fabs x) + (inst_data (InstructionData.Unary (Opcode.Fabs) x)) +) + +(decl fcopysign (Value Value) Inst) +(extractor + (fcopysign x y) + (inst_data (InstructionData.Binary (Opcode.Fcopysign) (value_array_2 x y))) +) + +(decl fmin (Value Value) Inst) +(extractor + (fmin x y) + (inst_data (InstructionData.Binary (Opcode.Fmin) (value_array_2 x y))) +) + +(decl fmin_pseudo (Value Value) Inst) +(extractor + (fmin_pseudo x y) + (inst_data (InstructionData.Binary (Opcode.FminPseudo) (value_array_2 x y))) +) + +(decl fmax (Value Value) Inst) +(extractor + (fmax x y) + (inst_data (InstructionData.Binary (Opcode.Fmax) (value_array_2 x y))) +) + +(decl fmax_pseudo (Value Value) Inst) +(extractor + (fmax_pseudo x y) + (inst_data (InstructionData.Binary (Opcode.FmaxPseudo) (value_array_2 x y))) +) + +(decl ceil (Value) Inst) +(extractor + (ceil x) + (inst_data (InstructionData.Unary (Opcode.Ceil) x)) +) + +(decl floor (Value) Inst) +(extractor + (floor x) + (inst_data (InstructionData.Unary (Opcode.Floor) x)) +) + +(decl trunc (Value) Inst) +(extractor + (trunc x) + (inst_data (InstructionData.Unary (Opcode.Trunc) x)) +) + +(decl nearest (Value) Inst) +(extractor + (nearest x) + (inst_data (InstructionData.Unary (Opcode.Nearest) x)) +) + +(decl is_null (Value) Inst) +(extractor + (is_null x) + (inst_data (InstructionData.Unary (Opcode.IsNull) x)) +) + +(decl is_invalid (Value) Inst) +(extractor + (is_invalid x) + (inst_data (InstructionData.Unary (Opcode.IsInvalid) x)) +) + +(decl trueif (IntCC Value) Inst) +(extractor + (trueif Cond f) + (inst_data (InstructionData.IntCond (Opcode.Trueif) Cond f)) +) + +(decl trueff (FloatCC Value) Inst) +(extractor + (trueff Cond f) + (inst_data (InstructionData.FloatCond (Opcode.Trueff) Cond f)) +) + +(decl bitcast (Value) Inst) +(extractor + (bitcast x) + (inst_data (InstructionData.Unary (Opcode.Bitcast) x)) +) + +(decl raw_bitcast (Value) Inst) +(extractor + (raw_bitcast x) + (inst_data (InstructionData.Unary (Opcode.RawBitcast) x)) +) + +(decl scalar_to_vector (Value) Inst) +(extractor + (scalar_to_vector s) + (inst_data (InstructionData.Unary (Opcode.ScalarToVector) s)) +) + +(decl breduce (Value) Inst) +(extractor + (breduce x) + (inst_data (InstructionData.Unary (Opcode.Breduce) x)) +) + +(decl bextend (Value) Inst) +(extractor + (bextend x) + (inst_data (InstructionData.Unary (Opcode.Bextend) x)) +) + +(decl bint (Value) Inst) +(extractor + (bint x) + (inst_data (InstructionData.Unary (Opcode.Bint) x)) +) + +(decl bmask (Value) Inst) +(extractor + (bmask x) + (inst_data (InstructionData.Unary (Opcode.Bmask) x)) +) + +(decl ireduce (Value) Inst) +(extractor + (ireduce x) + (inst_data (InstructionData.Unary (Opcode.Ireduce) x)) +) + +(decl snarrow (Value Value) Inst) +(extractor + (snarrow x y) + (inst_data (InstructionData.Binary (Opcode.Snarrow) (value_array_2 x y))) +) + +(decl unarrow (Value Value) Inst) +(extractor + (unarrow x y) + (inst_data (InstructionData.Binary (Opcode.Unarrow) (value_array_2 x y))) +) + +(decl uunarrow (Value Value) Inst) +(extractor + (uunarrow x y) + (inst_data (InstructionData.Binary (Opcode.Uunarrow) (value_array_2 x y))) +) + +(decl swiden_low (Value) Inst) +(extractor + (swiden_low x) + (inst_data (InstructionData.Unary (Opcode.SwidenLow) x)) +) + +(decl swiden_high (Value) Inst) +(extractor + (swiden_high x) + (inst_data (InstructionData.Unary (Opcode.SwidenHigh) x)) +) + +(decl uwiden_low (Value) Inst) +(extractor + (uwiden_low x) + (inst_data (InstructionData.Unary (Opcode.UwidenLow) x)) +) + +(decl uwiden_high (Value) Inst) +(extractor + (uwiden_high x) + (inst_data (InstructionData.Unary (Opcode.UwidenHigh) x)) +) + +(decl iadd_pairwise (Value Value) Inst) +(extractor + (iadd_pairwise x y) + (inst_data (InstructionData.Binary (Opcode.IaddPairwise) (value_array_2 x y))) +) + +(decl widening_pairwise_dot_product_s (Value Value) Inst) +(extractor + (widening_pairwise_dot_product_s x y) + (inst_data (InstructionData.Binary (Opcode.WideningPairwiseDotProductS) (value_array_2 x y))) +) + +(decl uextend (Value) Inst) +(extractor + (uextend x) + (inst_data (InstructionData.Unary (Opcode.Uextend) x)) +) + +(decl sextend (Value) Inst) +(extractor + (sextend x) + (inst_data (InstructionData.Unary (Opcode.Sextend) x)) +) + +(decl fpromote (Value) Inst) +(extractor + (fpromote x) + (inst_data (InstructionData.Unary (Opcode.Fpromote) x)) +) + +(decl fdemote (Value) Inst) +(extractor + (fdemote x) + (inst_data (InstructionData.Unary (Opcode.Fdemote) x)) +) + +(decl fvdemote (Value) Inst) +(extractor + (fvdemote x) + (inst_data (InstructionData.Unary (Opcode.Fvdemote) x)) +) + +(decl fvpromote_low (Value) Inst) +(extractor + (fvpromote_low a) + (inst_data (InstructionData.Unary (Opcode.FvpromoteLow) a)) +) + +(decl fcvt_to_uint (Value) Inst) +(extractor + (fcvt_to_uint x) + (inst_data (InstructionData.Unary (Opcode.FcvtToUint) x)) +) + +(decl fcvt_to_uint_sat (Value) Inst) +(extractor + (fcvt_to_uint_sat x) + (inst_data (InstructionData.Unary (Opcode.FcvtToUintSat) x)) +) + +(decl fcvt_to_sint (Value) Inst) +(extractor + (fcvt_to_sint x) + (inst_data (InstructionData.Unary (Opcode.FcvtToSint) x)) +) + +(decl fcvt_to_sint_sat (Value) Inst) +(extractor + (fcvt_to_sint_sat x) + (inst_data (InstructionData.Unary (Opcode.FcvtToSintSat) x)) +) + +(decl fcvt_from_uint (Value) Inst) +(extractor + (fcvt_from_uint x) + (inst_data (InstructionData.Unary (Opcode.FcvtFromUint) x)) +) + +(decl fcvt_from_sint (Value) Inst) +(extractor + (fcvt_from_sint x) + (inst_data (InstructionData.Unary (Opcode.FcvtFromSint) x)) +) + +(decl fcvt_low_from_sint (Value) Inst) +(extractor + (fcvt_low_from_sint x) + (inst_data (InstructionData.Unary (Opcode.FcvtLowFromSint) x)) +) + +(decl isplit (Value) Inst) +(extractor + (isplit x) + (inst_data (InstructionData.Unary (Opcode.Isplit) x)) +) + +(decl iconcat (Value Value) Inst) +(extractor + (iconcat lo hi) + (inst_data (InstructionData.Binary (Opcode.Iconcat) (value_array_2 lo hi))) +) + +(decl atomic_rmw (MemFlags AtomicRmwOp Value Value) Inst) +(extractor + (atomic_rmw MemFlags AtomicRmwOp p x) + (inst_data (InstructionData.AtomicRmw (Opcode.AtomicRmw) MemFlags AtomicRmwOp (value_array_2 p x))) +) + +(decl atomic_cas (MemFlags Value Value Value) Inst) +(extractor + (atomic_cas MemFlags p e x) + (inst_data (InstructionData.AtomicCas (Opcode.AtomicCas) MemFlags (value_array_3 p e x))) +) + +(decl atomic_load (MemFlags Value) Inst) +(extractor + (atomic_load MemFlags p) + (inst_data (InstructionData.LoadNoOffset (Opcode.AtomicLoad) MemFlags p)) +) + +(decl atomic_store (MemFlags Value Value) Inst) +(extractor + (atomic_store MemFlags x p) + (inst_data (InstructionData.StoreNoOffset (Opcode.AtomicStore) MemFlags (value_array_2 x p))) +) + +(decl fence () Inst) +(extractor + (fence ) + (inst_data (InstructionData.NullAry (Opcode.Fence))) +) + diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle new file mode 100644 index 0000000000..e89ccc3bcc --- /dev/null +++ b/cranelift/codegen/src/isa/x64/inst.isle @@ -0,0 +1,933 @@ +;; Extern type definitions and constructors for the x64 `MachInst` type. + +;;;; `MInst` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(type MInst extern + (enum (Nop (len u8)) + (AluRmiR (size OperandSize) + (op AluRmiROpcode) + (src1 Reg) + (src2 RegMemImm) + (dst WritableReg)) + (XmmRmR (op SseOpcode) + (src1 Reg) + (src2 RegMem) + (dst WritableReg)) + (XmmUnaryRmR (op SseOpcode) + (src RegMem) + (dst WritableReg)) + (XmmRmiReg (opcode SseOpcode) + (src1 Reg) + (src2 RegMemImm) + (dst WritableReg)) + (XmmRmRImm (op SseOpcode) + (src1 Reg) + (src2 RegMem) + (dst WritableReg) + (imm u8) + (size OperandSize)) + (CmpRmiR (size OperandSize) + (opcode CmpOpcode) + (src RegMemImm) + (dst Reg)) + (Imm (dst_size OperandSize) + (simm64 u64) + (dst WritableReg)) + (ShiftR (size OperandSize) + (kind ShiftKind) + (src Reg) + (num_bits Imm8Reg) + (dst WritableReg)) + (MovzxRmR (ext_mode ExtMode) + (src RegMem) + (dst WritableReg)) + (MovsxRmR (ext_mode ExtMode) + (src RegMem) + (dst WritableReg)) + (Cmove (size OperandSize) + (cc CC) + (consequent RegMem) + (alternative Reg) + (dst WritableReg)) + (XmmRmREvex (op Avx512Opcode) + (src1 RegMem) + (src2 Reg) + (dst WritableReg)))) + +(type OperandSize extern + (enum Size8 + Size16 + Size32 + Size64)) + +;; Get the `OperandSize` for a given `Type`. +(decl operand_size_of_type (Type) OperandSize) +(extern constructor operand_size_of_type operand_size_of_type) + +;; Get the bit width of an `OperandSize`. +(decl operand_size_bits (OperandSize) u16) +(rule (operand_size_bits (OperandSize.Size8)) 8) +(rule (operand_size_bits (OperandSize.Size16)) 16) +(rule (operand_size_bits (OperandSize.Size32)) 32) +(rule (operand_size_bits (OperandSize.Size64)) 64) + +(type AluRmiROpcode extern + (enum Add + Adc + Sub + Sbb + And + Or + Xor + Mul + And8 + Or8)) + +(type SseOpcode extern + (enum Addps + Addpd + Addss + Addsd + Andps + Andpd + Andnps + Andnpd + Blendvpd + Blendvps + Comiss + Comisd + Cmpps + Cmppd + Cmpss + Cmpsd + Cvtdq2ps + Cvtdq2pd + Cvtpd2ps + Cvtps2pd + Cvtsd2ss + Cvtsd2si + Cvtsi2ss + Cvtsi2sd + Cvtss2si + Cvtss2sd + Cvttpd2dq + Cvttps2dq + Cvttss2si + Cvttsd2si + Divps + Divpd + Divss + Divsd + Insertps + Maxps + Maxpd + Maxss + Maxsd + Minps + Minpd + Minss + Minsd + Movaps + Movapd + Movd + Movdqa + Movdqu + Movlhps + Movmskps + Movmskpd + Movq + Movss + Movsd + Movups + Movupd + Mulps + Mulpd + Mulss + Mulsd + Orps + Orpd + Pabsb + Pabsw + Pabsd + Packssdw + Packsswb + Packusdw + Packuswb + Paddb + Paddd + Paddq + Paddw + Paddsb + Paddsw + Paddusb + Paddusw + Palignr + Pand + Pandn + Pavgb + Pavgw + Pblendvb + Pcmpeqb + Pcmpeqw + Pcmpeqd + Pcmpeqq + Pcmpgtb + Pcmpgtw + Pcmpgtd + Pcmpgtq + Pextrb + Pextrw + Pextrd + Pinsrb + Pinsrw + Pinsrd + Pmaddubsw + Pmaddwd + Pmaxsb + Pmaxsw + Pmaxsd + Pmaxub + Pmaxuw + Pmaxud + Pminsb + Pminsw + Pminsd + Pminub + Pminuw + Pminud + Pmovmskb + Pmovsxbd + Pmovsxbw + Pmovsxbq + Pmovsxwd + Pmovsxwq + Pmovsxdq + Pmovzxbd + Pmovzxbw + Pmovzxbq + Pmovzxwd + Pmovzxwq + Pmovzxdq + Pmuldq + Pmulhw + Pmulhuw + Pmulhrsw + Pmulld + Pmullw + Pmuludq + Por + Pshufb + Pshufd + Psllw + Pslld + Psllq + Psraw + Psrad + Psrlw + Psrld + Psrlq + Psubb + Psubd + Psubq + Psubw + Psubsb + Psubsw + Psubusb + Psubusw + Ptest + Punpckhbw + Punpckhwd + Punpcklbw + Punpcklwd + Pxor + Rcpss + Roundps + Roundpd + Roundss + Roundsd + Rsqrtss + Shufps + Sqrtps + Sqrtpd + Sqrtss + Sqrtsd + Subps + Subpd + Subss + Subsd + Ucomiss + Ucomisd + Unpcklps + Xorps + Xorpd)) + +(type CmpOpcode extern + (enum Cmp + Test)) + +(type RegMemImm extern + (enum + (Reg (reg Reg)) + (Mem (addr SyntheticAmode)) + (Imm (simm32 u32)))) + +(type RegMem extern + (enum + (Reg (reg Reg)) + (Mem (addr SyntheticAmode)))) + +;; Put the given clif value into a `RegMem` operand. +;; +;; Asserts that the value fits into a single register, and doesn't require +;; multiple registers for its representation (like `i128` for example). +;; +;; As a side effect, this marks the value as used. +(decl put_in_reg_mem (Value) RegMem) +(extern constructor put_in_reg_mem put_in_reg_mem) + +(type SyntheticAmode extern (enum)) + +(type ShiftKind extern + (enum ShiftLeft + ShiftRightLogical + ShiftRightArithmetic + RotateLeft + RotateRight)) + +(type Imm8Reg extern + (enum (Imm8 (imm u8)) + (Reg (reg Reg)))) + +(type CC extern + (enum O + NO + B + NB + Z + NZ + BE + NBE + S + NS + L + NL + LE + NLE + P + NP)) + +(type Avx512Opcode extern + (enum Vcvtudq2ps + Vpabsq + Vpermi2b + Vpmullq + Vpopcntb)) + +;;;; Helpers for Querying Enabled ISA Extensions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(decl avx512vl_enabled () Type) +(extern extractor avx512vl_enabled avx512vl_enabled) + +(decl avx512dq_enabled () Type) +(extern extractor avx512dq_enabled avx512dq_enabled) + +;;;; Helpers for Merging and Sinking Immediates/Loads ;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Extract a constant `Imm8Reg.Imm8` from a value operand. +(decl imm8_from_value (Imm8Reg) Value) +(extern extractor imm8_from_value imm8_from_value) + +;; Extract a constant `RegMemImm.Imm` from a value operand. +(decl simm32_from_value (RegMemImm) Value) +(extern extractor simm32_from_value simm32_from_value) + +;; Extract a constant `RegMemImm.Imm` from an `Imm64` immediate. +(decl simm32_from_imm64 (RegMemImm) Imm64) +(extern extractor simm32_from_imm64 simm32_from_imm64) + +;; A load that can be sunk into another operation. +(type SinkableLoad extern (enum)) + +;; Extract a `SinkableLoad` that works with `RegMemImm.Mem` from a value +;; operand. +(decl sinkable_load (SinkableLoad) Value) +(extern extractor sinkable_load sinkable_load) + +;; Sink a `SinkableLoad` into a `RegMemImm.Mem`. +;; +;; This is a side-effectful operation that notifies the context that the +;; instruction that produced the `SinkableImm` has been sunk into another +;; instruction, and no longer needs to be lowered. +(decl sink_load (SinkableLoad) RegMemImm) +(extern constructor sink_load sink_load) + +;;;; Helpers for Working with Flags ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Newtype wrapper around `MInst` for instructions that are used for their +;; effect on flags. +(type ProducesFlags (enum (ProducesFlags (inst MInst) (result Reg)))) + +;; Newtype wrapper around `MInst` for instructions that consume flags. +(type ConsumesFlags (enum (ConsumesFlags (inst MInst) (result Reg)))) + +;; Combine flags-producing and -consuming instructions together, ensuring that +;; they are emitted back-to-back and no other instructions can be emitted +;; between them and potentially clobber the flags. +;; +;; Returns a `ValueRegs` where the first register is the result of the +;; `ProducesFlags` instruction and the second is the result of the +;; `ConsumesFlags` instruction. +(decl with_flags (ProducesFlags ConsumesFlags) ValueRegs) +(rule (with_flags (ProducesFlags.ProducesFlags producer_inst producer_result) + (ConsumesFlags.ConsumesFlags consumer_inst consumer_result)) + (let ((_x Unit (emit producer_inst)) + (_y Unit (emit consumer_inst))) + (value_regs producer_result consumer_result))) + +;; Like `with_flags` but returns only the result of the consumer operation. +(decl with_flags_1 (ProducesFlags ConsumesFlags) Reg) +(rule (with_flags_1 (ProducesFlags.ProducesFlags producer_inst _producer_result) + (ConsumesFlags.ConsumesFlags consumer_inst consumer_result)) + (let ((_x Unit (emit producer_inst)) + (_y Unit (emit consumer_inst))) + consumer_result)) + +;; Like `with_flags` but allows two consumers of the same flags. The result is a +;; `ValueRegs` containing the first consumer's result and then the second +;; consumer's result. +(decl with_flags_2 (ProducesFlags ConsumesFlags ConsumesFlags) ValueRegs) +(rule (with_flags_2 (ProducesFlags.ProducesFlags producer_inst producer_result) + (ConsumesFlags.ConsumesFlags consumer_inst_1 consumer_result_1) + (ConsumesFlags.ConsumesFlags consumer_inst_2 consumer_result_2)) + (let ((_x Unit (emit producer_inst)) + (_y Unit (emit consumer_inst_1)) + (_z Unit (emit consumer_inst_2))) + (value_regs consumer_result_1 consumer_result_2))) + +;;;; Helpers for Sign/Zero Extending ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(type ExtendKind (enum Sign Zero)) + +(type ExtMode extern (enum BL BQ WL WQ LQ)) + +;; `ExtMode::new` +(decl ext_mode (u16 u16) ExtMode) +(extern constructor ext_mode ext_mode) + +;; Put the given value into a register, but extended as the given type. +(decl extend_to_reg (Value Type ExtendKind) Reg) + +;; If the value is already of the requested type, no extending is necessary. +(rule (extend_to_reg (and val (value_type ty)) =ty _kind) + (put_in_reg val)) + +(rule (extend_to_reg (and val (value_type from_ty)) + to_ty + kind) + (let ((from_bits u16 (ty_bits from_ty)) + ;; Use `operand_size_of_type` so that the we clamp the output to 32- + ;; or 64-bit width types. + (to_bits u16 (operand_size_bits (operand_size_of_type to_ty)))) + (extend kind + to_ty + (ext_mode from_bits to_bits) + (put_in_reg_mem val)))) + +;; Do a sign or zero extension of the given `RegMem`. +(decl extend (ExtendKind Type ExtMode RegMem) Reg) + +;; Zero extending uses `movzx`. +(rule (extend (ExtendKind.Zero) ty mode src) + (movzx ty mode src)) + +;; Sign extending uses `movsx`. +(rule (extend (ExtendKind.Sign) ty mode src) + (movsx ty mode src)) + +;;;; Instruction Constructors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; These constructors create SSA-style `MInst`s. It is their responsibility to +;; maintain the invariant that each temporary register they allocate and define +;; only gets defined the once. + +;; Emit an instruction. +;; +;; This is low-level and side-effectful; it should only be used as an +;; implementation detail by helpers that preserve the SSA facade themselves. +(decl emit (MInst) Unit) +(extern constructor emit emit) + +;; Helper for emitting `MInst.AluRmiR` instructions. +(decl alu_rmi_r (Type AluRmiROpcode Reg RegMemImm) Reg) +(rule (alu_rmi_r ty opcode src1 src2) + (let ((dst WritableReg (temp_writable_reg ty)) + (size OperandSize (operand_size_of_type ty)) + (_ Unit (emit (MInst.AluRmiR size opcode src1 src2 dst)))) + (writable_reg_to_reg dst))) + +;; Helper for emitting `add` instructions. +(decl add (Type Reg RegMemImm) Reg) +(rule (add ty src1 src2) + (alu_rmi_r ty + (AluRmiROpcode.Add) + src1 + src2)) + +;; Helper for creating `add` instructions whose flags are also used. +(decl add_with_flags (Type Reg RegMemImm) ProducesFlags) +(rule (add_with_flags ty src1 src2) + (let ((dst WritableReg (temp_writable_reg ty))) + (ProducesFlags.ProducesFlags (MInst.AluRmiR (operand_size_of_type ty) + (AluRmiROpcode.Add) + src1 + src2 + dst) + (writable_reg_to_reg dst)))) + +;; Helper for creating `adc` instructions. +(decl adc (Type Reg RegMemImm) ConsumesFlags) +(rule (adc ty src1 src2) + (let ((dst WritableReg (temp_writable_reg ty))) + (ConsumesFlags.ConsumesFlags (MInst.AluRmiR (operand_size_of_type ty) + (AluRmiROpcode.Adc) + src1 + src2 + dst) + (writable_reg_to_reg dst)))) + +;; Helper for emitting `sub` instructions. +(decl sub (Type Reg RegMemImm) Reg) +(rule (sub ty src1 src2) + (alu_rmi_r ty + (AluRmiROpcode.Sub) + src1 + src2)) + +;; Helper for creating `sub` instructions whose flags are also used. +(decl sub_with_flags (Type Reg RegMemImm) ProducesFlags) +(rule (sub_with_flags ty src1 src2) + (let ((dst WritableReg (temp_writable_reg ty))) + (ProducesFlags.ProducesFlags (MInst.AluRmiR (operand_size_of_type ty) + (AluRmiROpcode.Sub) + src1 + src2 + dst) + (writable_reg_to_reg dst)))) + +;; Helper for creating `sbb` instructions. +(decl sbb (Type Reg RegMemImm) ConsumesFlags) +(rule (sbb ty src1 src2) + (let ((dst WritableReg (temp_writable_reg ty))) + (ConsumesFlags.ConsumesFlags (MInst.AluRmiR (operand_size_of_type ty) + (AluRmiROpcode.Sbb) + src1 + src2 + dst) + (writable_reg_to_reg dst)))) + +;; Helper for creating `mul` instructions. +(decl mul (Type Reg RegMemImm) Reg) +(rule (mul ty src1 src2) + (alu_rmi_r ty + (AluRmiROpcode.Mul) + src1 + src2)) + +;; Helper for emitting `and` instructions. +;; +;; Use `m_` prefix (short for "mach inst") to disambiguate with the ISLE-builtin +;; `and` operator. +(decl m_and (Type Reg RegMemImm) Reg) +(rule (m_and ty src1 src2) + (alu_rmi_r ty + (AluRmiROpcode.And) + src1 + src2)) + +;; Helper for emitting `or` instructions. +(decl or (Type Reg RegMemImm) Reg) +(rule (or ty src1 src2) + (alu_rmi_r ty + (AluRmiROpcode.Or) + src1 + src2)) + +;; Helper for emitting `xor` instructions. +(decl xor (Type Reg RegMemImm) Reg) +(rule (xor ty src1 src2) + (alu_rmi_r ty + (AluRmiROpcode.Xor) + src1 + src2)) + +;; Helper for emitting immediates. +(decl imm (Type u64) Reg) +(rule (imm ty simm64) + (let ((dst WritableReg (temp_writable_reg ty)) + (size OperandSize (operand_size_of_type ty)) + (_ Unit (emit (MInst.Imm size simm64 dst)))) + (writable_reg_to_reg dst))) + +(decl nonzero_u64_fits_in_u32 (u64) u64) +(extern extractor nonzero_u64_fits_in_u32 nonzero_u64_fits_in_u32) + +;; Special case for when a 64-bit immediate fits into 32-bits. We can use a +;; 32-bit move that zero-extends the value, which has a smaller encoding. +(rule (imm $I64 (nonzero_u64_fits_in_u32 x)) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.Imm (OperandSize.Size32) x dst)))) + (writable_reg_to_reg dst))) + +;; Special case for zero immediates: turn them into an `xor r, r`. +(rule (imm ty 0) + (let ((wr WritableReg (temp_writable_reg ty)) + (r Reg (writable_reg_to_reg wr)) + (size OperandSize (operand_size_of_type ty)) + (_ Unit (emit (MInst.AluRmiR size + (AluRmiROpcode.Xor) + r + (RegMemImm.Reg r) + wr)))) + r)) + +;; Helper for creating `MInst.ShifR` instructions. +(decl shift_r (Type ShiftKind Reg Imm8Reg) Reg) +(rule (shift_r ty kind src1 src2) + (let ((dst WritableReg (temp_writable_reg ty)) + (size OperandSize (operand_size_of_type ty)) + (_ Unit (emit (MInst.ShiftR size kind src1 src2 dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `rotl` instructions (prefixed with "m_", short for "mach +;; inst", to disambiguate this from clif's `rotl`). +(decl m_rotl (Type Reg Imm8Reg) Reg) +(rule (m_rotl ty src1 src2) + (shift_r ty (ShiftKind.RotateLeft) src1 src2)) + +;; Helper for creating `shl` instructions. +(decl shl (Type Reg Imm8Reg) Reg) +(rule (shl ty src1 src2) + (shift_r ty (ShiftKind.ShiftLeft) src1 src2)) + +;; Helper for creating logical shift-right instructions. +(decl shr (Type Reg Imm8Reg) Reg) +(rule (shr ty src1 src2) + (shift_r ty (ShiftKind.ShiftRightLogical) src1 src2)) + +;; Helper for creating arithmetic shift-right instructions. +(decl sar (Type Reg Imm8Reg) Reg) +(rule (sar ty src1 src2) + (shift_r ty (ShiftKind.ShiftRightArithmetic) src1 src2)) + +;; Helper for creating `MInst.CmpRmiR` instructions. +(decl cmp_rmi_r (OperandSize CmpOpcode RegMemImm Reg) ProducesFlags) +(rule (cmp_rmi_r size opcode src1 src2) + (ProducesFlags.ProducesFlags (MInst.CmpRmiR size + opcode + src1 + src2) + (invalid_reg))) + +;; Helper for creating `cmp` instructions. +(decl cmp (OperandSize RegMemImm Reg) ProducesFlags) +(rule (cmp size src1 src2) + (cmp_rmi_r size (CmpOpcode.Cmp) src1 src2)) + +;; Helper for creating `test` instructions. +(decl test (OperandSize RegMemImm Reg) ProducesFlags) +(rule (test size src1 src2) + (cmp_rmi_r size (CmpOpcode.Test) src1 src2)) + +;; Helper for creating `MInst.Cmove` instructions. +(decl cmove (Type CC RegMem Reg) ConsumesFlags) +(rule (cmove ty cc consequent alternative) + (let ((dst WritableReg (temp_writable_reg ty)) + (size OperandSize (operand_size_of_type ty))) + (ConsumesFlags.ConsumesFlags (MInst.Cmove size cc consequent alternative dst) + (writable_reg_to_reg dst)))) + +;; Helper for creating `MInst.MovzxRmR` instructions. +(decl movzx (Type ExtMode RegMem) Reg) +(rule (movzx ty mode src) + (let ((dst WritableReg (temp_writable_reg ty)) + (_ Unit (emit (MInst.MovzxRmR mode src dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `MInst.MovsxRmR` instructions. +(decl movsx (Type ExtMode RegMem) Reg) +(rule (movsx ty mode src) + (let ((dst WritableReg (temp_writable_reg ty)) + (_ Unit (emit (MInst.MovsxRmR mode src dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `MInst.XmmRmR` instructions. +(decl xmm_rm_r (Type SseOpcode Reg RegMem) Reg) +(rule (xmm_rm_r ty op src1 src2) + (let ((dst WritableReg (temp_writable_reg ty)) + (_ Unit (emit (MInst.XmmRmR op src1 src2 dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `paddb` instructions. +(decl paddb (Reg RegMem) Reg) +(rule (paddb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Paddb) src1 src2)) + +;; Helper for creating `paddw` instructions. +(decl paddw (Reg RegMem) Reg) +(rule (paddw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Paddw) src1 src2)) + +;; Helper for creating `paddd` instructions. +(decl paddd (Reg RegMem) Reg) +(rule (paddd src1 src2) + (xmm_rm_r $I32X4 (SseOpcode.Paddd) src1 src2)) + +;; Helper for creating `paddq` instructions. +(decl paddq (Reg RegMem) Reg) +(rule (paddq src1 src2) + (xmm_rm_r $I64X2 (SseOpcode.Paddq) src1 src2)) + +;; Helper for creating `paddsb` instructions. +(decl paddsb (Reg RegMem) Reg) +(rule (paddsb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Paddsb) src1 src2)) + +;; Helper for creating `paddsw` instructions. +(decl paddsw (Reg RegMem) Reg) +(rule (paddsw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Paddsw) src1 src2)) + +;; Helper for creating `paddusb` instructions. +(decl paddusb (Reg RegMem) Reg) +(rule (paddusb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Paddusb) src1 src2)) + +;; Helper for creating `paddusw` instructions. +(decl paddusw (Reg RegMem) Reg) +(rule (paddusw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Paddusw) src1 src2)) + +;; Helper for creating `psubb` instructions. +(decl psubb (Reg RegMem) Reg) +(rule (psubb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Psubb) src1 src2)) + +;; Helper for creating `psubw` instructions. +(decl psubw (Reg RegMem) Reg) +(rule (psubw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Psubw) src1 src2)) + +;; Helper for creating `psubd` instructions. +(decl psubd (Reg RegMem) Reg) +(rule (psubd src1 src2) + (xmm_rm_r $I32X4 (SseOpcode.Psubd) src1 src2)) + +;; Helper for creating `psubq` instructions. +(decl psubq (Reg RegMem) Reg) +(rule (psubq src1 src2) + (xmm_rm_r $I64X2 (SseOpcode.Psubq) src1 src2)) + +;; Helper for creating `psubsb` instructions. +(decl psubsb (Reg RegMem) Reg) +(rule (psubsb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Psubsb) src1 src2)) + +;; Helper for creating `psubsw` instructions. +(decl psubsw (Reg RegMem) Reg) +(rule (psubsw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Psubsw) src1 src2)) + +;; Helper for creating `psubusb` instructions. +(decl psubusb (Reg RegMem) Reg) +(rule (psubusb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Psubusb) src1 src2)) + +;; Helper for creating `psubusw` instructions. +(decl psubusw (Reg RegMem) Reg) +(rule (psubusw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Psubusw) src1 src2)) + +;; Helper for creating `pavgb` instructions. +(decl pavgb (Reg RegMem) Reg) +(rule (pavgb src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Pavgb) src1 src2)) + +;; Helper for creating `pavgw` instructions. +(decl pavgw (Reg RegMem) Reg) +(rule (pavgw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Pavgw) src1 src2)) + +;; Helper for creating `pand` instructions. +(decl pand (Reg RegMem) Reg) +(rule (pand src1 src2) + (xmm_rm_r $F32X4 (SseOpcode.Pand) src1 src2)) + +;; Helper for creating `andps` instructions. +(decl andps (Reg RegMem) Reg) +(rule (andps src1 src2) + (xmm_rm_r $F32X4 (SseOpcode.Andps) src1 src2)) + +;; Helper for creating `andpd` instructions. +(decl andpd (Reg RegMem) Reg) +(rule (andpd src1 src2) + (xmm_rm_r $F64X2 (SseOpcode.Andpd) src1 src2)) + +;; Helper for creating `por` instructions. +(decl por (Reg RegMem) Reg) +(rule (por src1 src2) + (xmm_rm_r $F32X4 (SseOpcode.Por) src1 src2)) + +;; Helper for creating `orps` instructions. +(decl orps (Reg RegMem) Reg) +(rule (orps src1 src2) + (xmm_rm_r $F32X4 (SseOpcode.Orps) src1 src2)) + +;; Helper for creating `orpd` instructions. +(decl orpd (Reg RegMem) Reg) +(rule (orpd src1 src2) + (xmm_rm_r $F64X2 (SseOpcode.Orpd) src1 src2)) + +;; Helper for creating `pxor` instructions. +(decl pxor (Reg RegMem) Reg) +(rule (pxor src1 src2) + (xmm_rm_r $I8X16 (SseOpcode.Pxor) src1 src2)) + +;; Helper for creating `xorps` instructions. +(decl xorps (Reg RegMem) Reg) +(rule (xorps src1 src2) + (xmm_rm_r $F32X4 (SseOpcode.Xorps) src1 src2)) + +;; Helper for creating `xorpd` instructions. +(decl xorpd (Reg RegMem) Reg) +(rule (xorpd src1 src2) + (xmm_rm_r $F64X2 (SseOpcode.Xorpd) src1 src2)) + +;; Helper for creating `pmullw` instructions. +(decl pmullw (Reg RegMem) Reg) +(rule (pmullw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Pmullw) src1 src2)) + +;; Helper for creating `pmulld` instructions. +(decl pmulld (Reg RegMem) Reg) +(rule (pmulld src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Pmulld) src1 src2)) + +;; Helper for creating `pmulhw` instructions. +(decl pmulhw (Reg RegMem) Reg) +(rule (pmulhw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Pmulhw) src1 src2)) + +;; Helper for creating `pmulhuw` instructions. +(decl pmulhuw (Reg RegMem) Reg) +(rule (pmulhuw src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Pmulhuw) src1 src2)) + +;; Helper for creating `pmuldq` instructions. +(decl pmuldq (Reg RegMem) Reg) +(rule (pmuldq src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Pmuldq) src1 src2)) + +;; Helper for creating `pmuludq` instructions. +(decl pmuludq (Reg RegMem) Reg) +(rule (pmuludq src1 src2) + (xmm_rm_r $I64X2 (SseOpcode.Pmuludq) src1 src2)) + +;; Helper for creating `punpckhwd` instructions. +(decl punpckhwd (Reg RegMem) Reg) +(rule (punpckhwd src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Punpckhwd) src1 src2)) + +;; Helper for creating `punpcklwd` instructions. +(decl punpcklwd (Reg RegMem) Reg) +(rule (punpcklwd src1 src2) + (xmm_rm_r $I16X8 (SseOpcode.Punpcklwd) src1 src2)) + +;; Helper for creating `MInst.XmmRmRImm` instructions. +(decl xmm_rm_r_imm (SseOpcode Reg RegMem u8 OperandSize) Reg) +(rule (xmm_rm_r_imm op src1 src2 imm size) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.XmmRmRImm op + src1 + src2 + dst + imm + size)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `palignr` instructions. +(decl palignr (Reg RegMem u8 OperandSize) Reg) +(rule (palignr src1 src2 imm size) + (xmm_rm_r_imm (SseOpcode.Palignr) + src1 + src2 + imm + size)) + +;; Helper for creating `pshufd` instructions. +(decl pshufd (RegMem u8 OperandSize) Reg) +(rule (pshufd src imm size) + (let ((w_dst WritableReg (temp_writable_reg $I8X16)) + (dst Reg (writable_reg_to_reg w_dst)) + (_ Unit (emit (MInst.XmmRmRImm (SseOpcode.Pshufd) + dst + src + w_dst + imm + size)))) + dst)) + +;; Helper for creating `MInst.XmmUnaryRmR` instructions. +(decl xmm_unary_rm_r (SseOpcode RegMem) Reg) +(rule (xmm_unary_rm_r op src) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.XmmUnaryRmR op src dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `pmovsxbw` instructions. +(decl pmovsxbw (RegMem) Reg) +(rule (pmovsxbw src) + (xmm_unary_rm_r (SseOpcode.Pmovsxbw) src)) + +;; Helper for creating `pmovzxbw` instructions. +(decl pmovzxbw (RegMem) Reg) +(rule (pmovzxbw src) + (xmm_unary_rm_r (SseOpcode.Pmovzxbw) src)) + +;; Helper for creating `MInst.XmmRmREvex` instructions. +(decl xmm_rm_r_evex (Avx512Opcode RegMem Reg) Reg) +(rule (xmm_rm_r_evex op src1 src2) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.XmmRmREvex op + src1 + src2 + dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `vpmullq` instructions. +;; +;; Requires AVX-512 vl and dq. +(decl vpmullq (RegMem Reg) Reg) +(rule (vpmullq src1 src2) + (xmm_rm_r_evex (Avx512Opcode.Vpmullq) + src1 + src2)) + +;; Helper for creating `MInst.XmmRmiReg` instructions. +(decl xmm_rmi_reg (SseOpcode Reg RegMemImm) Reg) +(rule (xmm_rmi_reg op src1 src2) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.XmmRmiReg op + src1 + src2 + dst)))) + (writable_reg_to_reg dst))) + +;; Helper for creating `psllq` instructions. +(decl psllq (Reg RegMemImm) Reg) +(rule (psllq src1 src2) + (xmm_rmi_reg (SseOpcode.Psllq) src1 src2)) + +;; Helper for creating `psrlq` instructions. +(decl psrlq (Reg RegMemImm) Reg) +(rule (psrlq src1 src2) + (xmm_rmi_reg (SseOpcode.Psrlq) src1 src2)) diff --git a/cranelift/codegen/src/isa/x64/inst/args.rs b/cranelift/codegen/src/isa/x64/inst/args.rs index f279ee9096..e7aaf108fe 100644 --- a/cranelift/codegen/src/isa/x64/inst/args.rs +++ b/cranelift/codegen/src/isa/x64/inst/args.rs @@ -1,14 +1,13 @@ //! Instruction operand sub-components (aka "parts"): definitions and printing. use super::regs::{self, show_ireg_sized}; -use super::EmitState; +use super::{EmitState, RegMapper}; use crate::ir::condcodes::{FloatCC, IntCC}; use crate::ir::{MemFlags, Type}; use crate::isa::x64::inst::Inst; use crate::machinst::*; use regalloc::{ - PrettyPrint, PrettyPrintSized, RealRegUniverse, Reg, RegClass, RegUsageCollector, - RegUsageMapper, Writable, + PrettyPrint, PrettyPrintSized, RealRegUniverse, Reg, RegClass, RegUsageCollector, Writable, }; use smallvec::{smallvec, SmallVec}; use std::fmt; @@ -175,7 +174,7 @@ impl SyntheticAmode { } } - pub(crate) fn map_uses(&mut self, map: &RUM) { + pub(crate) fn map_uses(&mut self, map: &RM) { match self { SyntheticAmode::Real(addr) => addr.map_uses(map), SyntheticAmode::NominalSPOffset { .. } => { @@ -285,6 +284,25 @@ impl PrettyPrintSized for RegMemImm { } } +/// An operand which is either an 8-bit integer immediate or a register. +#[derive(Clone, Debug)] +pub enum Imm8Reg { + Imm8 { imm: u8 }, + Reg { reg: Reg }, +} + +impl From for Imm8Reg { + fn from(imm: u8) -> Self { + Self::Imm8 { imm } + } +} + +impl From for Imm8Reg { + fn from(reg: Reg) -> Self { + Self::Reg { reg } + } +} + /// An operand which is either an integer Register or a value in Memory. This can denote an 8, 16, /// 32, 64, or 128 bit value. #[derive(Clone, Debug)] diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs index 93132a2aab..53f67d7346 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit.rs @@ -147,14 +147,16 @@ pub(crate) fn emit( Inst::AluRmiR { size, op, - src, + src1, + src2, dst: reg_g, } => { + debug_assert_eq!(*src1, reg_g.to_reg()); let mut rex = RexFlags::from(*size); if *op == AluRmiROpcode::Mul { // We kinda freeloaded Mul into RMI_R_Op, but it doesn't fit the usual pattern, so // we have to special-case it. - match src { + match src2 { RegMemImm::Reg { reg: reg_e } => { emit_std_reg_reg( sink, @@ -213,7 +215,7 @@ pub(crate) fn emit( }; assert!(!(is_8bit && *size == OperandSize::Size64)); - match src { + match src2 { RegMemImm::Reg { reg: reg_e } => { if is_8bit { rex.always_emit_if_8bit_needed(*reg_e); @@ -323,8 +325,9 @@ pub(crate) fn emit( } } - Inst::Not { size, src } => { - let rex_flags = RexFlags::from((*size, src.to_reg())); + Inst::Not { size, src, dst } => { + debug_assert_eq!(*src, dst.to_reg()); + let rex_flags = RexFlags::from((*size, dst.to_reg())); let (opcode, prefix) = match size { OperandSize::Size8 => (0xF6, LegacyPrefixes::None), OperandSize::Size16 => (0xF7, LegacyPrefixes::_66), @@ -333,12 +336,13 @@ pub(crate) fn emit( }; let subopcode = 2; - let enc_src = int_reg_enc(src.to_reg()); + let enc_src = int_reg_enc(dst.to_reg()); emit_std_enc_enc(sink, prefix, opcode, 1, subopcode, enc_src, rex_flags) } - Inst::Neg { size, src } => { - let rex_flags = RexFlags::from((*size, src.to_reg())); + Inst::Neg { size, src, dst } => { + debug_assert_eq!(*src, dst.to_reg()); + let rex_flags = RexFlags::from((*size, dst.to_reg())); let (opcode, prefix) = match size { OperandSize::Size8 => (0xF6, LegacyPrefixes::None), OperandSize::Size16 => (0xF7, LegacyPrefixes::_66), @@ -347,15 +351,21 @@ pub(crate) fn emit( }; let subopcode = 3; - let enc_src = int_reg_enc(src.to_reg()); + let enc_src = int_reg_enc(dst.to_reg()); emit_std_enc_enc(sink, prefix, opcode, 1, subopcode, enc_src, rex_flags) } Inst::Div { size, signed, + dividend, divisor, + dst_quotient, + dst_remainder, } => { + debug_assert_eq!(*dividend, regs::rax()); + debug_assert_eq!(dst_quotient.to_reg(), regs::rax()); + debug_assert_eq!(dst_remainder.to_reg(), regs::rdx()); let (opcode, prefix) = match size { OperandSize::Size8 => (0xF6, LegacyPrefixes::None), OperandSize::Size16 => (0xF7, LegacyPrefixes::_66), @@ -397,7 +407,18 @@ pub(crate) fn emit( } } - Inst::MulHi { size, signed, rhs } => { + Inst::MulHi { + size, + signed, + src1, + src2, + dst_lo, + dst_hi, + } => { + debug_assert_eq!(*src1, regs::rax()); + debug_assert_eq!(dst_lo.to_reg(), regs::rax()); + debug_assert_eq!(dst_hi.to_reg(), regs::rdx()); + let rex_flags = RexFlags::from(*size); let prefix = match size { OperandSize::Size16 => LegacyPrefixes::_66, @@ -407,7 +428,7 @@ pub(crate) fn emit( }; let subopcode = if *signed { 5 } else { 4 }; - match rhs { + match src2 { RegMem::Reg { reg } => { let src = int_reg_enc(*reg); emit_std_enc_enc(sink, prefix, 0xF7, 1, subopcode, src, rex_flags) @@ -421,28 +442,39 @@ pub(crate) fn emit( } } - Inst::SignExtendData { size } => match size { - OperandSize::Size8 => { - sink.put1(0x66); - sink.put1(0x98); + Inst::SignExtendData { size, src, dst } => { + debug_assert_eq!(*src, regs::rax()); + debug_assert_eq!(dst.to_reg(), regs::rdx()); + match size { + OperandSize::Size8 => { + sink.put1(0x66); + sink.put1(0x98); + } + OperandSize::Size16 => { + sink.put1(0x66); + sink.put1(0x99); + } + OperandSize::Size32 => sink.put1(0x99), + OperandSize::Size64 => { + sink.put1(0x48); + sink.put1(0x99); + } } - OperandSize::Size16 => { - sink.put1(0x66); - sink.put1(0x99); - } - OperandSize::Size32 => sink.put1(0x99), - OperandSize::Size64 => { - sink.put1(0x48); - sink.put1(0x99); - } - }, + } Inst::CheckedDivOrRemSeq { kind, size, + dividend, divisor, tmp, + dst_quotient, + dst_remainder, } => { + debug_assert_eq!(*dividend, regs::rax()); + debug_assert_eq!(dst_quotient.to_reg(), regs::rax()); + debug_assert_eq!(dst_remainder.to_reg(), regs::rdx()); + // Generates the following code sequence: // // ;; check divide by zero: @@ -792,9 +824,11 @@ pub(crate) fn emit( Inst::ShiftR { size, kind, + src, num_bits, dst, } => { + debug_assert_eq!(*src, dst.to_reg()); let subopcode = match kind { ShiftKind::RotateLeft => 0, ShiftKind::RotateRight => 1, @@ -805,7 +839,8 @@ pub(crate) fn emit( let enc_dst = int_reg_enc(dst.to_reg()); let rex_flags = RexFlags::from((*size, dst.to_reg())); match num_bits { - None => { + Imm8Reg::Reg { reg } => { + debug_assert_eq!(*reg, regs::rcx()); let (opcode, prefix) = match size { OperandSize::Size8 => (0xD2, LegacyPrefixes::None), OperandSize::Size16 => (0xD3, LegacyPrefixes::_66), @@ -820,7 +855,7 @@ pub(crate) fn emit( emit_std_enc_enc(sink, prefix, opcode, 1, subopcode, enc_dst, rex_flags); } - Some(num_bits) => { + Imm8Reg::Imm8 { imm: num_bits } => { let (opcode, prefix) = match size { OperandSize::Size8 => (0xC0, LegacyPrefixes::None), OperandSize::Size16 => (0xC1, LegacyPrefixes::_66), @@ -840,10 +875,16 @@ pub(crate) fn emit( } } - Inst::XmmRmiReg { opcode, src, dst } => { + Inst::XmmRmiReg { + opcode, + src1, + src2, + dst, + } => { + debug_assert_eq!(*src1, dst.to_reg()); let rex = RexFlags::clear_w(); let prefix = LegacyPrefixes::_66; - if let RegMemImm::Imm { simm32 } = src { + if let RegMemImm::Imm { simm32 } = src2 { let (opcode_bytes, reg_digit) = match opcode { SseOpcode::Psllw => (0x0F71, 6), SseOpcode::Pslld => (0x0F72, 6), @@ -874,7 +915,7 @@ pub(crate) fn emit( _ => panic!("invalid opcode: {}", opcode), }; - match src { + match src2 { RegMemImm::Reg { reg } => { emit_std_reg_reg(sink, prefix, opcode_bytes, 2, dst.to_reg(), *reg, rex); } @@ -993,9 +1034,11 @@ pub(crate) fn emit( Inst::Cmove { size, cc, - src, + consequent, + alternative, dst: reg_g, } => { + debug_assert_eq!(*alternative, reg_g.to_reg()); let rex_flags = RexFlags::from(*size); let prefix = match size { OperandSize::Size16 => LegacyPrefixes::_66, @@ -1004,7 +1047,7 @@ pub(crate) fn emit( _ => unreachable!("invalid size spec for cmove"), }; let opcode = 0x0F40 + cc.get_enc() as u32; - match src { + match consequent { RegMem::Reg { reg: reg_e } => { emit_std_reg_reg(sink, prefix, opcode, 2, reg_g.to_reg(), *reg_e, rex_flags); } @@ -1433,9 +1476,11 @@ pub(crate) fn emit( Inst::XmmRmR { op, - src: src_e, + src1, + src2: src_e, dst: reg_g, } => { + debug_assert_eq!(*src1, reg_g.to_reg()); let rex = RexFlags::clear_w(); let (prefix, opcode, length) = match op { SseOpcode::Addps => (LegacyPrefixes::None, 0x0F58, 2), @@ -1678,11 +1723,13 @@ pub(crate) fn emit( Inst::XmmRmRImm { op, - src, + src1, + src2, dst, imm, size, } => { + debug_assert_eq!(*src1, dst.to_reg()); let (prefix, opcode, len) = match op { SseOpcode::Cmpps => (LegacyPrefixes::None, 0x0FC2, 2), SseOpcode::Cmppd => (LegacyPrefixes::_66, 0x0FC2, 2), @@ -1713,7 +1760,7 @@ pub(crate) fn emit( // `src` in ModRM's r/m field. _ => false, }; - match src { + match src2 { RegMem::Reg { reg } => { if regs_swapped { emit_std_reg_reg(sink, prefix, opcode, len, *reg, dst.to_reg(), rex); @@ -2403,8 +2450,17 @@ pub(crate) fn emit( } } - Inst::LockCmpxchg { ty, src, dst } => { - // lock cmpxchg{b,w,l,q} %src, (dst) + Inst::LockCmpxchg { + ty, + replacement, + expected, + mem, + dst_old, + } => { + debug_assert_eq!(*expected, regs::rax()); + debug_assert_eq!(dst_old.to_reg(), regs::rax()); + + // lock cmpxchg{b,w,l,q} %replacement, (mem) // Note that 0xF0 is the Lock prefix. let (prefix, opcodes) = match *ty { types::I8 => (LegacyPrefixes::_F0, 0x0FB0), @@ -2413,12 +2469,34 @@ pub(crate) fn emit( types::I64 => (LegacyPrefixes::_F0, 0x0FB1), _ => unreachable!(), }; - let rex = RexFlags::from((OperandSize::from_ty(*ty), *src)); - let amode = dst.finalize(state, sink); - emit_std_reg_mem(sink, state, info, prefix, opcodes, 2, *src, &amode, rex); + let rex = RexFlags::from((OperandSize::from_ty(*ty), *replacement)); + let amode = mem.finalize(state, sink); + emit_std_reg_mem( + sink, + state, + info, + prefix, + opcodes, + 2, + *replacement, + &amode, + rex, + ); } - Inst::AtomicRmwSeq { ty, op } => { + Inst::AtomicRmwSeq { + ty, + op, + address, + operand, + temp, + dst_old, + } => { + debug_assert_eq!(*address, regs::r9()); + debug_assert_eq!(*operand, regs::r10()); + debug_assert_eq!(temp.to_reg(), regs::r11()); + debug_assert_eq!(dst_old.to_reg(), regs::rax()); + // Emit this: // // mov{zbq,zwq,zlq,q} (%r9), %rax // rax = old value @@ -2516,8 +2594,10 @@ pub(crate) fn emit( // No need to call `add_trap` here, since the `i4` emit will do that. let i4 = Inst::LockCmpxchg { ty: *ty, - src: r11, - dst: amode.into(), + replacement: r11, + expected: regs::rax(), + mem: amode.into(), + dst_old: Writable::from_reg(regs::rax()), }; i4.emit(sink, info, state); diff --git a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs index 1a81191141..2f753ace1a 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs @@ -4199,8 +4199,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I8, - src: rbx, - dst: am1, + mem: am1, + replacement: rbx, + expected: rax, + dst_old: w_rax, }, "F0410FB09C9241010000", "lock cmpxchgb %bl, 321(%r10,%rdx,4)", @@ -4209,8 +4211,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I8, - src: rdx, - dst: am2.clone(), + mem: am2.clone(), + replacement: rdx, + expected: rax, + dst_old: w_rax, }, "F00FB094F1C7CFFFFF", "lock cmpxchgb %dl, -12345(%rcx,%rsi,8)", @@ -4218,8 +4222,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I8, - src: rsi, - dst: am2.clone(), + mem: am2.clone(), + replacement: rsi, + expected: rax, + dst_old: w_rax, }, "F0400FB0B4F1C7CFFFFF", "lock cmpxchgb %sil, -12345(%rcx,%rsi,8)", @@ -4227,8 +4233,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I8, - src: r10, - dst: am2.clone(), + mem: am2.clone(), + replacement: r10, + expected: rax, + dst_old: w_rax, }, "F0440FB094F1C7CFFFFF", "lock cmpxchgb %r10b, -12345(%rcx,%rsi,8)", @@ -4236,8 +4244,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I8, - src: r15, - dst: am2.clone(), + mem: am2.clone(), + replacement: r15, + expected: rax, + dst_old: w_rax, }, "F0440FB0BCF1C7CFFFFF", "lock cmpxchgb %r15b, -12345(%rcx,%rsi,8)", @@ -4246,8 +4256,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I16, - src: rsi, - dst: am2.clone(), + mem: am2.clone(), + replacement: rsi, + expected: rax, + dst_old: w_rax, }, "66F00FB1B4F1C7CFFFFF", "lock cmpxchgw %si, -12345(%rcx,%rsi,8)", @@ -4255,8 +4267,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I16, - src: r10, - dst: am2.clone(), + mem: am2.clone(), + replacement: r10, + expected: rax, + dst_old: w_rax, }, "66F0440FB194F1C7CFFFFF", "lock cmpxchgw %r10w, -12345(%rcx,%rsi,8)", @@ -4265,8 +4279,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I32, - src: rsi, - dst: am2.clone(), + mem: am2.clone(), + replacement: rsi, + expected: rax, + dst_old: w_rax, }, "F00FB1B4F1C7CFFFFF", "lock cmpxchgl %esi, -12345(%rcx,%rsi,8)", @@ -4274,8 +4290,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I32, - src: r10, - dst: am2.clone(), + mem: am2.clone(), + replacement: r10, + expected: rax, + dst_old: w_rax, }, "F0440FB194F1C7CFFFFF", "lock cmpxchgl %r10d, -12345(%rcx,%rsi,8)", @@ -4284,8 +4302,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I64, - src: rsi, - dst: am2.clone(), + mem: am2.clone(), + replacement: rsi, + expected: rax, + dst_old: w_rax, }, "F0480FB1B4F1C7CFFFFF", "lock cmpxchgq %rsi, -12345(%rcx,%rsi,8)", @@ -4293,8 +4313,10 @@ fn test_x64_emit() { insns.push(( Inst::LockCmpxchg { ty: types::I64, - src: r10, - dst: am2.clone(), + mem: am2.clone(), + replacement: r10, + expected: rax, + dst_old: w_rax, }, "F04C0FB194F1C7CFFFFF", "lock cmpxchgq %r10, -12345(%rcx,%rsi,8)", @@ -4302,27 +4324,62 @@ fn test_x64_emit() { // AtomicRmwSeq insns.push(( - Inst::AtomicRmwSeq { ty: types::I8, op: inst_common::AtomicRmwOp::Or, }, + Inst::AtomicRmwSeq { + ty: types::I8, + op: inst_common::AtomicRmwOp::Or, + address: r9, + operand: r10, + temp: w_r11, + dst_old: w_rax + }, "490FB6014989C34D09D3F0450FB0190F85EFFFFFFF", "atomically { 8_bits_at_[%r9]) Or= %r10; %rax = old_value_at_[%r9]; %r11, %rflags = trash }" )); insns.push(( - Inst::AtomicRmwSeq { ty: types::I16, op: inst_common::AtomicRmwOp::And, }, + Inst::AtomicRmwSeq { + ty: types::I16, + op: inst_common::AtomicRmwOp::And, + address: r9, + operand: r10, + temp: w_r11, + dst_old: w_rax + }, "490FB7014989C34D21D366F0450FB1190F85EEFFFFFF", "atomically { 16_bits_at_[%r9]) And= %r10; %rax = old_value_at_[%r9]; %r11, %rflags = trash }" )); insns.push(( - Inst::AtomicRmwSeq { ty: types::I32, op: inst_common::AtomicRmwOp::Xchg, }, + Inst::AtomicRmwSeq { + ty: types::I32, + op: inst_common::AtomicRmwOp::Xchg, + address: r9, + operand: r10, + temp: w_r11, + dst_old: w_rax + }, "418B014989C34D89D3F0450FB1190F85EFFFFFFF", "atomically { 32_bits_at_[%r9]) Xchg= %r10; %rax = old_value_at_[%r9]; %r11, %rflags = trash }" )); insns.push(( - Inst::AtomicRmwSeq { ty: types::I32, op: inst_common::AtomicRmwOp::Umin, }, + Inst::AtomicRmwSeq { + ty: types::I32, + op: inst_common::AtomicRmwOp::Umin, + address: r9, + operand: r10, + temp: w_r11, + dst_old: w_rax + }, "418B014989C34539DA4D0F46DAF0450FB1190F85EBFFFFFF", "atomically { 32_bits_at_[%r9]) Umin= %r10; %rax = old_value_at_[%r9]; %r11, %rflags = trash }" )); insns.push(( - Inst::AtomicRmwSeq { ty: types::I64, op: inst_common::AtomicRmwOp::Add, }, + Inst::AtomicRmwSeq { + ty: types::I64, + op: inst_common::AtomicRmwOp::Add, + address: r9, + operand: r10, + temp: w_r11, + dst_old: w_rax + }, "498B014989C34D01D3F04D0FB1190F85EFFFFFFF", "atomically { 64_bits_at_[%r9]) Add= %r10; %rax = old_value_at_[%r9]; %r11, %rflags = trash }" )); diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs index e682c6f51c..5d67835941 100644 --- a/cranelift/codegen/src/isa/x64/inst/mod.rs +++ b/cranelift/codegen/src/isa/x64/inst/mod.rs @@ -11,8 +11,8 @@ use crate::{settings, settings::Flags, CodegenError, CodegenResult}; use alloc::boxed::Box; use alloc::vec::Vec; use regalloc::{ - PrettyPrint, PrettyPrintSized, RealRegUniverse, Reg, RegClass, RegUsageCollector, - RegUsageMapper, SpillSlot, VirtualReg, Writable, + PrettyPrint, PrettyPrintSized, RealRegUniverse, Reg, RegClass, RegUsageCollector, SpillSlot, + VirtualReg, Writable, }; use smallvec::{smallvec, SmallVec}; use std::fmt; @@ -33,7 +33,7 @@ use regs::{create_reg_universe_systemv, show_ireg_sized}; // Don't build these directly. Instead use the Inst:: functions to create them. -/// Instructions. Destinations are on the RIGHT (a la AT&T syntax). +/// Instructions. #[derive(Clone)] pub enum Inst { /// Nops of various sizes, including zero. @@ -45,7 +45,8 @@ pub enum Inst { AluRmiR { size: OperandSize, // 4 or 8 op: AluRmiROpcode, - src: RegMemImm, + src1: Reg, + src2: RegMemImm, dst: Writable, }, @@ -60,13 +61,15 @@ pub enum Inst { /// Bitwise not Not { size: OperandSize, // 1, 2, 4 or 8 - src: Writable, + src: Reg, + dst: Writable, }, /// Integer negation Neg { size: OperandSize, // 1, 2, 4 or 8 - src: Writable, + src: Reg, + dst: Writable, }, /// Integer quotient and remainder: (div idiv) $rax $rdx (reg addr) @@ -74,19 +77,27 @@ pub enum Inst { size: OperandSize, // 1, 2, 4 or 8 signed: bool, divisor: RegMem, + dividend: Reg, + dst_quotient: Writable, + dst_remainder: Writable, }, /// The high bits (RDX) of a (un)signed multiply: RDX:RAX := RAX * rhs. MulHi { size: OperandSize, // 2, 4, or 8 signed: bool, - rhs: RegMem, + src1: Reg, + src2: RegMem, + dst_lo: Writable, + dst_hi: Writable, }, /// A synthetic sequence to implement the right inline checks for remainder and division, /// assuming the dividend is in %rax. + /// /// Puts the result back into %rax if is_div, %rdx if !is_div, to mimic what the div /// instruction does. + /// /// The generated code sequence is described in the emit's function match arm for this /// instruction. /// @@ -97,9 +108,12 @@ pub enum Inst { CheckedDivOrRemSeq { kind: DivOrRemKind, size: OperandSize, + dividend: Reg, /// The divisor operand. Note it's marked as modified so that it gets assigned a register /// different from the temporary. divisor: Writable, + dst_quotient: Writable, + dst_remainder: Writable, tmp: Option>, }, @@ -107,9 +121,12 @@ pub enum Inst { /// or al into ah: (cbw) SignExtendData { size: OperandSize, // 1, 2, 4 or 8 + src: Reg, + dst: Writable, }, /// Constant materialization: (imm32 imm64) reg. + /// /// Either: movl $imm32, %reg32 or movabsq $imm64, %reg32. Imm { dst_size: OperandSize, // 4 or 8 @@ -163,15 +180,17 @@ pub enum Inst { ShiftR { size: OperandSize, // 1, 2, 4 or 8 kind: ShiftKind, + src: Reg, /// shift count: Some(0 .. #bits-in-type - 1), or None to mean "%cl". - num_bits: Option, + num_bits: Imm8Reg, dst: Writable, }, /// Arithmetic SIMD shifts. XmmRmiReg { opcode: SseOpcode, - src: RegMemImm, + src1: Reg, + src2: RegMemImm, dst: Writable, }, @@ -191,7 +210,8 @@ pub enum Inst { Cmove { size: OperandSize, // 2, 4, or 8 cc: CC, - src: RegMem, + consequent: RegMem, + alternative: Reg, dst: Writable, }, @@ -208,7 +228,8 @@ pub enum Inst { /// XMM (scalar or vector) binary op: (add sub and or xor mul adc? sbb?) (32 64) (reg addr) reg XmmRmR { op: SseOpcode, - src: RegMem, + src1: Reg, + src2: RegMem, dst: Writable, }, @@ -337,7 +358,8 @@ pub enum Inst { /// A binary XMM instruction with an 8-bit immediate: e.g. cmp (ps pd) imm (reg addr) reg XmmRmRImm { op: SseOpcode, - src: RegMem, + src1: Reg, + src2: RegMem, dst: Writable, imm: u8, size: OperandSize, // 4 or 8 @@ -428,17 +450,19 @@ pub enum Inst { // Instructions pertaining to atomic memory accesses. /// A standard (native) `lock cmpxchg src, (amode)`, with register conventions: /// - /// `dst` (read) address - /// `src` (read) replacement value - /// %rax (modified) in: expected value, out: value that was actually at `dst` + /// `mem` (read) address + /// `replacement` (read) replacement value + /// %rax (modified) in: expected value, out: value that was actually at `dst` /// %rflags is written. Do not assume anything about it after the instruction. /// /// The instruction "succeeded" iff the lowest `ty` bits of %rax afterwards are the same as /// they were before. LockCmpxchg { ty: Type, // I8, I16, I32 or I64 - src: Reg, - dst: SyntheticAmode, + replacement: Reg, + expected: Reg, + mem: SyntheticAmode, + dst_old: Writable, }, /// A synthetic instruction, based on a loop around a native `lock cmpxchg` instruction. @@ -467,6 +491,10 @@ pub enum Inst { AtomicRmwSeq { ty: Type, // I8, I16, I32 or I64 op: inst_common::AtomicRmwOp, + address: Reg, + operand: Reg, + temp: Writable, + dst_old: Writable, }, /// A memory fence (mfence, lfence or sfence). @@ -606,7 +634,13 @@ impl Inst { debug_assert!(size.is_one_of(&[OperandSize::Size32, OperandSize::Size64])); src.assert_regclass_is(RegClass::I64); debug_assert!(dst.to_reg().get_class() == RegClass::I64); - Self::AluRmiR { size, op, src, dst } + Self::AluRmiR { + size, + op, + src1: dst.to_reg(), + src2: src, + dst, + } } pub(crate) fn unary_rm_r( @@ -627,12 +661,20 @@ impl Inst { pub(crate) fn not(size: OperandSize, src: Writable) -> Inst { debug_assert_eq!(src.to_reg().get_class(), RegClass::I64); - Inst::Not { size, src } + Inst::Not { + size, + src: src.to_reg(), + dst: src, + } } pub(crate) fn neg(size: OperandSize, src: Writable) -> Inst { debug_assert_eq!(src.to_reg().get_class(), RegClass::I64); - Inst::Neg { size, src } + Inst::Neg { + size, + src: src.to_reg(), + dst: src, + } } pub(crate) fn div(size: OperandSize, signed: bool, divisor: RegMem) -> Inst { @@ -641,6 +683,9 @@ impl Inst { size, signed, divisor, + dividend: regs::rax(), + dst_quotient: Writable::from_reg(regs::rax()), + dst_remainder: Writable::from_reg(regs::rdx()), } } @@ -651,7 +696,14 @@ impl Inst { OperandSize::Size64 ])); rhs.assert_regclass_is(RegClass::I64); - Inst::MulHi { size, signed, rhs } + Inst::MulHi { + size, + signed, + src1: regs::rax(), + src2: rhs, + dst_lo: Writable::from_reg(regs::rax()), + dst_hi: Writable::from_reg(regs::rdx()), + } } pub(crate) fn checked_div_or_rem_seq( @@ -668,12 +720,19 @@ impl Inst { kind, size, divisor, + dividend: regs::rax(), + dst_quotient: Writable::from_reg(regs::rax()), + dst_remainder: Writable::from_reg(regs::rdx()), tmp, } } pub(crate) fn sign_extend_data(size: OperandSize) -> Inst { - Inst::SignExtendData { size } + Inst::SignExtendData { + size, + src: regs::rax(), + dst: Writable::from_reg(regs::rdx()), + } } pub(crate) fn imm(dst_size: OperandSize, simm64: u64, dst: Writable) -> Inst { @@ -728,7 +787,12 @@ impl Inst { pub(crate) fn xmm_rm_r(op: SseOpcode, src: RegMem, dst: Writable) -> Self { src.assert_regclass_is(RegClass::V128); debug_assert!(dst.to_reg().get_class() == RegClass::V128); - Inst::XmmRmR { op, src, dst } + Inst::XmmRmR { + op, + src1: dst.to_reg(), + src2: src, + dst, + } } pub(crate) fn xmm_rm_r_evex( @@ -902,7 +966,8 @@ impl Inst { debug_assert!(size.is_one_of(&[OperandSize::Size32, OperandSize::Size64])); Inst::XmmRmRImm { op, - src, + src1: dst.to_reg(), + src2: src, dst, imm, size, @@ -918,7 +983,12 @@ impl Inst { pub(crate) fn xmm_rmi_reg(opcode: SseOpcode, src: RegMemImm, dst: Writable) -> Inst { src.assert_regclass_is(RegClass::V128); debug_assert!(dst.to_reg().get_class() == RegClass::V128); - Inst::XmmRmiReg { opcode, src, dst } + Inst::XmmRmiReg { + opcode, + src1: dst.to_reg(), + src2: src, + dst, + } } pub(crate) fn movsx_rm_r(ext_mode: ExtMode, src: RegMem, dst: Writable) -> Inst { @@ -976,7 +1046,11 @@ impl Inst { Inst::ShiftR { size, kind, - num_bits, + src: dst.to_reg(), + num_bits: match num_bits { + Some(imm) => Imm8Reg::Imm8 { imm }, + None => Imm8Reg::Reg { reg: regs::rcx() }, + }, dst, } } @@ -1024,7 +1098,13 @@ impl Inst { OperandSize::Size64 ])); debug_assert!(dst.to_reg().get_class() == RegClass::I64); - Inst::Cmove { size, cc, src, dst } + Inst::Cmove { + size, + cc, + consequent: src, + alternative: dst.to_reg(), + dst, + } } pub(crate) fn xmm_cmove(size: OperandSize, cc: CC, src: RegMem, dst: Writable) -> Inst { @@ -1193,13 +1273,13 @@ impl Inst { /// same as the first register (already handled). fn produces_const(&self) -> bool { match self { - Self::AluRmiR { op, src, dst, .. } => { - src.to_reg() == Some(dst.to_reg()) + Self::AluRmiR { op, src2, dst, .. } => { + src2.to_reg() == Some(dst.to_reg()) && (*op == AluRmiROpcode::Xor || *op == AluRmiROpcode::Sub) } - Self::XmmRmR { op, src, dst, .. } => { - src.to_reg() == Some(dst.to_reg()) + Self::XmmRmR { op, src2, dst, .. } => { + src2.to_reg() == Some(dst.to_reg()) && (*op == SseOpcode::Xorps || *op == SseOpcode::Xorpd || *op == SseOpcode::Pxor @@ -1210,9 +1290,9 @@ impl Inst { } Self::XmmRmRImm { - op, src, dst, imm, .. + op, src2, dst, imm, .. } => { - src.to_reg() == Some(dst.to_reg()) + src2.to_reg() == Some(dst.to_reg()) && (*op == SseOpcode::Cmppd || *op == SseOpcode::Cmpps) && *imm == FcmpImm::Equal.encode() } @@ -1285,6 +1365,265 @@ impl Inst { _ => unimplemented!("unimplemented type for Inst::xor: {}", ty), } } + + /// Translate three-operand instructions into a sequence of two-operand + /// instructions. + /// + /// For example: + /// + /// ```text + /// x = add a, b + /// ``` + /// + /// Becomes: + /// + /// ```text + /// mov x, a + /// add x, b + /// ``` + /// + /// The three-operand form for instructions allows our ISLE DSL code to have + /// a value-based, SSA view of the world. This method is responsible for + /// undoing that. + /// + /// Note that register allocation cleans up most of these inserted `mov`s + /// with its move coalescing. + pub(crate) fn mov_mitosis(mut self) -> impl Iterator { + log::trace!("mov_mitosis({:?})", self); + + let mut insts = SmallVec::<[Self; 4]>::new(); + + match &mut self { + Inst::AluRmiR { src1, dst, .. } => { + if *src1 != dst.to_reg() { + debug_assert!(src1.is_virtual()); + insts.push(Self::gen_move(*dst, *src1, types::I64)); + *src1 = dst.to_reg(); + } + insts.push(self); + } + Inst::XmmRmiReg { src1, dst, .. } => { + if *src1 != dst.to_reg() { + debug_assert!(src1.is_virtual()); + insts.push(Self::gen_move(*dst, *src1, types::I8X16)); + *src1 = dst.to_reg(); + } + insts.push(self); + } + Inst::XmmRmR { src1, dst, .. } => { + if *src1 != dst.to_reg() { + debug_assert!(src1.is_virtual()); + insts.push(Self::gen_move(*dst, *src1, types::I8X16)); + *src1 = dst.to_reg(); + } + insts.push(self); + } + Inst::XmmRmRImm { src1, dst, .. } => { + if *src1 != dst.to_reg() { + debug_assert!(src1.is_virtual()); + insts.push(Self::gen_move(*dst, *src1, types::I8X16)); + *src1 = dst.to_reg(); + } + insts.push(self); + } + Inst::Cmove { + size, + alternative, + dst, + .. + } => { + if *alternative != dst.to_reg() { + debug_assert!(alternative.is_virtual()); + insts.push(Self::mov_r_r(*size, *alternative, *dst)); + *alternative = dst.to_reg(); + } + insts.push(self); + } + Inst::Not { src, dst, .. } | Inst::Neg { src, dst, .. } => { + if *src != dst.to_reg() { + debug_assert!(src.is_virtual()); + insts.push(Self::gen_move(*dst, *src, types::I64)); + *src = dst.to_reg(); + } + insts.push(self); + } + Inst::Div { + dividend, + dst_quotient, + dst_remainder, + .. + } + | Inst::CheckedDivOrRemSeq { + dividend, + dst_quotient, + dst_remainder, + .. + } => { + if *dividend != regs::rax() { + debug_assert!(dividend.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::rax()), + *dividend, + types::I64, + )); + *dividend = regs::rax(); + } + let mut quotient_mov = None; + if dst_quotient.to_reg() != regs::rax() { + debug_assert!(dst_quotient.to_reg().is_virtual()); + quotient_mov = Some(Self::gen_move(*dst_quotient, regs::rax(), types::I64)); + *dst_quotient = Writable::from_reg(regs::rax()); + } + let mut remainder_mov = None; + if dst_remainder.to_reg() != regs::rdx() { + debug_assert!(dst_remainder.to_reg().is_virtual()); + remainder_mov = Some(Self::gen_move(*dst_remainder, regs::rdx(), types::I64)); + *dst_remainder = Writable::from_reg(regs::rdx()); + } + insts.push(self); + insts.extend(quotient_mov); + insts.extend(remainder_mov); + } + Inst::MulHi { + src1, + dst_lo, + dst_hi, + .. + } => { + if *src1 != regs::rax() { + debug_assert!(src1.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::rax()), + *src1, + types::I64, + )); + *src1 = regs::rax(); + } + let mut dst_lo_mov = None; + if dst_lo.to_reg() != regs::rax() { + debug_assert!(dst_lo.to_reg().is_virtual()); + dst_lo_mov = Some(Self::gen_move(*dst_lo, regs::rax(), types::I64)); + *dst_lo = Writable::from_reg(regs::rax()); + } + let mut dst_hi_mov = None; + if dst_hi.to_reg() != regs::rdx() { + debug_assert!(dst_hi.to_reg().is_virtual()); + dst_hi_mov = Some(Self::gen_move(*dst_hi, regs::rdx(), types::I64)); + *dst_hi = Writable::from_reg(regs::rdx()); + } + insts.push(self); + insts.extend(dst_lo_mov); + insts.extend(dst_hi_mov); + } + Inst::SignExtendData { src, dst, .. } => { + if *src != regs::rax() { + debug_assert!(src.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::rax()), + *src, + types::I64, + )); + *src = regs::rax(); + } + let mut dst_mov = None; + if dst.to_reg() != regs::rax() { + debug_assert!(dst.to_reg().is_virtual()); + dst_mov = Some(Self::gen_move(*dst, dst.to_reg(), types::I64)); + *dst = Writable::from_reg(regs::rax()); + } + insts.push(self); + insts.extend(dst_mov); + } + Inst::ShiftR { + src, num_bits, dst, .. + } => { + if *src != dst.to_reg() { + debug_assert!(src.is_virtual()); + insts.push(Self::gen_move(*dst, *src, types::I64)); + *src = dst.to_reg(); + } + if let Imm8Reg::Reg { reg } = num_bits { + if *reg != regs::rcx() { + debug_assert!(reg.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::rcx()), + *reg, + types::I64, + )); + *reg = regs::rcx(); + } + } + insts.push(self); + } + Inst::LockCmpxchg { + ty, + expected, + dst_old, + .. + } => { + if *expected != regs::rax() { + debug_assert!(expected.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::rax()), + *expected, + *ty, + )); + } + let mut dst_old_mov = None; + if dst_old.to_reg() != regs::rax() { + debug_assert!(dst_old.to_reg().is_virtual()); + dst_old_mov = Some(Self::gen_move(*dst_old, regs::rax(), *ty)); + *dst_old = Writable::from_reg(regs::rax()); + } + insts.push(self); + insts.extend(dst_old_mov); + } + Inst::AtomicRmwSeq { + ty, + address, + operand, + dst_old, + .. + } => { + if *address != regs::r9() { + debug_assert!(address.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::r9()), + *address, + types::I64, + )); + *address = regs::r9(); + } + if *operand != regs::r10() { + debug_assert!(operand.is_virtual()); + insts.push(Self::gen_move( + Writable::from_reg(regs::r10()), + *operand, + *ty, + )); + *address = regs::r10(); + } + let mut dst_old_mov = None; + if dst_old.to_reg() != regs::rax() { + debug_assert!(dst_old.to_reg().is_virtual()); + dst_old_mov = Some(Self::gen_move(*dst_old, regs::rax(), *ty)); + *dst_old = Writable::from_reg(regs::rax()); + } + insts.push(self); + insts.extend(dst_old_mov); + } + // No other instruction needs 3-operand to 2-operand legalization. + _ => insts.push(self), + } + + if log::log_enabled!(log::Level::Trace) { + for inst in &insts { + log::trace!(" -> {:?}", inst); + } + } + + insts.into_iter() + } } //============================================================================= @@ -1344,10 +1683,16 @@ impl PrettyPrint for Inst { match self { Inst::Nop { len } => format!("{} len={}", ljustify("nop".to_string()), len), - Inst::AluRmiR { size, op, src, dst } => format!( + Inst::AluRmiR { + size, + op, + src1: _, + src2, + dst, + } => format!( "{} {}, {}", ljustify2(op.to_string(), suffix_lqb(*size, op.is_8bit())), - src.show_rru_sized(mb_rru, size_lqb(*size, op.is_8bit())), + src2.show_rru_sized(mb_rru, size_lqb(*size, op.is_8bit())), show_ireg_sized(dst.to_reg(), mb_rru, size_lqb(*size, op.is_8bit())), ), @@ -1358,16 +1703,16 @@ impl PrettyPrint for Inst { show_ireg_sized(dst.to_reg(), mb_rru, size.to_bytes()), ), - Inst::Not { size, src } => format!( + Inst::Not { size, src: _, dst } => format!( "{} {}", ljustify2("not".to_string(), suffix_bwlq(*size)), - show_ireg_sized(src.to_reg(), mb_rru, size.to_bytes()) + show_ireg_sized(dst.to_reg(), mb_rru, size.to_bytes()) ), - Inst::Neg { size, src } => format!( + Inst::Neg { size, src: _, dst } => format!( "{} {}", ljustify2("neg".to_string(), suffix_bwlq(*size)), - show_ireg_sized(src.to_reg(), mb_rru, size.to_bytes()) + show_ireg_sized(dst.to_reg(), mb_rru, size.to_bytes()) ), Inst::Div { @@ -1386,7 +1731,7 @@ impl PrettyPrint for Inst { ), Inst::MulHi { - size, signed, rhs, .. + size, signed, src2, .. } => format!( "{} {}", ljustify(if *signed { @@ -1394,7 +1739,7 @@ impl PrettyPrint for Inst { } else { "mul".to_string() }), - rhs.show_rru_sized(mb_rru, size.to_bytes()) + src2.show_rru_sized(mb_rru, size.to_bytes()) ), Inst::CheckedDivOrRemSeq { @@ -1413,7 +1758,7 @@ impl PrettyPrint for Inst { show_ireg_sized(divisor.to_reg(), mb_rru, size.to_bytes()), ), - Inst::SignExtendData { size } => match size { + Inst::SignExtendData { size, .. } => match size { OperandSize::Size8 => "cbw", OperandSize::Size16 => "cwd", OperandSize::Size32 => "cdq", @@ -1442,10 +1787,10 @@ impl PrettyPrint for Inst { dst.show_rru(mb_rru), ), - Inst::XmmRmR { op, src, dst, .. } => format!( + Inst::XmmRmR { op, src2, dst, .. } => format!( "{} {}, {}", ljustify(op.to_string()), - src.show_rru_sized(mb_rru, 8), + src2.show_rru_sized(mb_rru, 8), show_ireg_sized(dst.to_reg(), mb_rru, 8), ), @@ -1484,7 +1829,7 @@ impl PrettyPrint for Inst { Inst::XmmRmRImm { op, - src, + src2, dst, imm, size, @@ -1501,7 +1846,7 @@ impl PrettyPrint for Inst { } )), imm, - src.show_rru(mb_rru), + src2.show_rru(mb_rru), dst.show_rru(mb_rru), ), @@ -1681,14 +2026,16 @@ impl PrettyPrint for Inst { kind, num_bits, dst, + .. } => match num_bits { - None => format!( - "{} %cl, {}", + Imm8Reg::Reg { reg } => format!( + "{} {}, {}", ljustify2(kind.to_string(), suffix_bwlq(*size)), + show_ireg_sized(*reg, mb_rru, 1), show_ireg_sized(dst.to_reg(), mb_rru, size.to_bytes()) ), - Some(num_bits) => format!( + Imm8Reg::Imm8 { imm: num_bits } => format!( "{} ${}, {}", ljustify2(kind.to_string(), suffix_bwlq(*size)), num_bits, @@ -1696,10 +2043,12 @@ impl PrettyPrint for Inst { ), }, - Inst::XmmRmiReg { opcode, src, dst } => format!( + Inst::XmmRmiReg { + opcode, src2, dst, .. + } => format!( "{} {}, {}", ljustify(opcode.to_string()), - src.show_rru(mb_rru), + src2.show_rru(mb_rru), dst.to_reg().show_rru(mb_rru) ), @@ -1727,7 +2076,13 @@ impl PrettyPrint for Inst { show_ireg_sized(dst.to_reg(), mb_rru, 1) ), - Inst::Cmove { size, cc, src, dst } => format!( + Inst::Cmove { + size, + cc, + consequent: src, + alternative: _, + dst, + } => format!( "{} {}, {}", ljustify(format!("cmov{}{}", cc.to_string(), suffix_bwlq(*size))), src.show_rru_sized(mb_rru, size.to_bytes()), @@ -1813,13 +2168,18 @@ impl PrettyPrint for Inst { show_ireg_sized(dst.to_reg(), mb_rru, 8), ), - Inst::LockCmpxchg { ty, src, dst, .. } => { + Inst::LockCmpxchg { + ty, + replacement, + mem, + .. + } => { let size = ty.bytes() as u8; format!( "lock cmpxchg{} {}, {}", suffix_bwlq(OperandSize::from_bytes(size as u32)), - show_ireg_sized(*src, mb_rru, size), - dst.show_rru(mb_rru) + show_ireg_sized(*replacement, mb_rru, size), + mem.show_rru(mb_rru) ) } @@ -1874,36 +2234,74 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { // regalloc.rs will "fix" this for us by removing the modified set from the use and def // sets. match inst { - Inst::AluRmiR { src, dst, .. } => { + Inst::AluRmiR { + src1, src2, dst, .. + } => { + debug_assert_eq!(*src1, dst.to_reg()); if inst.produces_const() { - // No need to account for src, since src == dst. + // No need to account for src2, since src2 == dst. collector.add_def(*dst); } else { - src.get_regs_as_uses(collector); + src2.get_regs_as_uses(collector); collector.add_mod(*dst); } } - Inst::Not { src, .. } => { - collector.add_mod(*src); + Inst::Not { src, dst, .. } => { + debug_assert_eq!(*src, dst.to_reg()); + collector.add_mod(*dst); } - Inst::Neg { src, .. } => { - collector.add_mod(*src); + Inst::Neg { src, dst, .. } => { + debug_assert_eq!(*src, dst.to_reg()); + collector.add_mod(*dst); } - Inst::Div { size, divisor, .. } => { + Inst::Div { + size, + divisor, + dividend, + dst_quotient, + dst_remainder, + .. + } => { + debug_assert_eq!(*dividend, regs::rax()); + debug_assert_eq!(dst_quotient.to_reg(), regs::rax()); collector.add_mod(Writable::from_reg(regs::rax())); + + debug_assert_eq!(dst_remainder.to_reg(), regs::rdx()); if *size == OperandSize::Size8 { collector.add_def(Writable::from_reg(regs::rdx())); } else { collector.add_mod(Writable::from_reg(regs::rdx())); } + divisor.get_regs_as_uses(collector); } - Inst::MulHi { rhs, .. } => { + Inst::MulHi { + src1, + src2, + dst_lo, + dst_hi, + .. + } => { + debug_assert_eq!(*src1, regs::rax()); + debug_assert_eq!(dst_lo.to_reg(), regs::rax()); collector.add_mod(Writable::from_reg(regs::rax())); + + debug_assert_eq!(dst_hi.to_reg(), regs::rdx()); collector.add_def(Writable::from_reg(regs::rdx())); - rhs.get_regs_as_uses(collector); + + src2.get_regs_as_uses(collector); } - Inst::CheckedDivOrRemSeq { divisor, tmp, .. } => { + Inst::CheckedDivOrRemSeq { + divisor, + dividend, + dst_quotient, + dst_remainder, + tmp, + .. + } => { + debug_assert_eq!(*dividend, regs::rax()); + debug_assert_eq!(dst_quotient.to_reg(), regs::rax()); + debug_assert_eq!(dst_remainder.to_reg(), regs::rdx()); // Mark both fixed registers as mods, to avoid an early clobber problem in codegen // (i.e. the temporary is allocated one of the fixed registers). This requires writing // the rdx register *before* the instruction, which is not too bad. @@ -1914,25 +2312,36 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { collector.add_def(*tmp); } } - Inst::SignExtendData { size } => match size { - OperandSize::Size8 => collector.add_mod(Writable::from_reg(regs::rax())), - _ => { - collector.add_use(regs::rax()); - collector.add_def(Writable::from_reg(regs::rdx())); + Inst::SignExtendData { size, src, dst } => { + debug_assert_eq!(*src, regs::rax()); + debug_assert_eq!(dst.to_reg(), regs::rdx()); + match size { + OperandSize::Size8 => collector.add_mod(Writable::from_reg(regs::rax())), + _ => { + collector.add_use(regs::rax()); + collector.add_def(Writable::from_reg(regs::rdx())); + } } - }, + } Inst::UnaryRmR { src, dst, .. } | Inst::XmmUnaryRmR { src, dst, .. } | Inst::XmmUnaryRmREvex { src, dst, .. } => { src.get_regs_as_uses(collector); collector.add_def(*dst); } - Inst::XmmRmR { src, dst, op, .. } => { + Inst::XmmRmR { + src1, + src2, + dst, + op, + .. + } => { + debug_assert_eq!(*src1, dst.to_reg()); if inst.produces_const() { // No need to account for src, since src == dst. collector.add_def(*dst); } else { - src.get_regs_as_uses(collector); + src2.get_regs_as_uses(collector); collector.add_mod(*dst); // Some instructions have an implicit use of XMM0. if *op == SseOpcode::Blendvpd @@ -1957,9 +2366,17 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { _ => collector.add_def(*dst), } } - Inst::XmmRmRImm { op, src, dst, .. } => { + Inst::XmmRmRImm { + op, + src1, + src2, + dst, + .. + } => { + debug_assert_eq!(*src1, dst.to_reg()); if inst.produces_const() { - // No need to account for src, since src == dst. + // No need to account for src2, since src2 == dst. + debug_assert_eq!(src2.to_reg(), Some(dst.to_reg())); collector.add_def(*dst); } else if *op == SseOpcode::Pextrb || *op == SseOpcode::Pextrw @@ -1970,10 +2387,10 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { || *op == SseOpcode::Roundps || *op == SseOpcode::Roundpd { - src.get_regs_as_uses(collector); + src2.get_regs_as_uses(collector); collector.add_def(*dst); } else { - src.get_regs_as_uses(collector); + src2.get_regs_as_uses(collector); collector.add_mod(*dst); } } @@ -1983,8 +2400,11 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { collector.add_use(*lhs); collector.add_mod(*rhs_dst); } - Inst::XmmRmiReg { src, dst, .. } => { - src.get_regs_as_uses(collector); + Inst::XmmRmiReg { + src1, src2, dst, .. + } => { + debug_assert_eq!(*src1, dst.to_reg()); + src2.get_regs_as_uses(collector); collector.add_mod(*dst); } Inst::XmmMovRM { src, dst, .. } => { @@ -2054,7 +2474,8 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { dst.get_regs_as_uses(collector); } Inst::ShiftR { num_bits, dst, .. } => { - if num_bits.is_none() { + if let Imm8Reg::Reg { reg } = num_bits { + debug_assert_eq!(*reg, regs::rcx()); collector.add_use(regs::rcx()); } collector.add_mod(*dst); @@ -2066,7 +2487,12 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { Inst::Setcc { dst, .. } => { collector.add_def(*dst); } - Inst::Cmove { src, dst, .. } | Inst::XmmCmove { src, dst, .. } => { + Inst::Cmove { + consequent: src, + dst, + .. + } + | Inst::XmmCmove { src, dst, .. } => { src.get_regs_as_uses(collector); collector.add_mod(*dst); } @@ -2115,9 +2541,18 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { collector.add_def(*dst); } - Inst::LockCmpxchg { src, dst, .. } => { - dst.get_regs_as_uses(collector); - collector.add_use(*src); + Inst::LockCmpxchg { + replacement, + expected, + mem, + dst_old, + .. + } => { + mem.get_regs_as_uses(collector); + collector.add_use(*replacement); + + debug_assert_eq!(*expected, regs::rax()); + debug_assert_eq!(dst_old.to_reg(), regs::rax()); collector.add_mod(Writable::from_reg(regs::rax())); } @@ -2165,29 +2600,55 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { //============================================================================= // Instructions and subcomponents: map_regs -fn map_use(m: &RUM, r: &mut Reg) { - if let Some(reg) = r.as_virtual_reg() { - let new = m.get_use(reg).unwrap().to_reg(); +// Define our own register-mapping trait so we can do arbitrary register +// renaming that are more free form than what `regalloc` constrains us to with +// its `RegUsageMapper` trait definition. +pub trait RegMapper { + fn get_use(&self, reg: Reg) -> Option; + fn get_def(&self, reg: Reg) -> Option; + fn get_mod(&self, reg: Reg) -> Option; +} + +impl RegMapper for T +where + T: regalloc::RegUsageMapper, +{ + fn get_use(&self, reg: Reg) -> Option { + let v = reg.as_virtual_reg()?; + self.get_use(v).map(|r| r.to_reg()) + } + + fn get_def(&self, reg: Reg) -> Option { + let v = reg.as_virtual_reg()?; + self.get_def(v).map(|r| r.to_reg()) + } + + fn get_mod(&self, reg: Reg) -> Option { + let v = reg.as_virtual_reg()?; + self.get_mod(v).map(|r| r.to_reg()) + } +} + +fn map_use(m: &RM, r: &mut Reg) { + if let Some(new) = m.get_use(*r) { *r = new; } } -fn map_def(m: &RUM, r: &mut Writable) { - if let Some(reg) = r.to_reg().as_virtual_reg() { - let new = m.get_def(reg).unwrap().to_reg(); +fn map_def(m: &RM, r: &mut Writable) { + if let Some(new) = m.get_def(r.to_reg()) { *r = Writable::from_reg(new); } } -fn map_mod(m: &RUM, r: &mut Writable) { - if let Some(reg) = r.to_reg().as_virtual_reg() { - let new = m.get_mod(reg).unwrap().to_reg(); +fn map_mod(m: &RM, r: &mut Writable) { + if let Some(new) = m.get_mod(r.to_reg()) { *r = Writable::from_reg(new); } } impl Amode { - fn map_uses(&mut self, map: &RUM) { + fn map_uses(&mut self, map: &RM) { match self { Amode::ImmReg { ref mut base, .. } => map_use(map, base), Amode::ImmRegRegShift { @@ -2217,7 +2678,7 @@ impl Amode { } impl RegMemImm { - fn map_uses(&mut self, map: &RUM) { + fn map_uses(&mut self, map: &RM) { match self { RegMemImm::Reg { ref mut reg } => map_use(map, reg), RegMemImm::Mem { ref mut addr } => addr.map_uses(map), @@ -2225,7 +2686,7 @@ impl RegMemImm { } } - fn map_as_def(&mut self, mapper: &RUM) { + fn map_as_def(&mut self, mapper: &RM) { match self { Self::Reg { reg } => { let mut writable_src = Writable::from_reg(*reg); @@ -2238,14 +2699,14 @@ impl RegMemImm { } impl RegMem { - fn map_uses(&mut self, map: &RUM) { + fn map_uses(&mut self, map: &RM) { match self { RegMem::Reg { ref mut reg } => map_use(map, reg), RegMem::Mem { ref mut addr, .. } => addr.map_uses(map), } } - fn map_as_def(&mut self, mapper: &RUM) { + fn map_as_def(&mut self, mapper: &RM) { match self { Self::Reg { reg } => { let mut writable_src = Writable::from_reg(*reg); @@ -2257,28 +2718,36 @@ impl RegMem { } } -fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { +pub(crate) fn x64_map_regs(inst: &mut Inst, mapper: &RM) { // Note this must be carefully synchronized with x64_get_regs. let produces_const = inst.produces_const(); match inst { // ** Nop Inst::AluRmiR { - ref mut src, + ref mut src1, + ref mut src2, ref mut dst, .. } => { + debug_assert_eq!(*src1, dst.to_reg()); if produces_const { - src.map_as_def(mapper); + src2.map_as_def(mapper); map_def(mapper, dst); + *src1 = dst.to_reg(); } else { - src.map_uses(mapper); + src2.map_uses(mapper); map_mod(mapper, dst); + *src1 = dst.to_reg(); } } - Inst::Not { src, .. } | Inst::Neg { src, .. } => map_mod(mapper, src), + Inst::Not { src, dst, .. } | Inst::Neg { src, dst, .. } => { + debug_assert_eq!(*src, dst.to_reg()); + map_mod(mapper, dst); + *src = dst.to_reg(); + } Inst::Div { divisor, .. } => divisor.map_uses(mapper), - Inst::MulHi { rhs, .. } => rhs.map_uses(mapper), + Inst::MulHi { src2, .. } => src2.map_uses(mapper), Inst::CheckedDivOrRemSeq { divisor, tmp, .. } => { map_mod(mapper, divisor); if let Some(tmp) = tmp { @@ -2306,13 +2775,16 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { } Inst::XmmRmRImm { ref op, - ref mut src, + ref mut src1, + ref mut src2, ref mut dst, .. } => { + debug_assert_eq!(*src1, dst.to_reg()); if produces_const { - src.map_as_def(mapper); + src2.map_as_def(mapper); map_def(mapper, dst); + *src1 = dst.to_reg(); } else if *op == SseOpcode::Pextrb || *op == SseOpcode::Pextrw || *op == SseOpcode::Pextrd @@ -2322,24 +2794,30 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { || *op == SseOpcode::Roundps || *op == SseOpcode::Roundpd { - src.map_uses(mapper); + src2.map_uses(mapper); map_def(mapper, dst); + *src1 = dst.to_reg(); } else { - src.map_uses(mapper); + src2.map_uses(mapper); map_mod(mapper, dst); + *src1 = dst.to_reg(); } } Inst::XmmRmR { - ref mut src, + ref mut src1, + ref mut src2, ref mut dst, .. } => { + debug_assert_eq!(*src1, dst.to_reg()); if produces_const { - src.map_as_def(mapper); + src2.map_as_def(mapper); map_def(mapper, dst); + *src1 = dst.to_reg(); } else { - src.map_uses(mapper); + src2.map_uses(mapper); map_mod(mapper, dst); + *src1 = dst.to_reg(); } } Inst::XmmRmREvex { @@ -2357,12 +2835,15 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { } } Inst::XmmRmiReg { - ref mut src, + ref mut src1, + ref mut src2, ref mut dst, .. } => { - src.map_uses(mapper); + debug_assert_eq!(*src1, dst.to_reg()); + src2.map_uses(mapper); map_mod(mapper, dst); + *src1 = dst.to_reg(); } Inst::XmmUninitializedValue { ref mut dst, .. } => { map_def(mapper, dst); @@ -2475,8 +2956,14 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { map_use(mapper, src); dst.map_uses(mapper); } - Inst::ShiftR { ref mut dst, .. } => { + Inst::ShiftR { + ref mut src, + ref mut dst, + .. + } => { + debug_assert_eq!(*src, dst.to_reg()); map_mod(mapper, dst); + *src = dst.to_reg(); } Inst::CmpRmiR { ref mut src, @@ -2488,17 +2975,22 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { } Inst::Setcc { ref mut dst, .. } => map_def(mapper, dst), Inst::Cmove { - ref mut src, + consequent: ref mut src, ref mut dst, + ref mut alternative, .. + } => { + src.map_uses(mapper); + map_mod(mapper, dst); + *alternative = dst.to_reg(); } - | Inst::XmmCmove { + Inst::XmmCmove { ref mut src, ref mut dst, .. } => { src.map_uses(mapper); - map_mod(mapper, dst) + map_mod(mapper, dst); } Inst::Push64 { ref mut src } => src.map_uses(mapper), Inst::Pop64 { ref mut dst } => { @@ -2549,12 +3041,12 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { Inst::LoadExtName { ref mut dst, .. } => map_def(mapper, dst), Inst::LockCmpxchg { - ref mut src, - ref mut dst, + ref mut replacement, + ref mut mem, .. } => { - map_use(mapper, src); - dst.map_uses(mapper); + map_use(mapper, replacement); + mem.map_uses(mapper); } Inst::ValueLabelMarker { ref mut reg, .. } => map_use(mapper, reg), @@ -2588,7 +3080,10 @@ impl MachInst for Inst { x64_get_regs(&self, collector) } - fn map_regs(&mut self, mapper: &RUM) { + fn map_regs(&mut self, mapper: &RUM) + where + RUM: regalloc::RegUsageMapper, + { x64_map_regs(self, mapper); } diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle new file mode 100644 index 0000000000..f89caccfe4 --- /dev/null +++ b/cranelift/codegen/src/isa/x64/lower.isle @@ -0,0 +1,890 @@ +;; x86-64 instruction selection and CLIF-to-MachInst lowering. + +;; The main lowering constructor term: takes a clif `Inst` and returns the +;; register(s) within which the lowered instruction's result values live. +(decl lower (Inst) ValueRegs) + +;;;; Rules for `iconst` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. +(rule (lower (has_type (fits_in_64 ty) + (iconst (u64_from_imm64 x)))) + (value_reg (imm ty x))) + +;; `i128` +(rule (lower (has_type $I128 + (iconst (u64_from_imm64 x)))) + (value_regs (imm $I64 x) + (imm $I64 0))) + +;;;; Rules for `bconst` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `b64` and smaller. + +(rule (lower (has_type (fits_in_64 ty) + (bconst $false))) + (value_reg (imm ty 0))) + +(rule (lower (has_type (fits_in_64 ty) + (bconst $true))) + (value_reg (imm ty 1))) + +;; `b128` + +(rule (lower (has_type $B128 + (bconst $false))) + (value_regs (imm $B64 0) + (imm $B64 0))) + +(rule (lower (has_type $B128 + (bconst $true))) + (value_regs (imm $B64 1) + (imm $B64 0))) + +;;;; Rules for `null` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type ty (null))) + (value_reg (imm ty 0))) + +;;;; Rules for `iadd` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +;; Add two registers. +(rule (lower (has_type (fits_in_64 ty) + (iadd x y))) + (value_reg (add ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; Add a register and an immediate. + +(rule (lower (has_type (fits_in_64 ty) + (iadd x (simm32_from_value y)))) + (value_reg (add ty (put_in_reg x) y))) + +(rule (lower (has_type (fits_in_64 ty) + (iadd (simm32_from_value x) y))) + (value_reg (add ty (put_in_reg y) x))) + +;; Add a register and memory. + +(rule (lower (has_type (fits_in_64 ty) + (iadd x (sinkable_load y)))) + (value_reg (add ty + (put_in_reg x) + (sink_load y)))) + +(rule (lower (has_type (fits_in_64 ty) + (iadd (sinkable_load x) y))) + (value_reg (add ty + (put_in_reg y) + (sink_load x)))) + +;; SSE. + +(rule (lower (has_type (multi_lane 8 16) + (iadd x y))) + (value_reg (paddb (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (iadd x y))) + (value_reg (paddw (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 32 4) + (iadd x y))) + (value_reg (paddd (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 64 2) + (iadd x y))) + (value_reg (paddq (put_in_reg x) + (put_in_reg_mem y)))) + +;; `i128` +(rule (lower (has_type $I128 (iadd x y))) + ;; Get the high/low registers for `x`. + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1))) + ;; Get the high/low registers for `y`. + (let ((y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1))) + ;; Do an add followed by an add-with-carry. + (with_flags (add_with_flags $I64 x_lo (RegMemImm.Reg y_lo)) + (adc $I64 x_hi (RegMemImm.Reg y_hi)))))) + +;;;; Rules for `sadd_sat` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type (multi_lane 8 16) + (sadd_sat x y))) + (value_reg (paddsb (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (sadd_sat x y))) + (value_reg (paddsw (put_in_reg x) + (put_in_reg_mem y)))) + +;;;; Rules for `uadd_sat` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type (multi_lane 8 16) + (uadd_sat x y))) + (value_reg (paddusb (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (uadd_sat x y))) + (value_reg (paddusw (put_in_reg x) + (put_in_reg_mem y)))) + +;;;; Rules for `iadd_ifcout` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Add two registers. +(rule (lower (has_type (fits_in_64 ty) + (iadd_ifcout x y))) + (value_reg (add ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; Add a register and an immediate. + +(rule (lower (has_type (fits_in_64 ty) + (iadd_ifcout x (simm32_from_value y)))) + (value_reg (add ty (put_in_reg x) y))) + +(rule (lower (has_type (fits_in_64 ty) + (iadd_ifcout (simm32_from_value x) y))) + (value_reg (add ty (put_in_reg y) x))) + +;; Add a register and memory. + +(rule (lower (has_type (fits_in_64 ty) + (iadd_ifcout x (sinkable_load y)))) + (value_reg (add ty + (put_in_reg x) + (sink_load y)))) + +(rule (lower (has_type (fits_in_64 ty) + (iadd_ifcout (sinkable_load x) y))) + (value_reg (add ty + (put_in_reg y) + (sink_load x)))) + +;; (No `iadd_ifcout` for `i128`.) + +;;;; Rules for `iadd_imm` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +;; When the immediate fits in a `RegMemImm.Imm`, use that. +(rule (lower (has_type (fits_in_64 ty) (iadd_imm (simm32_from_imm64 x) y))) + (value_reg (add ty (put_in_reg y) x))) + +;; Otherwise, put the immediate into a register. +(rule (lower (has_type (fits_in_64 ty) (iadd_imm (u64_from_imm64 x) y))) + (value_reg (add ty (put_in_reg y) (RegMemImm.Reg (imm ty x))))) + +;; `i128` + +;; When the immediate fits in a `RegMemImm.Imm`, use that. +(rule (lower (has_type $I128 (iadd_imm (simm32_from_imm64 x) y))) + (let ((y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1))) + (with_flags (add_with_flags $I64 y_lo x) + (adc $I64 y_hi (RegMemImm.Imm 0))))) + +;; Otherwise, put the immediate into a register. +(rule (lower (has_type $I128 (iadd_imm (u64_from_imm64 x) y))) + (let ((y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1)) + (x_lo Reg (imm $I64 x))) + (with_flags (add_with_flags $I64 y_lo (RegMemImm.Reg x_lo)) + (adc $I64 y_hi (RegMemImm.Imm 0))))) + +;;;; Rules for `isub` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +;; Sub two registers. +(rule (lower (has_type (fits_in_64 ty) + (isub x y))) + (value_reg (sub ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; Sub a register and an immediate. +(rule (lower (has_type (fits_in_64 ty) + (isub x (simm32_from_value y)))) + (value_reg (sub ty (put_in_reg x) y))) + +;; Sub a register and memory. +(rule (lower (has_type (fits_in_64 ty) + (isub x (sinkable_load y)))) + (value_reg (sub ty + (put_in_reg x) + (sink_load y)))) + +;; SSE. + +(rule (lower (has_type (multi_lane 8 16) + (isub x y))) + (value_reg (psubb (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (isub x y))) + (value_reg (psubw (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 32 4) + (isub x y))) + (value_reg (psubd (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 64 2) + (isub x y))) + (value_reg (psubq (put_in_reg x) + (put_in_reg_mem y)))) + +;; `i128` +(rule (lower (has_type $I128 (isub x y))) + ;; Get the high/low registers for `x`. + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1))) + ;; Get the high/low registers for `y`. + (let ((y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1))) + ;; Do a sub followed by an sub-with-borrow. + (with_flags (sub_with_flags $I64 x_lo (RegMemImm.Reg y_lo)) + (sbb $I64 x_hi (RegMemImm.Reg y_hi)))))) + +;;;; Rules for `ssub_sat` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type (multi_lane 8 16) + (ssub_sat x y))) + (value_reg (psubsb (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (ssub_sat x y))) + (value_reg (psubsw (put_in_reg x) + (put_in_reg_mem y)))) + +;;;; Rules for `usub_sat` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type (multi_lane 8 16) + (usub_sat x y))) + (value_reg (psubusb (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (usub_sat x y))) + (value_reg (psubusw (put_in_reg x) + (put_in_reg_mem y)))) + +;;;; Rules for `band` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `{i,b}64` and smaller. + +;; And two registers. +(rule (lower (has_type (fits_in_64 ty) (band x y))) + (value_reg (m_and ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; And with a memory operand. + +(rule (lower (has_type (fits_in_64 ty) + (band x (sinkable_load y)))) + (value_reg (m_and ty + (put_in_reg x) + (sink_load y)))) + +(rule (lower (has_type (fits_in_64 ty) + (band (sinkable_load x) y))) + (value_reg (m_and ty + (put_in_reg y) + (sink_load x)))) + +;; And with an immediate. + +(rule (lower (has_type (fits_in_64 ty) + (band x (simm32_from_value y)))) + (value_reg (m_and ty + (put_in_reg x) + y))) + +(rule (lower (has_type (fits_in_64 ty) + (band (simm32_from_value x) y))) + (value_reg (m_and ty + (put_in_reg y) + x))) + +;; SSE. + +(rule (lower (has_type $F32X4 (band x y))) + (value_reg (andps (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type $F64X2 (band x y))) + (value_reg (andpd (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane _bits _lanes) + (band x y))) + (value_reg (pand (put_in_reg x) + (put_in_reg_mem y)))) + +;; `{i,b}128`. + +(rule (lower (has_type $I128 (band x y))) + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + (y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1))) + (value_regs (m_and $I64 x_lo (RegMemImm.Reg y_lo)) + (m_and $I64 x_hi (RegMemImm.Reg y_hi))))) + +(rule (lower (has_type $B128 (band x y))) + ;; Booleans are always `0` or `1`, so we only need to do the `and` on the + ;; low half. The high half is always zero but, rather than generate a new + ;; zero, we just reuse `x`'s high half which is already zero. + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + (y_lo Reg (lo_reg y))) + (value_regs (m_and $I64 x_lo (RegMemImm.Reg y_lo)) + x_hi))) + +;;;; Rules for `bor` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `{i,b}64` and smaller. + +;; Or two registers. +(rule (lower (has_type (fits_in_64 ty) (bor x y))) + (value_reg (or ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; Or with a memory operand. + +(rule (lower (has_type (fits_in_64 ty) + (bor x (sinkable_load y)))) + (value_reg (or ty + (put_in_reg x) + (sink_load y)))) + +(rule (lower (has_type (fits_in_64 ty) + (bor (sinkable_load x) y))) + (value_reg (or ty + (put_in_reg y) + (sink_load x)))) + +;; Or with an immediate. + +(rule (lower (has_type (fits_in_64 ty) + (bor x (simm32_from_value y)))) + (value_reg (or ty + (put_in_reg x) + y))) + +(rule (lower (has_type (fits_in_64 ty) + (bor (simm32_from_value x) y))) + (value_reg (or ty + (put_in_reg y) + x))) + +;; SSE. + +(rule (lower (has_type $F32X4 (bor x y))) + (value_reg (orps (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type $F64X2 (bor x y))) + (value_reg (orpd (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane _bits _lanes) + (bor x y))) + (value_reg (por (put_in_reg x) + (put_in_reg_mem y)))) + +;; `{i,b}128`. + +(decl or_i128 (ValueRegs ValueRegs) ValueRegs) +(rule (or_i128 x y) + (let ((x_lo Reg (value_regs_get x 0)) + (x_hi Reg (value_regs_get x 1)) + (y_lo Reg (value_regs_get y 0)) + (y_hi Reg (value_regs_get y 1))) + (value_regs (or $I64 x_lo (RegMemImm.Reg y_lo)) + (or $I64 x_hi (RegMemImm.Reg y_hi))))) + +(rule (lower (has_type $I128 (bor x y))) + (or_i128 (put_in_regs x) (put_in_regs y))) + +(rule (lower (has_type $B128 (bor x y))) + ;; Booleans are always `0` or `1`, so we only need to do the `or` on the + ;; low half. The high half is always zero but, rather than generate a new + ;; zero, we just reuse `x`'s high half which is already zero. + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + (y_lo Reg (lo_reg y))) + (value_regs (or $I64 x_lo (RegMemImm.Reg y_lo)) + x_hi))) + +;;;; Rules for `bxor` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `{i,b}64` and smaller. + +;; Xor two registers. +(rule (lower (has_type (fits_in_64 ty) (bxor x y))) + (value_reg (xor ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; Xor with a memory operand. + +(rule (lower (has_type (fits_in_64 ty) + (bxor x (sinkable_load y)))) + (value_reg (xor ty + (put_in_reg x) + (sink_load y)))) + +(rule (lower (has_type (fits_in_64 ty) + (bxor (sinkable_load x) y))) + (value_reg (xor ty + (put_in_reg y) + (sink_load x)))) + +;; Xor with an immediate. + +(rule (lower (has_type (fits_in_64 ty) + (bxor x (simm32_from_value y)))) + (value_reg (xor ty + (put_in_reg x) + y))) + +(rule (lower (has_type (fits_in_64 ty) + (bxor (simm32_from_value x) y))) + (value_reg (xor ty + (put_in_reg y) + x))) + +;; SSE. + +(rule (lower (has_type $F32X4 (bxor x y))) + (value_reg (xorps (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type $F64X2 (bxor x y))) + (value_reg (xorpd (put_in_reg x) + (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane _bits _lanes) + (bxor x y))) + (value_reg (pxor (put_in_reg x) + (put_in_reg_mem y)))) + +;; `{i,b}128`. + +(rule (lower (has_type $I128 (bxor x y))) + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + (y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1))) + (value_regs (xor $I64 x_lo (RegMemImm.Reg y_lo)) + (xor $I64 x_hi (RegMemImm.Reg y_hi))))) + +(rule (lower (has_type $B128 (bxor x y))) + ;; Booleans are always `0` or `1`, so we only need to do the `xor` on the + ;; low half. The high half is always zero but, rather than generate a new + ;; zero, we just reuse `x`'s high half which is already zero. + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + (y_lo Reg (lo_reg y))) + (value_regs (xor $I64 x_lo (RegMemImm.Reg y_lo)) + x_hi))) + +;;;; Rules for `ishl` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +(rule (lower (has_type (fits_in_64 ty) (ishl src amt))) + ;; NB: Only the low bits of `amt` matter since we logically mask the shift + ;; amount to the value's bit width. + (let ((amt_ Reg (lo_reg amt))) + (value_reg (shl ty (put_in_reg src) (Imm8Reg.Reg amt_))))) + +(rule (lower (has_type (fits_in_64 ty) (ishl src (imm8_from_value amt)))) + (value_reg (shl ty (put_in_reg src) amt))) + +;; `i128`. + +(decl shl_i128 (ValueRegs Reg) ValueRegs) +(rule (shl_i128 src amt) + ;; Unpack the registers that make up the 128-bit value being shifted. + (let ((src_lo Reg (value_regs_get src 0)) + (src_hi Reg (value_regs_get src 1)) + ;; Do two 64-bit shifts. + (lo_shifted Reg (shl $I64 src_lo (Imm8Reg.Reg amt))) + (hi_shifted Reg (shl $I64 src_hi (Imm8Reg.Reg amt))) + ;; `src_lo >> (64 - amt)` are the bits to carry over from the lo + ;; into the hi. + (carry Reg (shr $I64 src_lo (Imm8Reg.Reg (sub $I64 (imm $I64 64) (RegMemImm.Reg amt))))) + (zero Reg (imm $I64 0)) + ;; Nullify the carry if we are shifting in by a multiple of 128. + (carry_ Reg (with_flags_1 (test (OperandSize.Size64) (RegMemImm.Imm 127) amt) + (cmove $I64 (CC.Z) (RegMem.Reg zero) carry))) + ;; Add the carry into the high half. + (hi_shifted_ Reg (or $I64 carry_ (RegMemImm.Reg hi_shifted)))) + ;; Combine the two shifted halves. However, if we are shifting by >= 64 + ;; (modulo 128), then the low bits are zero and the high bits are our + ;; low bits. + (with_flags_2 (test (OperandSize.Size64) (RegMemImm.Imm 64) amt) + (cmove $I64 (CC.Z) (RegMem.Reg lo_shifted) zero) + (cmove $I64 (CC.Z) (RegMem.Reg hi_shifted_) lo_shifted)))) + +(rule (lower (has_type $I128 (ishl src amt))) + ;; NB: Only the low bits of `amt` matter since we logically mask the shift + ;; amount to the value's bit width. + (let ((amt_ Reg (lo_reg amt))) + (shl_i128 (put_in_regs src) amt_))) + +;;;; Rules for `ushr` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +(rule (lower (has_type (fits_in_64 ty) (ushr src amt))) + (let ((src_ Reg (extend_to_reg src ty (ExtendKind.Zero))) + ;; NB: Only the low bits of `amt` matter since we logically mask the + ;; shift amount to the value's bit width. + (amt_ Reg (lo_reg amt))) + (value_reg (shr ty src_ (Imm8Reg.Reg amt_))))) + +(rule (lower (has_type (fits_in_64 ty) (ushr src (imm8_from_value amt)))) + (let ((src_ Reg (extend_to_reg src ty (ExtendKind.Zero)))) + (value_reg (shr ty src_ amt)))) + +;; `i128`. + +(decl shr_i128 (ValueRegs Reg) ValueRegs) +(rule (shr_i128 src amt) + ;; Unpack the lo/hi halves of `src`. + (let ((src_lo Reg (value_regs_get src 0)) + (src_hi Reg (value_regs_get src 1)) + ;; Do a shift on each half. + (lo_shifted Reg (shr $I64 src_lo (Imm8Reg.Reg amt))) + (hi_shifted Reg (shr $I64 src_hi (Imm8Reg.Reg amt))) + ;; `src_hi << (64 - amt)` are the bits to carry over from the hi + ;; into the lo. + (carry Reg (shl $I64 src_hi (Imm8Reg.Reg (sub $I64 (imm $I64 64) (RegMemImm.Reg amt))))) + ;; Nullify the carry if we are shifting by a multiple of 128. + (carry_ Reg (with_flags_1 (test (OperandSize.Size64) (RegMemImm.Imm 127) amt) + (cmove $I64 (CC.Z) (RegMem.Reg (imm $I64 0)) carry))) + ;; Add the carry bits into the lo. + (lo_shifted_ Reg (or $I64 carry_ (RegMemImm.Reg lo_shifted)))) + ;; Combine the two shifted halves. However, if we are shifting by >= 64 + ;; (modulo 128), then the hi bits are zero and the lo bits are what + ;; would otherwise be our hi bits. + (with_flags_2 (test (OperandSize.Size64) (RegMemImm.Imm 64) amt) + (cmove $I64 (CC.Z) (RegMem.Reg lo_shifted_) hi_shifted) + (cmove $I64 (CC.Z) (RegMem.Reg hi_shifted) (imm $I64 0))))) + +(rule (lower (has_type $I128 (ushr src amt))) + ;; NB: Only the low bits of `amt` matter since we logically mask the shift + ;; amount to the value's bit width. + (let ((amt_ Reg (lo_reg amt))) + (shr_i128 (put_in_regs src) amt_))) + +;;;; Rules for `rotl` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +(rule (lower (has_type (fits_in_64 ty) (rotl src amt))) + ;; NB: Only the low bits of `amt` matter since we logically mask the + ;; shift amount to the value's bit width. + (let ((amt_ Reg (lo_reg amt))) + (value_reg (m_rotl ty (put_in_reg src) (Imm8Reg.Reg amt_))))) + +(rule (lower (has_type (fits_in_64 ty) (rotl src (imm8_from_value amt)))) + (value_reg (m_rotl ty (put_in_reg src) amt))) + +;; `i128`. + +(rule (lower (has_type $I128 (rotl src amt))) + (let ((src_ ValueRegs (put_in_regs src)) + ;; NB: Only the low bits of `amt` matter since we logically mask the + ;; rotation amount to the value's bit width. + (amt_ Reg (lo_reg amt))) + (or_i128 (shl_i128 src_ amt_) + (shr_i128 src_ (sub $I64 (imm $I64 128) (RegMemImm.Reg amt_)))))) + +;;;; Rules for `avg_round` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type (multi_lane 8 16) + (avg_round x y))) + (value_reg (pavgb (put_in_reg x) (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 16 8) + (avg_round x y))) + (value_reg (pavgw (put_in_reg x) (put_in_reg_mem y)))) + +;;;; Rules for `imul` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `i64` and smaller. + +;; Multiply two registers. +(rule (lower (has_type (fits_in_64 ty) (imul x y))) + (value_reg (mul ty + (put_in_reg x) + (RegMemImm.Reg (put_in_reg y))))) + +;; Multiply a register and an immediate. + +(rule (lower (has_type (fits_in_64 ty) + (imul x (simm32_from_value y)))) + (value_reg (mul ty (put_in_reg x) y))) + +(rule (lower (has_type (fits_in_64 ty) + (imul (simm32_from_value x) y))) + (value_reg (mul ty (put_in_reg y) x))) + +;; Multiply a register and a memory load. + +(rule (lower (has_type (fits_in_64 ty) + (imul x (sinkable_load y)))) + (value_reg (mul ty + (put_in_reg x) + (sink_load y)))) + +(rule (lower (has_type (fits_in_64 ty) + (imul (sinkable_load x) y))) + (value_reg (mul ty + (put_in_reg y) + (sink_load x)))) + +;; SSE. + +;; (No i8x16 multiply.) + +(rule (lower (has_type (multi_lane 16 8) (imul x y))) + (value_reg (pmullw (put_in_reg x) (put_in_reg_mem y)))) + +(rule (lower (has_type (multi_lane 32 4) (imul x y))) + (value_reg (pmulld (put_in_reg x) (put_in_reg_mem y)))) + +;; With AVX-512 we can implement `i64x2` multiplication with a single +;; instruction. +(rule (lower (has_type (and (avx512vl_enabled) + (avx512dq_enabled) + (multi_lane 64 2)) + (imul x y))) + (value_reg (vpmullq (put_in_reg_mem x) (put_in_reg y)))) + +;; Otherwise, for i64x2 multiplication we describe a lane A as being composed of +;; a 32-bit upper half "Ah" and a 32-bit lower half "Al". The 32-bit long hand +;; multiplication can then be written as: +;; +;; Ah Al +;; * Bh Bl +;; ----- +;; Al * Bl +;; + (Ah * Bl) << 32 +;; + (Al * Bh) << 32 +;; +;; So for each lane we will compute: +;; +;; A * B = (Al * Bl) + ((Ah * Bl) + (Al * Bh)) << 32 +;; +;; Note, the algorithm will use `pmuldq` which operates directly on the lower +;; 32-bit (`Al` or `Bl`) of a lane and writes the result to the full 64-bits of +;; the lane of the destination. For this reason we don't need shifts to isolate +;; the lower 32-bits, however, we will need to use shifts to isolate the high +;; 32-bits when doing calculations, i.e., `Ah == A >> 32`. +(rule (lower (has_type (multi_lane 64 2) + (imul a b))) + (let ((a0 Reg (put_in_reg a)) + (b0 Reg (put_in_reg b)) + ;; a_hi = A >> 32 + (a_hi Reg (psrlq a0 (RegMemImm.Imm 32))) + ;; ah_bl = Ah * Bl + (ah_bl Reg (pmuludq a_hi (RegMem.Reg b0))) + ;; b_hi = B >> 32 + (b_hi Reg (psrlq b0 (RegMemImm.Imm 32))) + ;; al_bh = Al * Bh + (al_bh Reg (pmuludq a0 (RegMem.Reg b_hi))) + ;; aa_bb = ah_bl + al_bh + (aa_bb Reg (paddq ah_bl (RegMem.Reg al_bh))) + ;; aa_bb_shifted = aa_bb << 32 + (aa_bb_shifted Reg (psllq aa_bb (RegMemImm.Imm 32))) + ;; al_bl = Al * Bl + (al_bl Reg (pmuludq a0 (RegMem.Reg b0)))) + ;; al_bl + aa_bb_shifted + (value_reg (paddq al_bl (RegMem.Reg aa_bb_shifted))))) + +;; Special case for `i16x8.extmul_high_i8x16_s`. +(rule (lower (has_type (multi_lane 16 8) + (imul (def_inst (swiden_high (and (value_type (multi_lane 8 16)) + x))) + (def_inst (swiden_high (and (value_type (multi_lane 8 16)) + y)))))) + (let ((x1 Reg (put_in_reg x)) + (x2 Reg (palignr x1 (RegMem.Reg x1) 8 (OperandSize.Size32))) + (x3 Reg (pmovsxbw (RegMem.Reg x2))) + (y1 Reg (put_in_reg y)) + (y2 Reg (palignr y1 (RegMem.Reg y1) 8 (OperandSize.Size32))) + (y3 Reg (pmovsxbw (RegMem.Reg y2)))) + (value_reg (pmullw x3 (RegMem.Reg y3))))) + +;; Special case for `i32x4.extmul_high_i16x8_s`. +(rule (lower (has_type (multi_lane 32 4) + (imul (def_inst (swiden_high (and (value_type (multi_lane 16 8)) + x))) + (def_inst (swiden_high (and (value_type (multi_lane 16 8)) + y)))))) + (let ((x2 Reg (put_in_reg x)) + (y2 Reg (put_in_reg y)) + (lo Reg (pmullw x2 (RegMem.Reg y2))) + (hi Reg (pmulhw x2 (RegMem.Reg y2)))) + (value_reg (punpckhwd lo (RegMem.Reg hi))))) + +;; Special case for `i64x2.extmul_high_i32x4_s`. +(rule (lower (has_type (multi_lane 64 2) + (imul (def_inst (swiden_high (and (value_type (multi_lane 32 4)) + x))) + (def_inst (swiden_high (and (value_type (multi_lane 32 4)) + y)))))) + (let ((x2 Reg (pshufd (put_in_reg_mem x) + 0xFA + (OperandSize.Size32))) + (y2 Reg (pshufd (put_in_reg_mem y) + 0xFA + (OperandSize.Size32)))) + (value_reg (pmuldq x2 (RegMem.Reg y2))))) + +;; Special case for `i16x8.extmul_low_i8x16_s`. +(rule (lower (has_type (multi_lane 16 8) + (imul (def_inst (swiden_low (and (value_type (multi_lane 8 16)) + x))) + (def_inst (swiden_low (and (value_type (multi_lane 8 16)) + y)))))) + (let ((x2 Reg (pmovsxbw (put_in_reg_mem x))) + (y2 Reg (pmovsxbw (put_in_reg_mem y)))) + (value_reg (pmullw x2 (RegMem.Reg y2))))) + +;; Special case for `i32x4.extmul_low_i16x8_s`. +(rule (lower (has_type (multi_lane 32 4) + (imul (def_inst (swiden_low (and (value_type (multi_lane 16 8)) + x))) + (def_inst (swiden_low (and (value_type (multi_lane 16 8)) + y)))))) + (let ((x2 Reg (put_in_reg x)) + (y2 Reg (put_in_reg y)) + (lo Reg (pmullw x2 (RegMem.Reg y2))) + (hi Reg (pmulhw x2 (RegMem.Reg y2)))) + (value_reg (punpcklwd lo (RegMem.Reg hi))))) + +;; Special case for `i64x2.extmul_low_i32x4_s`. +(rule (lower (has_type (multi_lane 64 2) + (imul (def_inst (swiden_low (and (value_type (multi_lane 32 4)) + x))) + (def_inst (swiden_low (and (value_type (multi_lane 32 4)) + y)))))) + (let ((x2 Reg (pshufd (put_in_reg_mem x) + 0x50 + (OperandSize.Size32))) + (y2 Reg (pshufd (put_in_reg_mem y) + 0x50 + (OperandSize.Size32)))) + (value_reg (pmuldq x2 (RegMem.Reg y2))))) + +;; Special case for `i16x8.extmul_high_i8x16_u`. +(rule (lower (has_type (multi_lane 16 8) + (imul (def_inst (uwiden_high (and (value_type (multi_lane 8 16)) + x))) + (def_inst (uwiden_high (and (value_type (multi_lane 8 16)) + y)))))) + (let ((x1 Reg (put_in_reg x)) + (x2 Reg (palignr x1 (RegMem.Reg x1) 8 (OperandSize.Size32))) + (x3 Reg (pmovzxbw (RegMem.Reg x2))) + (y1 Reg (put_in_reg y)) + (y2 Reg (palignr y1 (RegMem.Reg y1) 8 (OperandSize.Size32))) + (y3 Reg (pmovzxbw (RegMem.Reg y2)))) + (value_reg (pmullw x3 (RegMem.Reg y3))))) + +;; Special case for `i32x4.extmul_high_i16x8_u`. +(rule (lower (has_type (multi_lane 32 4) + (imul (def_inst (uwiden_high (and (value_type (multi_lane 16 8)) + x))) + (def_inst (uwiden_high (and (value_type (multi_lane 16 8)) + y)))))) + (let ((x2 Reg (put_in_reg x)) + (y2 Reg (put_in_reg y)) + (lo Reg (pmullw x2 (RegMem.Reg y2))) + (hi Reg (pmulhuw x2 (RegMem.Reg y2)))) + (value_reg (punpckhwd lo (RegMem.Reg hi))))) + +;; Special case for `i64x2.extmul_high_i32x4_u`. +(rule (lower (has_type (multi_lane 64 2) + (imul (def_inst (uwiden_high (and (value_type (multi_lane 32 4)) + x))) + (def_inst (uwiden_high (and (value_type (multi_lane 32 4)) + y)))))) + (let ((x2 Reg (pshufd (put_in_reg_mem x) + 0xFA + (OperandSize.Size32))) + (y2 Reg (pshufd (put_in_reg_mem y) + 0xFA + (OperandSize.Size32)))) + (value_reg (pmuludq x2 (RegMem.Reg y2))))) + +;; Special case for `i16x8.extmul_low_i8x16_u`. +(rule (lower (has_type (multi_lane 16 8) + (imul (def_inst (uwiden_low (and (value_type (multi_lane 8 16)) + x))) + (def_inst (uwiden_low (and (value_type (multi_lane 8 16)) + y)))))) + (let ((x2 Reg (pmovzxbw (put_in_reg_mem x))) + (y2 Reg (pmovzxbw (put_in_reg_mem y)))) + (value_reg (pmullw x2 (RegMem.Reg y2))))) + +;; Special case for `i32x4.extmul_low_i16x8_u`. +(rule (lower (has_type (multi_lane 32 4) + (imul (def_inst (uwiden_low (and (value_type (multi_lane 16 8)) + x))) + (def_inst (uwiden_low (and (value_type (multi_lane 16 8)) + y)))))) + (let ((x2 Reg (put_in_reg x)) + (y2 Reg (put_in_reg y)) + (lo Reg (pmullw x2 (RegMem.Reg y2))) + (hi Reg (pmulhuw x2 (RegMem.Reg y2)))) + (value_reg (punpcklwd lo (RegMem.Reg hi))))) + +;; Special case for `i64x2.extmul_low_i32x4_u`. +(rule (lower (has_type (multi_lane 64 2) + (imul (def_inst (uwiden_low (and (value_type (multi_lane 32 4)) + x))) + (def_inst (uwiden_low (and (value_type (multi_lane 32 4)) + y)))))) + (let ((x2 Reg (pshufd (put_in_reg_mem x) + 0x50 + (OperandSize.Size32))) + (y2 Reg (pshufd (put_in_reg_mem y) + 0x50 + (OperandSize.Size32)))) + (value_reg (pmuludq x2 (RegMem.Reg y2))))) diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs index c67dab3dde..65e94e396a 100644 --- a/cranelift/codegen/src/isa/x64/lower.rs +++ b/cranelift/codegen/src/isa/x64/lower.rs @@ -1,5 +1,8 @@ //! Lowering rules for X64. +// ISLE integration glue. +mod isle; + use crate::data_value::DataValue; use crate::ir::{ condcodes::{CondCode, FloatCC, IntCC}, @@ -1497,20 +1500,15 @@ fn lower_insn_to_regs>( None }; - match op { - Opcode::Iconst | Opcode::Bconst | Opcode::Null => { - let value = ctx - .get_constant(insn) - .expect("constant value for iconst et al"); - let dst = get_output_reg(ctx, outputs[0]); - for inst in Inst::gen_constant(dst, value as u128, ty.unwrap(), |ty| { - ctx.alloc_tmp(ty).only_reg().unwrap() - }) { - ctx.emit(inst); - } - } + if let Ok(()) = isle::lower(ctx, isa_flags, &outputs, insn) { + return Ok(()); + } - Opcode::Iadd + match op { + Opcode::Iconst + | Opcode::Bconst + | Opcode::Null + | Opcode::Iadd | Opcode::IaddIfcout | Opcode::SaddSat | Opcode::UaddSat @@ -1521,149 +1519,11 @@ fn lower_insn_to_regs>( | Opcode::Band | Opcode::Bor | Opcode::Bxor => { - let ty = ty.unwrap(); - if ty.lane_count() > 1 { - let sse_op = match op { - Opcode::Iadd => match ty { - types::I8X16 => SseOpcode::Paddb, - types::I16X8 => SseOpcode::Paddw, - types::I32X4 => SseOpcode::Paddd, - types::I64X2 => SseOpcode::Paddq, - _ => panic!("Unsupported type for packed iadd instruction: {}", ty), - }, - Opcode::SaddSat => match ty { - types::I8X16 => SseOpcode::Paddsb, - types::I16X8 => SseOpcode::Paddsw, - _ => panic!("Unsupported type for packed sadd_sat instruction: {}", ty), - }, - Opcode::UaddSat => match ty { - types::I8X16 => SseOpcode::Paddusb, - types::I16X8 => SseOpcode::Paddusw, - _ => panic!("Unsupported type for packed uadd_sat instruction: {}", ty), - }, - Opcode::Isub => match ty { - types::I8X16 => SseOpcode::Psubb, - types::I16X8 => SseOpcode::Psubw, - types::I32X4 => SseOpcode::Psubd, - types::I64X2 => SseOpcode::Psubq, - _ => panic!("Unsupported type for packed isub instruction: {}", ty), - }, - Opcode::SsubSat => match ty { - types::I8X16 => SseOpcode::Psubsb, - types::I16X8 => SseOpcode::Psubsw, - _ => panic!("Unsupported type for packed ssub_sat instruction: {}", ty), - }, - Opcode::UsubSat => match ty { - types::I8X16 => SseOpcode::Psubusb, - types::I16X8 => SseOpcode::Psubusw, - _ => panic!("Unsupported type for packed usub_sat instruction: {}", ty), - }, - Opcode::AvgRound => match ty { - types::I8X16 => SseOpcode::Pavgb, - types::I16X8 => SseOpcode::Pavgw, - _ => panic!("Unsupported type for packed avg_round instruction: {}", ty), - }, - Opcode::Band => match ty { - types::F32X4 => SseOpcode::Andps, - types::F64X2 => SseOpcode::Andpd, - _ => SseOpcode::Pand, - }, - Opcode::Bor => match ty { - types::F32X4 => SseOpcode::Orps, - types::F64X2 => SseOpcode::Orpd, - _ => SseOpcode::Por, - }, - Opcode::Bxor => match ty { - types::F32X4 => SseOpcode::Xorps, - types::F64X2 => SseOpcode::Xorpd, - _ => SseOpcode::Pxor, - }, - _ => panic!("Unsupported packed instruction: {}", op), - }; - let lhs = put_input_in_reg(ctx, inputs[0]); - let rhs = input_to_reg_mem(ctx, inputs[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - // Move the `lhs` to the same register as `dst`. - ctx.emit(Inst::gen_move(dst, lhs, ty)); - ctx.emit(Inst::xmm_rm_r(sse_op, rhs, dst)); - } else if ty == types::I128 || ty == types::B128 { - let alu_ops = match op { - Opcode::Iadd => (AluRmiROpcode::Add, AluRmiROpcode::Adc), - Opcode::Isub => (AluRmiROpcode::Sub, AluRmiROpcode::Sbb), - Opcode::Band => (AluRmiROpcode::And, AluRmiROpcode::And), - Opcode::Bor => (AluRmiROpcode::Or, AluRmiROpcode::Or), - Opcode::Bxor => (AluRmiROpcode::Xor, AluRmiROpcode::Xor), - _ => panic!("Unsupported opcode with 128-bit integers: {:?}", op), - }; - let lhs = put_input_in_regs(ctx, inputs[0]); - let rhs = put_input_in_regs(ctx, inputs[1]); - let dst = get_output_reg(ctx, outputs[0]); - assert_eq!(lhs.len(), 2); - assert_eq!(rhs.len(), 2); - assert_eq!(dst.len(), 2); - - // For add, sub, and, or, xor: just do ops on lower then upper - // half. Carry-flag propagation is implicit (add/adc, sub/sbb). - ctx.emit(Inst::gen_move(dst.regs()[0], lhs.regs()[0], types::I64)); - ctx.emit(Inst::gen_move(dst.regs()[1], lhs.regs()[1], types::I64)); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - alu_ops.0, - RegMemImm::reg(rhs.regs()[0]), - dst.regs()[0], - )); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - alu_ops.1, - RegMemImm::reg(rhs.regs()[1]), - dst.regs()[1], - )); - } else { - let size = if ty == types::I64 { - OperandSize::Size64 - } else { - OperandSize::Size32 - }; - let alu_op = match op { - Opcode::Iadd | Opcode::IaddIfcout => AluRmiROpcode::Add, - Opcode::Isub => AluRmiROpcode::Sub, - Opcode::Band => AluRmiROpcode::And, - Opcode::Bor => AluRmiROpcode::Or, - Opcode::Bxor => AluRmiROpcode::Xor, - _ => unreachable!(), - }; - - let (lhs, rhs) = match op { - Opcode::Iadd - | Opcode::IaddIfcout - | Opcode::Band - | Opcode::Bor - | Opcode::Bxor => { - // For commutative operations, try to commute operands if one is an - // immediate or direct memory reference. Do so by converting LHS to RMI; if - // reg, then always convert RHS to RMI; else, use LHS as RMI and convert - // RHS to reg. - let lhs = input_to_reg_mem_imm(ctx, inputs[0]); - if let RegMemImm::Reg { reg: lhs_reg } = lhs { - let rhs = input_to_reg_mem_imm(ctx, inputs[1]); - (lhs_reg, rhs) - } else { - let rhs_reg = put_input_in_reg(ctx, inputs[1]); - (rhs_reg, lhs) - } - } - Opcode::Isub => ( - put_input_in_reg(ctx, inputs[0]), - input_to_reg_mem_imm(ctx, inputs[1]), - ), - _ => unreachable!(), - }; - - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - ctx.emit(Inst::mov_r_r(OperandSize::Size64, lhs, dst)); - ctx.emit(Inst::alu_rmi_r(size, alu_op, rhs, dst)); - } + unreachable!( + "implemented in ISLE: inst = `{}`, type = `{:?}`", + ctx.dfg().display_inst(insn), + ty + ); } Opcode::Imul => { @@ -1681,469 +1541,9 @@ fn lower_insn_to_regs>( Opcode::UwidenLow, ], ) { - // Optimized ext_mul_* lowerings are based on optimized lowerings - // here: https://github.com/WebAssembly/simd/pull/376 - if let Some(swiden0_high) = matches_input(ctx, inputs[0], Opcode::SwidenHigh) { - if let Some(swiden1_high) = matches_input(ctx, inputs[1], Opcode::SwidenHigh) { - let swiden_input = &[ - InsnInput { - insn: swiden0_high, - input: 0, - }, - InsnInput { - insn: swiden1_high, - input: 0, - }, - ]; - let input0_ty = ctx.input_ty(swiden0_high, 0); - let input1_ty = ctx.input_ty(swiden1_high, 0); - let output_ty = ctx.output_ty(insn, 0); - let lhs = put_input_in_reg(ctx, swiden_input[0]); - let rhs = put_input_in_reg(ctx, swiden_input[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - match (input0_ty, input1_ty, output_ty) { - (types::I8X16, types::I8X16, types::I16X8) => { - // i16x8.extmul_high_i8x16_s - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Palignr, - RegMem::reg(lhs), - Writable::from_reg(lhs), - 8, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_mov( - SseOpcode::Pmovsxbw, - RegMem::reg(lhs), - Writable::from_reg(lhs), - )); - - ctx.emit(Inst::gen_move(dst, rhs, output_ty)); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Palignr, - RegMem::reg(rhs), - dst, - 8, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_mov( - SseOpcode::Pmovsxbw, - RegMem::reg(dst.to_reg()), - dst, - )); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmullw, RegMem::reg(lhs), dst)); - } - (types::I16X8, types::I16X8, types::I32X4) => { - // i32x4.extmul_high_i16x8_s - ctx.emit(Inst::gen_move(dst, lhs, input0_ty)); - let tmp_reg = ctx.alloc_tmp(types::I16X8).only_reg().unwrap(); - ctx.emit(Inst::gen_move(tmp_reg, lhs, input0_ty)); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmullw, RegMem::reg(rhs), dst)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmulhw, - RegMem::reg(rhs), - tmp_reg, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Punpckhwd, - RegMem::from(tmp_reg), - dst, - )); - } - (types::I32X4, types::I32X4, types::I64X2) => { - // i64x2.extmul_high_i32x4_s - let tmp_reg = ctx.alloc_tmp(types::I32X4).only_reg().unwrap(); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(lhs), - tmp_reg, - 0xFA, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(rhs), - dst, - 0xFA, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmuldq, - RegMem::reg(tmp_reg.to_reg()), - dst, - )); - } - // Note swiden_high only allows types: I8X16, I16X8, and I32X4 - _ => panic!("Unsupported extmul_low_signed type"), - } - } - } else if let Some(swiden0_low) = matches_input(ctx, inputs[0], Opcode::SwidenLow) { - if let Some(swiden1_low) = matches_input(ctx, inputs[1], Opcode::SwidenLow) { - let swiden_input = &[ - InsnInput { - insn: swiden0_low, - input: 0, - }, - InsnInput { - insn: swiden1_low, - input: 0, - }, - ]; - let input0_ty = ctx.input_ty(swiden0_low, 0); - let input1_ty = ctx.input_ty(swiden1_low, 0); - let output_ty = ctx.output_ty(insn, 0); - let lhs = put_input_in_reg(ctx, swiden_input[0]); - let rhs = put_input_in_reg(ctx, swiden_input[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - match (input0_ty, input1_ty, output_ty) { - (types::I8X16, types::I8X16, types::I16X8) => { - // i32x4.extmul_low_i8x16_s - let tmp_reg = ctx.alloc_tmp(types::I16X8).only_reg().unwrap(); - ctx.emit(Inst::xmm_mov( - SseOpcode::Pmovsxbw, - RegMem::reg(lhs), - tmp_reg, - )); - ctx.emit(Inst::xmm_mov(SseOpcode::Pmovsxbw, RegMem::reg(rhs), dst)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmullw, - RegMem::reg(tmp_reg.to_reg()), - dst, - )); - } - (types::I16X8, types::I16X8, types::I32X4) => { - // i32x4.extmul_low_i16x8_s - ctx.emit(Inst::gen_move(dst, lhs, input0_ty)); - let tmp_reg = ctx.alloc_tmp(types::I16X8).only_reg().unwrap(); - ctx.emit(Inst::gen_move(tmp_reg, lhs, input0_ty)); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmullw, RegMem::reg(rhs), dst)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmulhw, - RegMem::reg(rhs), - tmp_reg, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Punpcklwd, - RegMem::from(tmp_reg), - dst, - )); - } - (types::I32X4, types::I32X4, types::I64X2) => { - // i64x2.extmul_low_i32x4_s - let tmp_reg = ctx.alloc_tmp(types::I32X4).only_reg().unwrap(); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(lhs), - tmp_reg, - 0x50, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(rhs), - dst, - 0x50, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmuldq, - RegMem::reg(tmp_reg.to_reg()), - dst, - )); - } - // Note swiden_low only allows types: I8X16, I16X8, and I32X4 - _ => panic!("Unsupported extmul_low_signed type"), - } - } - } else if let Some(uwiden0_high) = matches_input(ctx, inputs[0], Opcode::UwidenHigh) - { - if let Some(uwiden1_high) = matches_input(ctx, inputs[1], Opcode::UwidenHigh) { - let uwiden_input = &[ - InsnInput { - insn: uwiden0_high, - input: 0, - }, - InsnInput { - insn: uwiden1_high, - input: 0, - }, - ]; - let input0_ty = ctx.input_ty(uwiden0_high, 0); - let input1_ty = ctx.input_ty(uwiden1_high, 0); - let output_ty = ctx.output_ty(insn, 0); - let lhs = put_input_in_reg(ctx, uwiden_input[0]); - let rhs = put_input_in_reg(ctx, uwiden_input[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - match (input0_ty, input1_ty, output_ty) { - (types::I8X16, types::I8X16, types::I16X8) => { - // i16x8.extmul_high_i8x16_u - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Palignr, - RegMem::reg(lhs), - Writable::from_reg(lhs), - 8, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_mov( - SseOpcode::Pmovzxbw, - RegMem::reg(lhs), - Writable::from_reg(lhs), - )); - ctx.emit(Inst::gen_move(dst, rhs, output_ty)); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Palignr, - RegMem::reg(rhs), - dst, - 8, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_mov( - SseOpcode::Pmovzxbw, - RegMem::reg(dst.to_reg()), - dst, - )); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmullw, RegMem::reg(lhs), dst)); - } - (types::I16X8, types::I16X8, types::I32X4) => { - // i32x4.extmul_high_i16x8_u - ctx.emit(Inst::gen_move(dst, lhs, input0_ty)); - let tmp_reg = ctx.alloc_tmp(types::I16X8).only_reg().unwrap(); - ctx.emit(Inst::gen_move(tmp_reg, lhs, input0_ty)); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmullw, RegMem::reg(rhs), dst)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmulhuw, - RegMem::reg(rhs), - tmp_reg, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Punpckhwd, - RegMem::from(tmp_reg), - dst, - )); - } - (types::I32X4, types::I32X4, types::I64X2) => { - // i64x2.extmul_high_i32x4_u - let tmp_reg = ctx.alloc_tmp(types::I32X4).only_reg().unwrap(); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(lhs), - tmp_reg, - 0xFA, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(rhs), - dst, - 0xFA, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmuludq, - RegMem::reg(tmp_reg.to_reg()), - dst, - )); - } - // Note uwiden_high only allows types: I8X16, I16X8, and I32X4 - _ => panic!("Unsupported extmul_high_unsigned type"), - } - } - } else if let Some(uwiden0_low) = matches_input(ctx, inputs[0], Opcode::UwidenLow) { - if let Some(uwiden1_low) = matches_input(ctx, inputs[1], Opcode::UwidenLow) { - let uwiden_input = &[ - InsnInput { - insn: uwiden0_low, - input: 0, - }, - InsnInput { - insn: uwiden1_low, - input: 0, - }, - ]; - - let input0_ty = ctx.input_ty(uwiden0_low, 0); - let input1_ty = ctx.input_ty(uwiden1_low, 0); - let output_ty = ctx.output_ty(insn, 0); - let lhs = put_input_in_reg(ctx, uwiden_input[0]); - let rhs = put_input_in_reg(ctx, uwiden_input[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - match (input0_ty, input1_ty, output_ty) { - (types::I8X16, types::I8X16, types::I16X8) => { - // i16x8.extmul_low_i8x16_u - let tmp_reg = ctx.alloc_tmp(types::I16X8).only_reg().unwrap(); - ctx.emit(Inst::xmm_mov( - SseOpcode::Pmovzxbw, - RegMem::reg(lhs), - tmp_reg, - )); - ctx.emit(Inst::xmm_mov(SseOpcode::Pmovzxbw, RegMem::reg(rhs), dst)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmullw, - RegMem::reg(tmp_reg.to_reg()), - dst, - )); - } - (types::I16X8, types::I16X8, types::I32X4) => { - // i32x4.extmul_low_i16x8_u - ctx.emit(Inst::gen_move(dst, lhs, input0_ty)); - let tmp_reg = ctx.alloc_tmp(types::I16X8).only_reg().unwrap(); - ctx.emit(Inst::gen_move(tmp_reg, lhs, input0_ty)); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmullw, RegMem::reg(rhs), dst)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmulhuw, - RegMem::reg(rhs), - tmp_reg, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Punpcklwd, - RegMem::from(tmp_reg), - dst, - )); - } - (types::I32X4, types::I32X4, types::I64X2) => { - // i64x2.extmul_low_i32x4_u - let tmp_reg = ctx.alloc_tmp(types::I32X4).only_reg().unwrap(); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(lhs), - tmp_reg, - 0x50, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r_imm( - SseOpcode::Pshufd, - RegMem::reg(rhs), - dst, - 0x50, - OperandSize::Size32, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmuludq, - RegMem::reg(tmp_reg.to_reg()), - dst, - )); - } - // Note uwiden_low only allows types: I8X16, I16X8, and I32X4 - _ => panic!("Unsupported extmul_low_unsigned type"), - } - } - } else { - panic!("Unsupported imul operation for type: {}", ty); - } + unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); } else if ty == types::I64X2 { - // Eventually one of these should be `input_to_reg_mem` (TODO). - let lhs = put_input_in_reg(ctx, inputs[0]); - let rhs = put_input_in_reg(ctx, inputs[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - if isa_flags.use_avx512vl_simd() && isa_flags.use_avx512dq_simd() { - // With the right AVX512 features (VL + DQ) this operation - // can lower to a single operation. - ctx.emit(Inst::xmm_rm_r_evex( - Avx512Opcode::Vpmullq, - RegMem::reg(rhs), - lhs, - dst, - )); - } else { - // Otherwise, for I64X2 multiplication we describe a lane A as being - // composed of a 32-bit upper half "Ah" and a 32-bit lower half - // "Al". The 32-bit long hand multiplication can then be written - // as: - // Ah Al - // * Bh Bl - // ----- - // Al * Bl - // + (Ah * Bl) << 32 - // + (Al * Bh) << 32 - // - // So for each lane we will compute: - // A * B = (Al * Bl) + ((Ah * Bl) + (Al * Bh)) << 32 - // - // Note, the algorithm will use pmuldq which operates directly - // on the lower 32-bit (Al or Bl) of a lane and writes the - // result to the full 64-bits of the lane of the destination. - // For this reason we don't need shifts to isolate the lower - // 32-bits, however, we will need to use shifts to isolate the - // high 32-bits when doing calculations, i.e., Ah == A >> 32. - // - // The full sequence then is as follows: - // A' = A - // A' = A' >> 32 - // A' = Ah' * Bl - // B' = B - // B' = B' >> 32 - // B' = Bh' * Al - // B' = B' + A' - // B' = B' << 32 - // A' = A - // A' = Al' * Bl - // A' = A' + B' - // dst = A' - - // A' = A - let rhs_1 = ctx.alloc_tmp(types::I64X2).only_reg().unwrap(); - ctx.emit(Inst::gen_move(rhs_1, rhs, ty)); - - // A' = A' >> 32 - // A' = Ah' * Bl - ctx.emit(Inst::xmm_rmi_reg( - SseOpcode::Psrlq, - RegMemImm::imm(32), - rhs_1, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmuludq, - RegMem::reg(lhs.clone()), - rhs_1, - )); - - // B' = B - let lhs_1 = ctx.alloc_tmp(types::I64X2).only_reg().unwrap(); - ctx.emit(Inst::gen_move(lhs_1, lhs, ty)); - - // B' = B' >> 32 - // B' = Bh' * Al - ctx.emit(Inst::xmm_rmi_reg( - SseOpcode::Psrlq, - RegMemImm::imm(32), - lhs_1, - )); - ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmuludq, RegMem::reg(rhs), lhs_1)); - - // B' = B' + A' - // B' = B' << 32 - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Paddq, - RegMem::reg(rhs_1.to_reg()), - lhs_1, - )); - ctx.emit(Inst::xmm_rmi_reg( - SseOpcode::Psllq, - RegMemImm::imm(32), - lhs_1, - )); - - // A' = A - // A' = Al' * Bl - // A' = A' + B' - // dst = A' - ctx.emit(Inst::gen_move(rhs_1, rhs, ty)); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Pmuludq, - RegMem::reg(lhs.clone()), - rhs_1, - )); - ctx.emit(Inst::xmm_rm_r( - SseOpcode::Paddq, - RegMem::reg(lhs_1.to_reg()), - rhs_1, - )); - ctx.emit(Inst::gen_move(dst, rhs_1.to_reg(), ty)); - } + unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); } else if ty.lane_count() > 1 { // Emit single instruction lowerings for the remaining vector // multiplications. @@ -2228,29 +1628,7 @@ fn lower_insn_to_regs>( dst.regs()[1], )); } else { - let size = if ty == types::I64 { - OperandSize::Size64 - } else { - OperandSize::Size32 - }; - let alu_op = AluRmiROpcode::Mul; - - // For commutative operations, try to commute operands if one is - // an immediate or direct memory reference. Do so by converting - // LHS to RMI; if reg, then always convert RHS to RMI; else, use - // LHS as RMI and convert RHS to reg. - let lhs = input_to_reg_mem_imm(ctx, inputs[0]); - let (lhs, rhs) = if let RegMemImm::Reg { reg: lhs_reg } = lhs { - let rhs = input_to_reg_mem_imm(ctx, inputs[1]); - (lhs_reg, rhs) - } else { - let rhs_reg = put_input_in_reg(ctx, inputs[1]); - (rhs_reg, lhs) - }; - - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - ctx.emit(Inst::mov_r_r(OperandSize::Size64, lhs, dst)); - ctx.emit(Inst::alu_rmi_r(size, alu_op, rhs, dst)); + unreachable!("implemented in ISLE") } } @@ -5801,7 +5179,14 @@ fn lower_insn_to_regs>( // Now the AtomicRmwSeq (pseudo-) instruction itself let op = inst_common::AtomicRmwOp::from(ctx.data(insn).atomic_rmw_op().unwrap()); - ctx.emit(Inst::AtomicRmwSeq { ty: ty_access, op }); + ctx.emit(Inst::AtomicRmwSeq { + ty: ty_access, + op, + address: regs::r9(), + operand: regs::r10(), + temp: Writable::from_reg(regs::r11()), + dst_old: Writable::from_reg(regs::rax()), + }); // And finally, copy the preordained AtomicRmwSeq output reg to its destination. ctx.emit(Inst::gen_move(dst, regs::rax(), types::I64)); @@ -5827,8 +5212,10 @@ fn lower_insn_to_regs>( )); ctx.emit(Inst::LockCmpxchg { ty: ty_access, - src: replacement, - dst: addr.into(), + mem: addr.into(), + replacement, + expected: regs::rax(), + dst_old: Writable::from_reg(regs::rax()), }); // And finally, copy the old value at the location to its destination reg. ctx.emit(Inst::gen_move(dst, regs::rax(), types::I64)); diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs new file mode 100644 index 0000000000..2a4c960301 --- /dev/null +++ b/cranelift/codegen/src/isa/x64/lower/isle.rs @@ -0,0 +1,428 @@ +//! ISLE integration glue code for x64 lowering. + +// Pull in the ISLE generated code. +mod generated_code; + +// Types that the generated ISLE code uses via `use super::*`. +use super::{ + is_mergeable_load, lower_to_amode, AluRmiROpcode, Inst as MInst, OperandSize, Reg, RegMemImm, + Writable, +}; +use crate::isa::x64::settings as x64_settings; +use crate::{ + ir::{immediates::*, types::*, Inst, InstructionData, Opcode, Value, ValueList}, + isa::x64::inst::{ + args::{ + Amode, Avx512Opcode, CmpOpcode, ExtMode, Imm8Reg, RegMem, ShiftKind, SseOpcode, CC, + }, + x64_map_regs, RegMapper, + }, + machinst::{get_output_reg, InsnInput, InsnOutput, LowerCtx}, +}; +use smallvec::SmallVec; +use std::convert::TryFrom; + +type Unit = (); +type ValueSlice<'a> = &'a [Value]; +type ValueArray2 = [Value; 2]; +type ValueArray3 = [Value; 3]; +type WritableReg = Writable; +type ValueRegs = crate::machinst::ValueRegs; + +pub struct SinkableLoad { + inst: Inst, + addr_input: InsnInput, + offset: i32, +} + +#[derive(Default)] +struct RegRenamer { + // Map of `(old, new)` register names. Use a `SmallVec` because we typically + // only have one or two renamings. + renames: SmallVec<[(Reg, Reg); 2]>, +} + +impl RegRenamer { + fn add_rename(&mut self, old: Reg, new: Reg) { + self.renames.push((old, new)); + } + + fn get_rename(&self, reg: Reg) -> Option { + self.renames + .iter() + .find(|(old, _)| reg == *old) + .map(|(_, new)| *new) + } +} + +impl RegMapper for RegRenamer { + fn get_use(&self, reg: Reg) -> Option { + self.get_rename(reg) + } + + fn get_def(&self, reg: Reg) -> Option { + self.get_rename(reg) + } + + fn get_mod(&self, reg: Reg) -> Option { + self.get_rename(reg) + } +} + +/// The main entry point for lowering with ISLE. +pub(crate) fn lower( + lower_ctx: &mut C, + isa_flags: &x64_settings::Flags, + outputs: &[InsnOutput], + inst: Inst, +) -> Result<(), ()> +where + C: LowerCtx, +{ + // TODO: reuse the ISLE context across lowerings so we can reuse its + // internal heap allocations. + let mut isle_ctx = IsleContext::new(lower_ctx, isa_flags); + + let temp_regs = generated_code::constructor_lower(&mut isle_ctx, inst).ok_or(())?; + let mut temp_regs = temp_regs.regs().iter(); + + // The ISLE generated code emits its own registers to define the + // instruction's lowered values in. We rename those registers to the + // registers they were assigned when their value was used as an operand in + // earlier lowerings. + let mut renamer = RegRenamer::default(); + for output in outputs { + let dsts = get_output_reg(isle_ctx.lower_ctx, *output); + for (temp, dst) in temp_regs.by_ref().zip(dsts.regs()) { + renamer.add_rename(*temp, dst.to_reg()); + } + } + + for mut inst in isle_ctx.into_emitted_insts() { + x64_map_regs(&mut inst, &renamer); + lower_ctx.emit(inst); + } + + Ok(()) +} + +pub struct IsleContext<'a, C> { + lower_ctx: &'a mut C, + isa_flags: &'a x64_settings::Flags, + emitted_insts: SmallVec<[MInst; 6]>, +} + +impl<'a, C> IsleContext<'a, C> { + pub fn new(lower_ctx: &'a mut C, isa_flags: &'a x64_settings::Flags) -> Self { + IsleContext { + lower_ctx, + isa_flags, + emitted_insts: SmallVec::new(), + } + } + + pub fn into_emitted_insts(self) -> SmallVec<[MInst; 6]> { + self.emitted_insts + } +} + +impl<'a, C> generated_code::Context for IsleContext<'a, C> +where + C: LowerCtx, +{ + #[inline] + fn unpack_value_array_2(&mut self, arr: &ValueArray2) -> (Value, Value) { + let [a, b] = *arr; + (a, b) + } + + #[inline] + fn pack_value_array_2(&mut self, a: Value, b: Value) -> ValueArray2 { + [a, b] + } + + #[inline] + fn unpack_value_array_3(&mut self, arr: &ValueArray3) -> (Value, Value, Value) { + let [a, b, c] = *arr; + (a, b, c) + } + + #[inline] + fn pack_value_array_3(&mut self, a: Value, b: Value, c: Value) -> ValueArray3 { + [a, b, c] + } + + #[inline] + fn value_reg(&mut self, reg: Reg) -> ValueRegs { + ValueRegs::one(reg) + } + + #[inline] + fn value_regs(&mut self, r1: Reg, r2: Reg) -> ValueRegs { + ValueRegs::two(r1, r2) + } + + #[inline] + fn temp_writable_reg(&mut self, ty: Type) -> WritableReg { + let value_regs = self.lower_ctx.alloc_tmp(ty); + value_regs.only_reg().unwrap() + } + + #[inline] + fn invalid_reg(&mut self) -> Reg { + Reg::invalid() + } + + #[inline] + fn put_in_reg(&mut self, val: Value) -> Reg { + self.lower_ctx.put_value_in_regs(val).only_reg().unwrap() + } + + #[inline] + fn put_in_regs(&mut self, val: Value) -> ValueRegs { + self.lower_ctx.put_value_in_regs(val) + } + + #[inline] + fn value_regs_get(&mut self, regs: ValueRegs, i: usize) -> Reg { + regs.regs()[i] + } + + #[inline] + fn u8_as_u64(&mut self, x: u8) -> u64 { + x.into() + } + + #[inline] + fn u16_as_u64(&mut self, x: u16) -> u64 { + x.into() + } + + #[inline] + fn u32_as_u64(&mut self, x: u32) -> u64 { + x.into() + } + + #[inline] + fn ty_bits(&mut self, ty: Type) -> u16 { + ty.bits() + } + + #[inline] + fn fits_in_64(&mut self, ty: Type) -> Option { + if ty.bits() <= 64 { + Some(ty) + } else { + None + } + } + + #[inline] + fn value_list_slice(&mut self, list: ValueList) -> ValueSlice { + list.as_slice(&self.lower_ctx.dfg().value_lists) + } + + #[inline] + fn unwrap_head_value_list_1(&mut self, list: ValueList) -> (Value, ValueSlice) { + match self.value_list_slice(list) { + [head, tail @ ..] => (*head, tail), + _ => out_of_line_panic("`unwrap_head_value_list_1` on empty `ValueList`"), + } + } + + #[inline] + fn unwrap_head_value_list_2(&mut self, list: ValueList) -> (Value, Value, ValueSlice) { + match self.value_list_slice(list) { + [head1, head2, tail @ ..] => (*head1, *head2, tail), + _ => out_of_line_panic( + "`unwrap_head_value_list_2` on list without at least two elements", + ), + } + } + + #[inline] + fn writable_reg_to_reg(&mut self, r: WritableReg) -> Reg { + r.to_reg() + } + + #[inline] + fn u64_from_imm64(&mut self, imm: Imm64) -> u64 { + imm.bits() as u64 + } + + #[inline] + fn inst_results(&mut self, inst: Inst) -> ValueSlice { + self.lower_ctx.dfg().inst_results(inst) + } + + #[inline] + fn first_result(&mut self, inst: Inst) -> Option { + self.lower_ctx.dfg().inst_results(inst).first().copied() + } + + #[inline] + fn inst_data(&mut self, inst: Inst) -> InstructionData { + self.lower_ctx.dfg()[inst].clone() + } + + #[inline] + fn value_type(&mut self, val: Value) -> Type { + self.lower_ctx.dfg().value_type(val) + } + + #[inline] + fn multi_lane(&mut self, ty: Type) -> Option<(u8, u16)> { + if ty.lane_count() > 1 { + Some((ty.lane_bits(), ty.lane_count())) + } else { + None + } + } + + #[inline] + fn def_inst(&mut self, val: Value) -> Option { + self.lower_ctx.dfg().value_def(val).inst() + } + + #[inline] + fn operand_size_of_type(&mut self, ty: Type) -> OperandSize { + if ty.bits() == 64 { + OperandSize::Size64 + } else { + OperandSize::Size32 + } + } + + fn put_in_reg_mem(&mut self, val: Value) -> RegMem { + let inputs = self.lower_ctx.get_value_as_source_or_const(val); + + if let Some(c) = inputs.constant { + // Generate constants fresh at each use to minimize long-range + // register pressure. + let ty = self.value_type(val); + return RegMem::reg(generated_code::constructor_imm(self, ty, c).unwrap()); + } + + if let Some((src_insn, 0)) = inputs.inst { + if let Some((addr_input, offset)) = is_mergeable_load(self.lower_ctx, src_insn) { + self.lower_ctx.sink_inst(src_insn); + let amode = lower_to_amode(self.lower_ctx, addr_input, offset); + return RegMem::mem(amode); + } + } + + RegMem::reg(self.put_in_reg(val)) + } + + #[inline] + fn avx512vl_enabled(&mut self, _: Type) -> Option<()> { + if self.isa_flags.use_avx512vl_simd() { + Some(()) + } else { + None + } + } + + #[inline] + fn avx512dq_enabled(&mut self, _: Type) -> Option<()> { + if self.isa_flags.use_avx512dq_simd() { + Some(()) + } else { + None + } + } + + #[inline] + fn imm8_from_value(&mut self, val: Value) -> Option { + let inst = self.lower_ctx.dfg().value_def(val).inst()?; + let constant = self.lower_ctx.get_constant(inst)?; + let imm = u8::try_from(constant).ok()?; + Some(Imm8Reg::Imm8 { imm }) + } + + #[inline] + fn simm32_from_value(&mut self, val: Value) -> Option { + let inst = self.lower_ctx.dfg().value_def(val).inst()?; + let constant: u64 = self.lower_ctx.get_constant(inst)?; + let constant = constant as i64; + to_simm32(constant) + } + + #[inline] + fn simm32_from_imm64(&mut self, imm: Imm64) -> Option { + to_simm32(imm.bits()) + } + + fn sinkable_load(&mut self, val: Value) -> Option { + let input = self.lower_ctx.get_value_as_source_or_const(val); + if let Some((inst, 0)) = input.inst { + if let Some((addr_input, offset)) = is_mergeable_load(self.lower_ctx, inst) { + return Some(SinkableLoad { + inst, + addr_input, + offset, + }); + } + } + None + } + + fn sink_load(&mut self, load: &SinkableLoad) -> RegMemImm { + self.lower_ctx.sink_inst(load.inst); + + let flags = self + .lower_ctx + .memflags(load.inst) + .expect("sinkable loads should have memflags"); + + let base = self + .lower_ctx + .put_input_in_regs(load.addr_input.insn, load.addr_input.input) + .only_reg() + .unwrap(); + + RegMemImm::Mem { + addr: Amode::imm_reg(load.offset as u32, base) + .with_flags(flags) + .into(), + } + } + + #[inline] + fn ext_mode(&mut self, from_bits: u16, to_bits: u16) -> ExtMode { + ExtMode::new(from_bits, to_bits).unwrap() + } + + fn emit(&mut self, inst: &MInst) -> Unit { + for inst in inst.clone().mov_mitosis() { + self.emitted_insts.push(inst); + } + } + + #[inline] + fn nonzero_u64_fits_in_u32(&mut self, x: u64) -> Option { + if x != 0 && x < u64::from(u32::MAX) { + Some(x) + } else { + None + } + } +} + +#[inline] +fn to_simm32(constant: i64) -> Option { + if constant == ((constant << 32) >> 32) { + Some(RegMemImm::Imm { + simm32: constant as u32, + }) + } else { + None + } +} + +#[inline(never)] +#[cold] +#[track_caller] +fn out_of_line_panic(msg: &str) -> ! { + panic!("{}", msg); +} diff --git a/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs b/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs new file mode 100644 index 0000000000..8050f87947 --- /dev/null +++ b/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs @@ -0,0 +1,3496 @@ +// GENERATED BY ISLE. DO NOT EDIT! +// +// Generated automatically from the instruction-selection DSL code in: +// - src/clif.isle +// - src/prelude.isle +// - src/isa/x64/inst.isle +// - src/isa/x64/lower.isle + +#![allow(dead_code, unreachable_code, unreachable_patterns)] +#![allow(unused_imports, unused_variables, non_snake_case)] +#![allow(irrefutable_let_patterns)] + +use super::*; // Pulls in all external types. + +/// Context during lowering: an implementation of this trait +/// must be provided with all external constructors and extractors. +/// A mutable borrow is passed along through all lowering logic. +pub trait Context { + fn unpack_value_array_2(&mut self, arg0: &ValueArray2) -> (Value, Value); + fn pack_value_array_2(&mut self, arg0: Value, arg1: Value) -> ValueArray2; + fn unpack_value_array_3(&mut self, arg0: &ValueArray3) -> (Value, Value, Value); + fn pack_value_array_3(&mut self, arg0: Value, arg1: Value, arg2: Value) -> ValueArray3; + fn value_reg(&mut self, arg0: Reg) -> ValueRegs; + fn value_regs(&mut self, arg0: Reg, arg1: Reg) -> ValueRegs; + fn temp_writable_reg(&mut self, arg0: Type) -> WritableReg; + fn invalid_reg(&mut self) -> Reg; + fn put_in_reg(&mut self, arg0: Value) -> Reg; + fn put_in_regs(&mut self, arg0: Value) -> ValueRegs; + fn value_regs_get(&mut self, arg0: ValueRegs, arg1: usize) -> Reg; + fn u8_as_u64(&mut self, arg0: u8) -> u64; + fn u16_as_u64(&mut self, arg0: u16) -> u64; + fn u32_as_u64(&mut self, arg0: u32) -> u64; + fn ty_bits(&mut self, arg0: Type) -> u16; + fn fits_in_64(&mut self, arg0: Type) -> Option; + fn value_list_slice(&mut self, arg0: ValueList) -> ValueSlice; + fn unwrap_head_value_list_1(&mut self, arg0: ValueList) -> (Value, ValueSlice); + fn unwrap_head_value_list_2(&mut self, arg0: ValueList) -> (Value, Value, ValueSlice); + fn writable_reg_to_reg(&mut self, arg0: WritableReg) -> Reg; + fn u64_from_imm64(&mut self, arg0: Imm64) -> u64; + fn inst_results(&mut self, arg0: Inst) -> ValueSlice; + fn first_result(&mut self, arg0: Inst) -> Option; + fn inst_data(&mut self, arg0: Inst) -> InstructionData; + fn value_type(&mut self, arg0: Value) -> Type; + fn multi_lane(&mut self, arg0: Type) -> Option<(u8, u16)>; + fn def_inst(&mut self, arg0: Value) -> Option; + fn operand_size_of_type(&mut self, arg0: Type) -> OperandSize; + fn put_in_reg_mem(&mut self, arg0: Value) -> RegMem; + fn avx512vl_enabled(&mut self, arg0: Type) -> Option<()>; + fn avx512dq_enabled(&mut self, arg0: Type) -> Option<()>; + fn imm8_from_value(&mut self, arg0: Value) -> Option; + fn simm32_from_value(&mut self, arg0: Value) -> Option; + fn simm32_from_imm64(&mut self, arg0: Imm64) -> Option; + fn sinkable_load(&mut self, arg0: Value) -> Option; + fn sink_load(&mut self, arg0: &SinkableLoad) -> RegMemImm; + fn ext_mode(&mut self, arg0: u16, arg1: u16) -> ExtMode; + fn emit(&mut self, arg0: &MInst) -> Unit; + fn nonzero_u64_fits_in_u32(&mut self, arg0: u64) -> Option; +} + +/// Internal type ProducesFlags: defined at src/isa/x64/inst.isle line 368. +#[derive(Clone, Debug)] +pub enum ProducesFlags { + ProducesFlags { inst: MInst, result: Reg }, +} + +/// Internal type ConsumesFlags: defined at src/isa/x64/inst.isle line 371. +#[derive(Clone, Debug)] +pub enum ConsumesFlags { + ConsumesFlags { inst: MInst, result: Reg }, +} + +/// Internal type ExtendKind: defined at src/isa/x64/inst.isle line 409. +#[derive(Clone, Debug)] +pub enum ExtendKind { + Sign, + Zero, +} + +// Generated as internal constructor for term temp_reg. +pub fn constructor_temp_reg(ctx: &mut C, arg0: Type) -> Option { + let pattern0_0 = arg0; + // Rule at src/prelude.isle line 57. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr1_0); +} + +// Generated as internal constructor for term lo_reg. +pub fn constructor_lo_reg(ctx: &mut C, arg0: Value) -> Option { + let pattern0_0 = arg0; + // Rule at src/prelude.isle line 92. + let expr0_0 = C::put_in_regs(ctx, pattern0_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + return Some(expr2_0); +} + +// Generated as internal constructor for term operand_size_bits. +pub fn constructor_operand_size_bits(ctx: &mut C, arg0: &OperandSize) -> Option { + let pattern0_0 = arg0; + match pattern0_0 { + &OperandSize::Size8 => { + // Rule at src/isa/x64/inst.isle line 69. + let expr0_0: u16 = 8; + return Some(expr0_0); + } + &OperandSize::Size16 => { + // Rule at src/isa/x64/inst.isle line 70. + let expr0_0: u16 = 16; + return Some(expr0_0); + } + &OperandSize::Size32 => { + // Rule at src/isa/x64/inst.isle line 71. + let expr0_0: u16 = 32; + return Some(expr0_0); + } + &OperandSize::Size64 => { + // Rule at src/isa/x64/inst.isle line 72. + let expr0_0: u16 = 64; + return Some(expr0_0); + } + _ => {} + } + return None; +} + +// Generated as internal constructor for term with_flags. +pub fn constructor_with_flags( + ctx: &mut C, + arg0: &ProducesFlags, + arg1: &ConsumesFlags, +) -> Option { + let pattern0_0 = arg0; + if let &ProducesFlags::ProducesFlags { + inst: ref pattern1_0, + result: pattern1_1, + } = pattern0_0 + { + let pattern2_0 = arg1; + if let &ConsumesFlags::ConsumesFlags { + inst: ref pattern3_0, + result: pattern3_1, + } = pattern2_0 + { + // Rule at src/isa/x64/inst.isle line 381. + let expr0_0 = C::emit(ctx, &pattern1_0); + let expr1_0 = C::emit(ctx, &pattern3_0); + let expr2_0 = C::value_regs(ctx, pattern1_1, pattern3_1); + return Some(expr2_0); + } + } + return None; +} + +// Generated as internal constructor for term with_flags_1. +pub fn constructor_with_flags_1( + ctx: &mut C, + arg0: &ProducesFlags, + arg1: &ConsumesFlags, +) -> Option { + let pattern0_0 = arg0; + if let &ProducesFlags::ProducesFlags { + inst: ref pattern1_0, + result: pattern1_1, + } = pattern0_0 + { + let pattern2_0 = arg1; + if let &ConsumesFlags::ConsumesFlags { + inst: ref pattern3_0, + result: pattern3_1, + } = pattern2_0 + { + // Rule at src/isa/x64/inst.isle line 389. + let expr0_0 = C::emit(ctx, &pattern1_0); + let expr1_0 = C::emit(ctx, &pattern3_0); + return Some(pattern3_1); + } + } + return None; +} + +// Generated as internal constructor for term with_flags_2. +pub fn constructor_with_flags_2( + ctx: &mut C, + arg0: &ProducesFlags, + arg1: &ConsumesFlags, + arg2: &ConsumesFlags, +) -> Option { + let pattern0_0 = arg0; + if let &ProducesFlags::ProducesFlags { + inst: ref pattern1_0, + result: pattern1_1, + } = pattern0_0 + { + let pattern2_0 = arg1; + if let &ConsumesFlags::ConsumesFlags { + inst: ref pattern3_0, + result: pattern3_1, + } = pattern2_0 + { + let pattern4_0 = arg2; + if let &ConsumesFlags::ConsumesFlags { + inst: ref pattern5_0, + result: pattern5_1, + } = pattern4_0 + { + // Rule at src/isa/x64/inst.isle line 399. + let expr0_0 = C::emit(ctx, &pattern1_0); + let expr1_0 = C::emit(ctx, &pattern3_0); + let expr2_0 = C::emit(ctx, &pattern5_0); + let expr3_0 = C::value_regs(ctx, pattern3_1, pattern5_1); + return Some(expr3_0); + } + } + } + return None; +} + +// Generated as internal constructor for term extend_to_reg. +pub fn constructor_extend_to_reg( + ctx: &mut C, + arg0: Value, + arg1: Type, + arg2: &ExtendKind, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = C::value_type(ctx, pattern0_0); + let pattern2_0 = arg1; + if pattern2_0 == pattern1_0 { + let pattern4_0 = arg2; + // Rule at src/isa/x64/inst.isle line 421. + let expr0_0 = C::put_in_reg(ctx, pattern0_0); + return Some(expr0_0); + } + let pattern3_0 = arg2; + // Rule at src/isa/x64/inst.isle line 424. + let expr0_0 = C::ty_bits(ctx, pattern1_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern2_0); + let expr2_0 = constructor_operand_size_bits(ctx, &expr1_0)?; + let expr3_0 = C::ext_mode(ctx, expr0_0, expr2_0); + let expr4_0 = C::put_in_reg_mem(ctx, pattern0_0); + let expr5_0 = constructor_extend(ctx, pattern3_0, pattern2_0, &expr3_0, &expr4_0)?; + return Some(expr5_0); +} + +// Generated as internal constructor for term extend. +pub fn constructor_extend( + ctx: &mut C, + arg0: &ExtendKind, + arg1: Type, + arg2: &ExtMode, + arg3: &RegMem, +) -> Option { + let pattern0_0 = arg0; + match pattern0_0 { + &ExtendKind::Sign => { + let pattern2_0 = arg1; + let pattern3_0 = arg2; + let pattern4_0 = arg3; + // Rule at src/isa/x64/inst.isle line 444. + let expr0_0 = constructor_movsx(ctx, pattern2_0, pattern3_0, pattern4_0)?; + return Some(expr0_0); + } + &ExtendKind::Zero => { + let pattern2_0 = arg1; + let pattern3_0 = arg2; + let pattern4_0 = arg3; + // Rule at src/isa/x64/inst.isle line 440. + let expr0_0 = constructor_movzx(ctx, pattern2_0, pattern3_0, pattern4_0)?; + return Some(expr0_0); + } + _ => {} + } + return None; +} + +// Generated as internal constructor for term alu_rmi_r. +pub fn constructor_alu_rmi_r( + ctx: &mut C, + arg0: Type, + arg1: &AluRmiROpcode, + arg2: Reg, + arg3: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 462. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = MInst::AluRmiR { + size: expr1_0, + op: pattern1_0.clone(), + src1: pattern2_0, + src2: pattern3_0.clone(), + dst: expr0_0, + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term add. +pub fn constructor_add( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 470. + let expr0_0 = AluRmiROpcode::Add; + let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term add_with_flags. +pub fn constructor_add_with_flags( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 478. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = AluRmiROpcode::Add; + let expr3_0 = MInst::AluRmiR { + size: expr1_0, + op: expr2_0, + src1: pattern1_0, + src2: pattern2_0.clone(), + dst: expr0_0, + }; + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr5_0 = ProducesFlags::ProducesFlags { + inst: expr3_0, + result: expr4_0, + }; + return Some(expr5_0); +} + +// Generated as internal constructor for term adc. +pub fn constructor_adc( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 489. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = AluRmiROpcode::Adc; + let expr3_0 = MInst::AluRmiR { + size: expr1_0, + op: expr2_0, + src1: pattern1_0, + src2: pattern2_0.clone(), + dst: expr0_0, + }; + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr5_0 = ConsumesFlags::ConsumesFlags { + inst: expr3_0, + result: expr4_0, + }; + return Some(expr5_0); +} + +// Generated as internal constructor for term sub. +pub fn constructor_sub( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 500. + let expr0_0 = AluRmiROpcode::Sub; + let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term sub_with_flags. +pub fn constructor_sub_with_flags( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 508. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = AluRmiROpcode::Sub; + let expr3_0 = MInst::AluRmiR { + size: expr1_0, + op: expr2_0, + src1: pattern1_0, + src2: pattern2_0.clone(), + dst: expr0_0, + }; + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr5_0 = ProducesFlags::ProducesFlags { + inst: expr3_0, + result: expr4_0, + }; + return Some(expr5_0); +} + +// Generated as internal constructor for term sbb. +pub fn constructor_sbb( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 519. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = AluRmiROpcode::Sbb; + let expr3_0 = MInst::AluRmiR { + size: expr1_0, + op: expr2_0, + src1: pattern1_0, + src2: pattern2_0.clone(), + dst: expr0_0, + }; + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr5_0 = ConsumesFlags::ConsumesFlags { + inst: expr3_0, + result: expr4_0, + }; + return Some(expr5_0); +} + +// Generated as internal constructor for term mul. +pub fn constructor_mul( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 530. + let expr0_0 = AluRmiROpcode::Mul; + let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term m_and. +pub fn constructor_m_and( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 541. + let expr0_0 = AluRmiROpcode::And; + let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term or. +pub fn constructor_or( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 549. + let expr0_0 = AluRmiROpcode::Or; + let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term xor. +pub fn constructor_xor( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 557. + let expr0_0 = AluRmiROpcode::Xor; + let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term imm. +pub fn constructor_imm(ctx: &mut C, arg0: Type, arg1: u64) -> Option { + let pattern0_0 = arg0; + if pattern0_0 == I64 { + let pattern2_0 = arg1; + if let Some(pattern3_0) = C::nonzero_u64_fits_in_u32(ctx, pattern2_0) { + // Rule at src/isa/x64/inst.isle line 576. + let expr0_0: Type = I64; + let expr1_0 = C::temp_writable_reg(ctx, expr0_0); + let expr2_0 = OperandSize::Size32; + let expr3_0 = MInst::Imm { + dst_size: expr2_0, + simm64: pattern3_0, + dst: expr1_0, + }; + let expr4_0 = C::emit(ctx, &expr3_0); + let expr5_0 = C::writable_reg_to_reg(ctx, expr1_0); + return Some(expr5_0); + } + } + let pattern1_0 = arg1; + if pattern1_0 == 0 { + // Rule at src/isa/x64/inst.isle line 582. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr2_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr3_0 = AluRmiROpcode::Xor; + let expr4_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr5_0 = MInst::AluRmiR { + size: expr2_0, + op: expr3_0, + src1: expr1_0, + src2: expr4_0, + dst: expr0_0, + }; + let expr6_0 = C::emit(ctx, &expr5_0); + return Some(expr1_0); + } + // Rule at src/isa/x64/inst.isle line 565. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = MInst::Imm { + dst_size: expr1_0, + simm64: pattern1_0, + dst: expr0_0, + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term shift_r. +pub fn constructor_shift_r( + ctx: &mut C, + arg0: Type, + arg1: &ShiftKind, + arg2: Reg, + arg3: &Imm8Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 595. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = MInst::ShiftR { + size: expr1_0, + kind: pattern1_0.clone(), + src: pattern2_0, + num_bits: pattern3_0.clone(), + dst: expr0_0, + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term m_rotl. +pub fn constructor_m_rotl( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &Imm8Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 604. + let expr0_0 = ShiftKind::RotateLeft; + let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term shl. +pub fn constructor_shl( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &Imm8Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 609. + let expr0_0 = ShiftKind::ShiftLeft; + let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term shr. +pub fn constructor_shr( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &Imm8Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 614. + let expr0_0 = ShiftKind::ShiftRightLogical; + let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term sar. +pub fn constructor_sar( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &Imm8Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 619. + let expr0_0 = ShiftKind::ShiftRightArithmetic; + let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term cmp_rmi_r. +pub fn constructor_cmp_rmi_r( + ctx: &mut C, + arg0: &OperandSize, + arg1: &CmpOpcode, + arg2: &RegMemImm, + arg3: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 624. + let expr0_0 = MInst::CmpRmiR { + size: pattern0_0.clone(), + opcode: pattern1_0.clone(), + src: pattern2_0.clone(), + dst: pattern3_0, + }; + let expr1_0 = C::invalid_reg(ctx); + let expr2_0 = ProducesFlags::ProducesFlags { + inst: expr0_0, + result: expr1_0, + }; + return Some(expr2_0); +} + +// Generated as internal constructor for term cmp. +pub fn constructor_cmp( + ctx: &mut C, + arg0: &OperandSize, + arg1: &RegMemImm, + arg2: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 633. + let expr0_0 = CmpOpcode::Cmp; + let expr1_0 = constructor_cmp_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term test. +pub fn constructor_test( + ctx: &mut C, + arg0: &OperandSize, + arg1: &RegMemImm, + arg2: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 638. + let expr0_0 = CmpOpcode::Test; + let expr1_0 = constructor_cmp_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term cmove. +pub fn constructor_cmove( + ctx: &mut C, + arg0: Type, + arg1: &CC, + arg2: &RegMem, + arg3: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 643. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr2_0 = MInst::Cmove { + size: expr1_0, + cc: pattern1_0.clone(), + consequent: pattern2_0.clone(), + alternative: pattern3_0, + dst: expr0_0, + }; + let expr3_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr4_0 = ConsumesFlags::ConsumesFlags { + inst: expr2_0, + result: expr3_0, + }; + return Some(expr4_0); +} + +// Generated as internal constructor for term movzx. +pub fn constructor_movzx( + ctx: &mut C, + arg0: Type, + arg1: &ExtMode, + arg2: &RegMem, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 651. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = MInst::MovzxRmR { + ext_mode: pattern1_0.clone(), + src: pattern2_0.clone(), + dst: expr0_0, + }; + let expr2_0 = C::emit(ctx, &expr1_0); + let expr3_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr3_0); +} + +// Generated as internal constructor for term movsx. +pub fn constructor_movsx( + ctx: &mut C, + arg0: Type, + arg1: &ExtMode, + arg2: &RegMem, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 658. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = MInst::MovsxRmR { + ext_mode: pattern1_0.clone(), + src: pattern2_0.clone(), + dst: expr0_0, + }; + let expr2_0 = C::emit(ctx, &expr1_0); + let expr3_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr3_0); +} + +// Generated as internal constructor for term xmm_rm_r. +pub fn constructor_xmm_rm_r( + ctx: &mut C, + arg0: Type, + arg1: &SseOpcode, + arg2: Reg, + arg3: &RegMem, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 665. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = MInst::XmmRmR { + op: pattern1_0.clone(), + src1: pattern2_0, + src2: pattern3_0.clone(), + dst: expr0_0, + }; + let expr2_0 = C::emit(ctx, &expr1_0); + let expr3_0 = C::writable_reg_to_reg(ctx, expr0_0); + return Some(expr3_0); +} + +// Generated as internal constructor for term paddb. +pub fn constructor_paddb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 672. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Paddb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddw. +pub fn constructor_paddw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 677. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Paddw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddd. +pub fn constructor_paddd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 682. + let expr0_0: Type = I32X4; + let expr1_0 = SseOpcode::Paddd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddq. +pub fn constructor_paddq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 687. + let expr0_0: Type = I64X2; + let expr1_0 = SseOpcode::Paddq; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddsb. +pub fn constructor_paddsb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 692. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Paddsb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddsw. +pub fn constructor_paddsw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 697. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Paddsw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddusb. +pub fn constructor_paddusb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 702. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Paddusb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term paddusw. +pub fn constructor_paddusw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 707. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Paddusw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubb. +pub fn constructor_psubb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 712. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Psubb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubw. +pub fn constructor_psubw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 717. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Psubw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubd. +pub fn constructor_psubd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 722. + let expr0_0: Type = I32X4; + let expr1_0 = SseOpcode::Psubd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubq. +pub fn constructor_psubq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 727. + let expr0_0: Type = I64X2; + let expr1_0 = SseOpcode::Psubq; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubsb. +pub fn constructor_psubsb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 732. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Psubsb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubsw. +pub fn constructor_psubsw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 737. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Psubsw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubusb. +pub fn constructor_psubusb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 742. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Psubusb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term psubusw. +pub fn constructor_psubusw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 747. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Psubusw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pavgb. +pub fn constructor_pavgb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 752. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Pavgb; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pavgw. +pub fn constructor_pavgw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 757. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Pavgw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pand. +pub fn constructor_pand(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 762. + let expr0_0: Type = F32X4; + let expr1_0 = SseOpcode::Pand; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term andps. +pub fn constructor_andps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 767. + let expr0_0: Type = F32X4; + let expr1_0 = SseOpcode::Andps; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term andpd. +pub fn constructor_andpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 772. + let expr0_0: Type = F64X2; + let expr1_0 = SseOpcode::Andpd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term por. +pub fn constructor_por(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 777. + let expr0_0: Type = F32X4; + let expr1_0 = SseOpcode::Por; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term orps. +pub fn constructor_orps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 782. + let expr0_0: Type = F32X4; + let expr1_0 = SseOpcode::Orps; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term orpd. +pub fn constructor_orpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 787. + let expr0_0: Type = F64X2; + let expr1_0 = SseOpcode::Orpd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pxor. +pub fn constructor_pxor(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 792. + let expr0_0: Type = I8X16; + let expr1_0 = SseOpcode::Pxor; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term xorps. +pub fn constructor_xorps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 797. + let expr0_0: Type = F32X4; + let expr1_0 = SseOpcode::Xorps; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term xorpd. +pub fn constructor_xorpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 802. + let expr0_0: Type = F64X2; + let expr1_0 = SseOpcode::Xorpd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pmullw. +pub fn constructor_pmullw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 807. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Pmullw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pmulld. +pub fn constructor_pmulld(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 812. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Pmulld; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pmulhw. +pub fn constructor_pmulhw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 817. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Pmulhw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pmulhuw. +pub fn constructor_pmulhuw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 822. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Pmulhuw; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pmuldq. +pub fn constructor_pmuldq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 827. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Pmuldq; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pmuludq. +pub fn constructor_pmuludq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 832. + let expr0_0: Type = I64X2; + let expr1_0 = SseOpcode::Pmuludq; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term punpckhwd. +pub fn constructor_punpckhwd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 837. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Punpckhwd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term punpcklwd. +pub fn constructor_punpcklwd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 842. + let expr0_0: Type = I16X8; + let expr1_0 = SseOpcode::Punpcklwd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term xmm_rm_r_imm. +pub fn constructor_xmm_rm_r_imm( + ctx: &mut C, + arg0: &SseOpcode, + arg1: Reg, + arg2: &RegMem, + arg3: u8, + arg4: &OperandSize, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + let pattern4_0 = arg4; + // Rule at src/isa/x64/inst.isle line 847. + let expr0_0: Type = I8X16; + let expr1_0 = C::temp_writable_reg(ctx, expr0_0); + let expr2_0 = MInst::XmmRmRImm { + op: pattern0_0.clone(), + src1: pattern1_0, + src2: pattern2_0.clone(), + dst: expr1_0, + imm: pattern3_0, + size: pattern4_0.clone(), + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr1_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term palignr. +pub fn constructor_palignr( + ctx: &mut C, + arg0: Reg, + arg1: &RegMem, + arg2: u8, + arg3: &OperandSize, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 859. + let expr0_0 = SseOpcode::Palignr; + let expr1_0 = constructor_xmm_rm_r_imm( + ctx, &expr0_0, pattern0_0, pattern1_0, pattern2_0, pattern3_0, + )?; + return Some(expr1_0); +} + +// Generated as internal constructor for term pshufd. +pub fn constructor_pshufd( + ctx: &mut C, + arg0: &RegMem, + arg1: u8, + arg2: &OperandSize, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 868. + let expr0_0: Type = I8X16; + let expr1_0 = C::temp_writable_reg(ctx, expr0_0); + let expr2_0 = C::writable_reg_to_reg(ctx, expr1_0); + let expr3_0 = SseOpcode::Pshufd; + let expr4_0 = MInst::XmmRmRImm { + op: expr3_0, + src1: expr2_0, + src2: pattern0_0.clone(), + dst: expr1_0, + imm: pattern1_0, + size: pattern2_0.clone(), + }; + let expr5_0 = C::emit(ctx, &expr4_0); + return Some(expr2_0); +} + +// Generated as internal constructor for term xmm_unary_rm_r. +pub fn constructor_xmm_unary_rm_r( + ctx: &mut C, + arg0: &SseOpcode, + arg1: &RegMem, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 881. + let expr0_0: Type = I8X16; + let expr1_0 = C::temp_writable_reg(ctx, expr0_0); + let expr2_0 = MInst::XmmUnaryRmR { + op: pattern0_0.clone(), + src: pattern1_0.clone(), + dst: expr1_0, + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr1_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term pmovsxbw. +pub fn constructor_pmovsxbw(ctx: &mut C, arg0: &RegMem) -> Option { + let pattern0_0 = arg0; + // Rule at src/isa/x64/inst.isle line 888. + let expr0_0 = SseOpcode::Pmovsxbw; + let expr1_0 = constructor_xmm_unary_rm_r(ctx, &expr0_0, pattern0_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term pmovzxbw. +pub fn constructor_pmovzxbw(ctx: &mut C, arg0: &RegMem) -> Option { + let pattern0_0 = arg0; + // Rule at src/isa/x64/inst.isle line 893. + let expr0_0 = SseOpcode::Pmovzxbw; + let expr1_0 = constructor_xmm_unary_rm_r(ctx, &expr0_0, pattern0_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term xmm_rm_r_evex. +pub fn constructor_xmm_rm_r_evex( + ctx: &mut C, + arg0: &Avx512Opcode, + arg1: &RegMem, + arg2: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 898. + let expr0_0: Type = I8X16; + let expr1_0 = C::temp_writable_reg(ctx, expr0_0); + let expr2_0 = MInst::XmmRmREvex { + op: pattern0_0.clone(), + src1: pattern1_0.clone(), + src2: pattern2_0, + dst: expr1_0, + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr1_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term vpmullq. +pub fn constructor_vpmullq(ctx: &mut C, arg0: &RegMem, arg1: Reg) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 910. + let expr0_0 = Avx512Opcode::Vpmullq; + let expr1_0 = constructor_xmm_rm_r_evex(ctx, &expr0_0, pattern0_0, pattern1_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term xmm_rmi_reg. +pub fn constructor_xmm_rmi_reg( + ctx: &mut C, + arg0: &SseOpcode, + arg1: Reg, + arg2: &RegMemImm, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 917. + let expr0_0: Type = I8X16; + let expr1_0 = C::temp_writable_reg(ctx, expr0_0); + let expr2_0 = MInst::XmmRmiReg { + opcode: pattern0_0.clone(), + src1: pattern1_0, + src2: pattern2_0.clone(), + dst: expr1_0, + }; + let expr3_0 = C::emit(ctx, &expr2_0); + let expr4_0 = C::writable_reg_to_reg(ctx, expr1_0); + return Some(expr4_0); +} + +// Generated as internal constructor for term psllq. +pub fn constructor_psllq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 927. + let expr0_0 = SseOpcode::Psllq; + let expr1_0 = constructor_xmm_rmi_reg(ctx, &expr0_0, pattern0_0, pattern1_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term psrlq. +pub fn constructor_psrlq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 932. + let expr0_0 = SseOpcode::Psrlq; + let expr1_0 = constructor_xmm_rmi_reg(ctx, &expr0_0, pattern0_0, pattern1_0)?; + return Some(expr1_0); +} + +// Generated as internal constructor for term lower. +pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option { + let pattern0_0 = arg0; + if let Some(pattern1_0) = C::first_result(ctx, pattern0_0) { + let pattern2_0 = C::value_type(ctx, pattern1_0); + if pattern2_0 == B128 { + let pattern4_0 = C::inst_data(ctx, pattern0_0); + match &pattern4_0 { + &InstructionData::UnaryBool { + opcode: ref pattern5_0, + imm: pattern5_1, + } => { + if let &Opcode::Bconst = &pattern5_0 { + if pattern5_1 == true { + // Rule at src/isa/x64/lower.isle line 39. + let expr0_0: Type = B64; + let expr1_0: u64 = 1; + let expr2_0 = constructor_imm(ctx, expr0_0, expr1_0)?; + let expr3_0: Type = B64; + let expr4_0: u64 = 0; + let expr5_0 = constructor_imm(ctx, expr3_0, expr4_0)?; + let expr6_0 = C::value_regs(ctx, expr2_0, expr5_0); + return Some(expr6_0); + } + if pattern5_1 == false { + // Rule at src/isa/x64/lower.isle line 34. + let expr0_0: Type = B64; + let expr1_0: u64 = 0; + let expr2_0 = constructor_imm(ctx, expr0_0, expr1_0)?; + let expr3_0: Type = B64; + let expr4_0: u64 = 0; + let expr5_0 = constructor_imm(ctx, expr3_0, expr4_0)?; + let expr6_0 = C::value_regs(ctx, expr2_0, expr5_0); + return Some(expr6_0); + } + } + } + &InstructionData::Binary { + opcode: ref pattern5_0, + args: ref pattern5_1, + } => { + match &pattern5_0 { + &Opcode::Band => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 358. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr6_0: Type = I64; + let expr7_0 = RegMemImm::Reg { reg: expr5_0 }; + let expr8_0 = constructor_m_and(ctx, expr6_0, expr2_0, &expr7_0)?; + let expr9_0 = C::value_regs(ctx, expr8_0, expr4_0); + return Some(expr9_0); + } + &Opcode::Bor => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 436. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr6_0: Type = I64; + let expr7_0 = RegMemImm::Reg { reg: expr5_0 }; + let expr8_0 = constructor_or(ctx, expr6_0, expr2_0, &expr7_0)?; + let expr9_0 = C::value_regs(ctx, expr8_0, expr4_0); + return Some(expr9_0); + } + &Opcode::Bxor => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 512. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr6_0: Type = I64; + let expr7_0 = RegMemImm::Reg { reg: expr5_0 }; + let expr8_0 = constructor_xor(ctx, expr6_0, expr2_0, &expr7_0)?; + let expr9_0 = C::value_regs(ctx, expr8_0, expr4_0); + return Some(expr9_0); + } + _ => {} + } + } + _ => {} + } + } + if pattern2_0 == I128 { + let pattern4_0 = C::inst_data(ctx, pattern0_0); + match &pattern4_0 { + &InstructionData::UnaryImm { + opcode: ref pattern5_0, + imm: pattern5_1, + } => { + if let &Opcode::Iconst = &pattern5_0 { + let pattern7_0 = C::u64_from_imm64(ctx, pattern5_1); + // Rule at src/isa/x64/lower.isle line 15. + let expr0_0: Type = I64; + let expr1_0 = constructor_imm(ctx, expr0_0, pattern7_0)?; + let expr2_0: Type = I64; + let expr3_0: u64 = 0; + let expr4_0 = constructor_imm(ctx, expr2_0, expr3_0)?; + let expr5_0 = C::value_regs(ctx, expr1_0, expr4_0); + return Some(expr5_0); + } + } + &InstructionData::Binary { + opcode: ref pattern5_0, + args: ref pattern5_1, + } => { + match &pattern5_0 { + &Opcode::Iadd => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 107. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = C::put_in_regs(ctx, pattern7_1); + let expr6_0: usize = 0; + let expr7_0 = C::value_regs_get(ctx, expr5_0, expr6_0); + let expr8_0: usize = 1; + let expr9_0 = C::value_regs_get(ctx, expr5_0, expr8_0); + let expr10_0: Type = I64; + let expr11_0 = RegMemImm::Reg { reg: expr7_0 }; + let expr12_0 = + constructor_add_with_flags(ctx, expr10_0, expr2_0, &expr11_0)?; + let expr13_0: Type = I64; + let expr14_0 = RegMemImm::Reg { reg: expr9_0 }; + let expr15_0 = constructor_adc(ctx, expr13_0, expr4_0, &expr14_0)?; + let expr16_0 = constructor_with_flags(ctx, &expr12_0, &expr15_0)?; + return Some(expr16_0); + } + &Opcode::Isub => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 256. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = C::put_in_regs(ctx, pattern7_1); + let expr6_0: usize = 0; + let expr7_0 = C::value_regs_get(ctx, expr5_0, expr6_0); + let expr8_0: usize = 1; + let expr9_0 = C::value_regs_get(ctx, expr5_0, expr8_0); + let expr10_0: Type = I64; + let expr11_0 = RegMemImm::Reg { reg: expr7_0 }; + let expr12_0 = + constructor_sub_with_flags(ctx, expr10_0, expr2_0, &expr11_0)?; + let expr13_0: Type = I64; + let expr14_0 = RegMemImm::Reg { reg: expr9_0 }; + let expr15_0 = constructor_sbb(ctx, expr13_0, expr4_0, &expr14_0)?; + let expr16_0 = constructor_with_flags(ctx, &expr12_0, &expr15_0)?; + return Some(expr16_0); + } + &Opcode::Band => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 348. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = C::put_in_regs(ctx, pattern7_1); + let expr6_0: usize = 0; + let expr7_0 = C::value_regs_get(ctx, expr5_0, expr6_0); + let expr8_0: usize = 1; + let expr9_0 = C::value_regs_get(ctx, expr5_0, expr8_0); + let expr10_0: Type = I64; + let expr11_0 = RegMemImm::Reg { reg: expr7_0 }; + let expr12_0 = constructor_m_and(ctx, expr10_0, expr2_0, &expr11_0)?; + let expr13_0: Type = I64; + let expr14_0 = RegMemImm::Reg { reg: expr9_0 }; + let expr15_0 = constructor_m_and(ctx, expr13_0, expr4_0, &expr14_0)?; + let expr16_0 = C::value_regs(ctx, expr12_0, expr15_0); + return Some(expr16_0); + } + &Opcode::Bor => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 433. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0 = C::put_in_regs(ctx, pattern7_1); + let expr2_0 = constructor_or_i128(ctx, expr0_0, expr1_0)?; + return Some(expr2_0); + } + &Opcode::Bxor => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 502. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = C::put_in_regs(ctx, pattern7_1); + let expr6_0: usize = 0; + let expr7_0 = C::value_regs_get(ctx, expr5_0, expr6_0); + let expr8_0: usize = 1; + let expr9_0 = C::value_regs_get(ctx, expr5_0, expr8_0); + let expr10_0: Type = I64; + let expr11_0 = RegMemImm::Reg { reg: expr7_0 }; + let expr12_0 = constructor_xor(ctx, expr10_0, expr2_0, &expr11_0)?; + let expr13_0: Type = I64; + let expr14_0 = RegMemImm::Reg { reg: expr9_0 }; + let expr15_0 = constructor_xor(ctx, expr13_0, expr4_0, &expr14_0)?; + let expr16_0 = C::value_regs(ctx, expr12_0, expr15_0); + return Some(expr16_0); + } + &Opcode::Rotl => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 629. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr2_0 = constructor_shl_i128(ctx, expr0_0, expr1_0)?; + let expr3_0: Type = I64; + let expr4_0: Type = I64; + let expr5_0: u64 = 128; + let expr6_0 = constructor_imm(ctx, expr4_0, expr5_0)?; + let expr7_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr8_0 = constructor_sub(ctx, expr3_0, expr6_0, &expr7_0)?; + let expr9_0 = constructor_shr_i128(ctx, expr0_0, expr8_0)?; + let expr10_0 = constructor_or_i128(ctx, expr2_0, expr9_0)?; + return Some(expr10_0); + } + &Opcode::Ishl => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 562. + let expr0_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr1_0 = C::put_in_regs(ctx, pattern7_0); + let expr2_0 = constructor_shl_i128(ctx, expr1_0, expr0_0)?; + return Some(expr2_0); + } + &Opcode::Ushr => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 608. + let expr0_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr1_0 = C::put_in_regs(ctx, pattern7_0); + let expr2_0 = constructor_shr_i128(ctx, expr1_0, expr0_0)?; + return Some(expr2_0); + } + _ => {} + } + } + &InstructionData::BinaryImm64 { + opcode: ref pattern5_0, + arg: pattern5_1, + imm: pattern5_2, + } => { + if let &Opcode::IaddImm = &pattern5_0 { + let pattern7_0 = C::u64_from_imm64(ctx, pattern5_2); + // Rule at src/isa/x64/lower.isle line 202. + let expr0_0 = C::put_in_regs(ctx, pattern5_1); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0: Type = I64; + let expr6_0 = constructor_imm(ctx, expr5_0, pattern7_0)?; + let expr7_0: Type = I64; + let expr8_0 = RegMemImm::Reg { reg: expr6_0 }; + let expr9_0 = constructor_add_with_flags(ctx, expr7_0, expr2_0, &expr8_0)?; + let expr10_0: Type = I64; + let expr11_0: u32 = 0; + let expr12_0 = RegMemImm::Imm { simm32: expr11_0 }; + let expr13_0 = constructor_adc(ctx, expr10_0, expr4_0, &expr12_0)?; + let expr14_0 = constructor_with_flags(ctx, &expr9_0, &expr13_0)?; + return Some(expr14_0); + } + } + _ => {} + } + } + if pattern2_0 == F32X4 { + let pattern4_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern5_0, + args: ref pattern5_1, + } = &pattern4_0 + { + match &pattern5_0 { + &Opcode::Band => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 333. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_andps(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Bor => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 409. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_orps(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Bxor => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 487. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_xorps(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + _ => {} + } + } + } + if pattern2_0 == F64X2 { + let pattern4_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern5_0, + args: ref pattern5_1, + } = &pattern4_0 + { + match &pattern5_0 { + &Opcode::Band => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 337. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_andpd(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Bor => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 413. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_orpd(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Bxor => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 491. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_xorpd(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + _ => {} + } + } + } + let pattern3_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::NullAry { + opcode: ref pattern4_0, + } = &pattern3_0 + { + if let &Opcode::Null = &pattern4_0 { + // Rule at src/isa/x64/lower.isle line 46. + let expr0_0: u64 = 0; + let expr1_0 = constructor_imm(ctx, pattern2_0, expr0_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + } + if let Some(()) = C::avx512vl_enabled(ctx, pattern2_0) { + if let Some(()) = C::avx512dq_enabled(ctx, pattern2_0) { + if let Some((pattern5_0, pattern5_1)) = C::multi_lane(ctx, pattern2_0) { + if pattern5_0 == 64 { + if pattern5_1 == 2 { + let pattern8_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern9_0, + args: ref pattern9_1, + } = &pattern8_0 + { + if let &Opcode::Imul = &pattern9_0 { + let (pattern11_0, pattern11_1) = + C::unpack_value_array_2(ctx, &pattern9_1); + // Rule at src/isa/x64/lower.isle line 693. + let expr0_0 = C::put_in_reg_mem(ctx, pattern11_0); + let expr1_0 = C::put_in_reg(ctx, pattern11_1); + let expr2_0 = constructor_vpmullq(ctx, &expr0_0, expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + } + } + } + } + } + } + if let Some((pattern3_0, pattern3_1)) = C::multi_lane(ctx, pattern2_0) { + if pattern3_0 == 8 { + if pattern3_1 == 16 { + let pattern6_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern7_0, + args: ref pattern7_1, + } = &pattern6_0 + { + match &pattern7_0 { + &Opcode::AvgRound => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 639. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_pavgb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::UaddSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 134. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddusb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::SaddSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 122. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddsb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::UsubSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 283. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubusb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::SsubSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 271. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubsb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Iadd => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 86. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Isub => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 235. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubb(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + _ => {} + } + } + } + } + if pattern3_0 == 16 { + if pattern3_1 == 8 { + let pattern6_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern7_0, + args: ref pattern7_1, + } = &pattern6_0 + { + match &pattern7_0 { + &Opcode::AvgRound => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 643. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_pavgw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::UaddSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 139. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddusw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::SaddSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 127. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddsw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::UsubSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 288. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubusw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::SsubSat => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 276. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubsw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Iadd => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 91. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Isub => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 240. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Imul => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + if let Some(pattern10_0) = C::def_inst(ctx, pattern9_0) { + let pattern11_0 = C::inst_data(ctx, pattern10_0); + if let &InstructionData::Unary { + opcode: ref pattern12_0, + arg: pattern12_1, + } = &pattern11_0 + { + match &pattern12_0 { + &Opcode::SwidenLow => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 8 { + if pattern15_1 == 16 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::SwidenLow = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 8 { + if pattern23_1 == 16 + { + // Rule at src/isa/x64/lower.isle line 781. + let expr0_0 = C::put_in_reg_mem(ctx, pattern12_1); + let expr1_0 = constructor_pmovsxbw(ctx, &expr0_0)?; + let expr2_0 = C::put_in_reg_mem(ctx, pattern20_1); + let expr3_0 = constructor_pmovsxbw(ctx, &expr2_0)?; + let expr4_0 = RegMem::Reg { + reg: expr3_0, + }; + let expr5_0 = constructor_pmullw(ctx, expr1_0, &expr4_0)?; + let expr6_0 = C::value_reg(ctx, expr5_0); + return Some( + expr6_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::SwidenHigh => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 8 { + if pattern15_1 == 16 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::SwidenHigh = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 8 { + if pattern23_1 == 16 + { + // Rule at src/isa/x64/lower.isle line 741. + let expr0_0 = C::put_in_reg(ctx, pattern12_1); + let expr1_0 = RegMem::Reg { + reg: expr0_0, + }; + let expr2_0: u8 = 8; + let expr3_0 = OperandSize::Size32; + let expr4_0 = constructor_palignr(ctx, expr0_0, &expr1_0, expr2_0, &expr3_0)?; + let expr5_0 = RegMem::Reg { + reg: expr4_0, + }; + let expr6_0 = constructor_pmovsxbw(ctx, &expr5_0)?; + let expr7_0 = C::put_in_reg(ctx, pattern20_1); + let expr8_0 = RegMem::Reg { + reg: expr7_0, + }; + let expr9_0: u8 = 8; + let expr10_0 = OperandSize::Size32; + let expr11_0 = constructor_palignr(ctx, expr7_0, &expr8_0, expr9_0, &expr10_0)?; + let expr12_0 = RegMem::Reg { + reg: expr11_0, + }; + let expr13_0 = constructor_pmovsxbw(ctx, &expr12_0)?; + let expr14_0 = RegMem::Reg { + reg: expr13_0, + }; + let expr15_0 = constructor_pmullw(ctx, expr6_0, &expr14_0)?; + let expr16_0 = C::value_reg(ctx, expr15_0); + return Some( + expr16_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::UwidenLow => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 8 { + if pattern15_1 == 16 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::UwidenLow = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 8 { + if pattern23_1 == 16 + { + // Rule at src/isa/x64/lower.isle line 857. + let expr0_0 = C::put_in_reg_mem(ctx, pattern12_1); + let expr1_0 = constructor_pmovzxbw(ctx, &expr0_0)?; + let expr2_0 = C::put_in_reg_mem(ctx, pattern20_1); + let expr3_0 = constructor_pmovzxbw(ctx, &expr2_0)?; + let expr4_0 = RegMem::Reg { + reg: expr3_0, + }; + let expr5_0 = constructor_pmullw(ctx, expr1_0, &expr4_0)?; + let expr6_0 = C::value_reg(ctx, expr5_0); + return Some( + expr6_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::UwidenHigh => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 8 { + if pattern15_1 == 16 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::UwidenHigh = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 8 { + if pattern23_1 == 16 + { + // Rule at src/isa/x64/lower.isle line 817. + let expr0_0 = C::put_in_reg(ctx, pattern12_1); + let expr1_0 = RegMem::Reg { + reg: expr0_0, + }; + let expr2_0: u8 = 8; + let expr3_0 = OperandSize::Size32; + let expr4_0 = constructor_palignr(ctx, expr0_0, &expr1_0, expr2_0, &expr3_0)?; + let expr5_0 = RegMem::Reg { + reg: expr4_0, + }; + let expr6_0 = constructor_pmovzxbw(ctx, &expr5_0)?; + let expr7_0 = C::put_in_reg(ctx, pattern20_1); + let expr8_0 = RegMem::Reg { + reg: expr7_0, + }; + let expr9_0: u8 = 8; + let expr10_0 = OperandSize::Size32; + let expr11_0 = constructor_palignr(ctx, expr7_0, &expr8_0, expr9_0, &expr10_0)?; + let expr12_0 = RegMem::Reg { + reg: expr11_0, + }; + let expr13_0 = constructor_pmovzxbw(ctx, &expr12_0)?; + let expr14_0 = RegMem::Reg { + reg: expr13_0, + }; + let expr15_0 = constructor_pmullw(ctx, expr6_0, &expr14_0)?; + let expr16_0 = C::value_reg(ctx, expr15_0); + return Some( + expr16_0, + ); + } + } + } + } + } + } + } + } + } + } + _ => {} + } + } + } + // Rule at src/isa/x64/lower.isle line 685. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_pmullw(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + _ => {} + } + } + } + } + if pattern3_0 == 32 { + if pattern3_1 == 4 { + let pattern6_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern7_0, + args: ref pattern7_1, + } = &pattern6_0 + { + match &pattern7_0 { + &Opcode::Iadd => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 96. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddd(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Isub => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 245. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubd(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Imul => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + if let Some(pattern10_0) = C::def_inst(ctx, pattern9_0) { + let pattern11_0 = C::inst_data(ctx, pattern10_0); + if let &InstructionData::Unary { + opcode: ref pattern12_0, + arg: pattern12_1, + } = &pattern11_0 + { + match &pattern12_0 { + &Opcode::SwidenLow => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 16 { + if pattern15_1 == 8 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::SwidenLow = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 16 { + if pattern23_1 == 8 + { + // Rule at src/isa/x64/lower.isle line 791. + let expr0_0 = C::put_in_reg(ctx, pattern12_1); + let expr1_0 = C::put_in_reg(ctx, pattern20_1); + let expr2_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr3_0 = constructor_pmullw(ctx, expr0_0, &expr2_0)?; + let expr4_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr5_0 = constructor_pmulhw(ctx, expr0_0, &expr4_0)?; + let expr6_0 = RegMem::Reg { + reg: expr5_0, + }; + let expr7_0 = constructor_punpcklwd(ctx, expr3_0, &expr6_0)?; + let expr8_0 = C::value_reg(ctx, expr7_0); + return Some( + expr8_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::SwidenHigh => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 16 { + if pattern15_1 == 8 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::SwidenHigh = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 16 { + if pattern23_1 == 8 + { + // Rule at src/isa/x64/lower.isle line 755. + let expr0_0 = C::put_in_reg(ctx, pattern12_1); + let expr1_0 = C::put_in_reg(ctx, pattern20_1); + let expr2_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr3_0 = constructor_pmullw(ctx, expr0_0, &expr2_0)?; + let expr4_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr5_0 = constructor_pmulhw(ctx, expr0_0, &expr4_0)?; + let expr6_0 = RegMem::Reg { + reg: expr5_0, + }; + let expr7_0 = constructor_punpckhwd(ctx, expr3_0, &expr6_0)?; + let expr8_0 = C::value_reg(ctx, expr7_0); + return Some( + expr8_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::UwidenLow => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 16 { + if pattern15_1 == 8 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::UwidenLow = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 16 { + if pattern23_1 == 8 + { + // Rule at src/isa/x64/lower.isle line 867. + let expr0_0 = C::put_in_reg(ctx, pattern12_1); + let expr1_0 = C::put_in_reg(ctx, pattern20_1); + let expr2_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr3_0 = constructor_pmullw(ctx, expr0_0, &expr2_0)?; + let expr4_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr5_0 = constructor_pmulhuw(ctx, expr0_0, &expr4_0)?; + let expr6_0 = RegMem::Reg { + reg: expr5_0, + }; + let expr7_0 = constructor_punpcklwd(ctx, expr3_0, &expr6_0)?; + let expr8_0 = C::value_reg(ctx, expr7_0); + return Some( + expr8_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::UwidenHigh => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 16 { + if pattern15_1 == 8 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::UwidenHigh = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 16 { + if pattern23_1 == 8 + { + // Rule at src/isa/x64/lower.isle line 831. + let expr0_0 = C::put_in_reg(ctx, pattern12_1); + let expr1_0 = C::put_in_reg(ctx, pattern20_1); + let expr2_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr3_0 = constructor_pmullw(ctx, expr0_0, &expr2_0)?; + let expr4_0 = RegMem::Reg { + reg: expr1_0, + }; + let expr5_0 = constructor_pmulhuw(ctx, expr0_0, &expr4_0)?; + let expr6_0 = RegMem::Reg { + reg: expr5_0, + }; + let expr7_0 = constructor_punpckhwd(ctx, expr3_0, &expr6_0)?; + let expr8_0 = C::value_reg(ctx, expr7_0); + return Some( + expr8_0, + ); + } + } + } + } + } + } + } + } + } + } + _ => {} + } + } + } + // Rule at src/isa/x64/lower.isle line 688. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_pmulld(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + _ => {} + } + } + } + } + if pattern3_0 == 64 { + if pattern3_1 == 2 { + let pattern6_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern7_0, + args: ref pattern7_1, + } = &pattern6_0 + { + match &pattern7_0 { + &Opcode::Iadd => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 101. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_paddq(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Isub => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + // Rule at src/isa/x64/lower.isle line 250. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern9_1); + let expr2_0 = constructor_psubq(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Imul => { + let (pattern9_0, pattern9_1) = + C::unpack_value_array_2(ctx, &pattern7_1); + if let Some(pattern10_0) = C::def_inst(ctx, pattern9_0) { + let pattern11_0 = C::inst_data(ctx, pattern10_0); + if let &InstructionData::Unary { + opcode: ref pattern12_0, + arg: pattern12_1, + } = &pattern11_0 + { + match &pattern12_0 { + &Opcode::SwidenLow => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 32 { + if pattern15_1 == 4 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::SwidenLow = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 32 { + if pattern23_1 == 4 + { + // Rule at src/isa/x64/lower.isle line 803. + let expr0_0 = C::put_in_reg_mem(ctx, pattern12_1); + let expr1_0: u8 = 80; + let expr2_0 = OperandSize::Size32; + let expr3_0 = constructor_pshufd(ctx, &expr0_0, expr1_0, &expr2_0)?; + let expr4_0 = C::put_in_reg_mem(ctx, pattern20_1); + let expr5_0: u8 = 80; + let expr6_0 = OperandSize::Size32; + let expr7_0 = constructor_pshufd(ctx, &expr4_0, expr5_0, &expr6_0)?; + let expr8_0 = RegMem::Reg { + reg: expr7_0, + }; + let expr9_0 = constructor_pmuldq(ctx, expr3_0, &expr8_0)?; + let expr10_0 = C::value_reg(ctx, expr9_0); + return Some( + expr10_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::SwidenHigh => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 32 { + if pattern15_1 == 4 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::SwidenHigh = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 32 { + if pattern23_1 == 4 + { + // Rule at src/isa/x64/lower.isle line 767. + let expr0_0 = C::put_in_reg_mem(ctx, pattern12_1); + let expr1_0: u8 = 250; + let expr2_0 = OperandSize::Size32; + let expr3_0 = constructor_pshufd(ctx, &expr0_0, expr1_0, &expr2_0)?; + let expr4_0 = C::put_in_reg_mem(ctx, pattern20_1); + let expr5_0: u8 = 250; + let expr6_0 = OperandSize::Size32; + let expr7_0 = constructor_pshufd(ctx, &expr4_0, expr5_0, &expr6_0)?; + let expr8_0 = RegMem::Reg { + reg: expr7_0, + }; + let expr9_0 = constructor_pmuldq(ctx, expr3_0, &expr8_0)?; + let expr10_0 = C::value_reg(ctx, expr9_0); + return Some( + expr10_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::UwidenLow => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 32 { + if pattern15_1 == 4 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::UwidenLow = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 32 { + if pattern23_1 == 4 + { + // Rule at src/isa/x64/lower.isle line 879. + let expr0_0 = C::put_in_reg_mem(ctx, pattern12_1); + let expr1_0: u8 = 80; + let expr2_0 = OperandSize::Size32; + let expr3_0 = constructor_pshufd(ctx, &expr0_0, expr1_0, &expr2_0)?; + let expr4_0 = C::put_in_reg_mem(ctx, pattern20_1); + let expr5_0: u8 = 80; + let expr6_0 = OperandSize::Size32; + let expr7_0 = constructor_pshufd(ctx, &expr4_0, expr5_0, &expr6_0)?; + let expr8_0 = RegMem::Reg { + reg: expr7_0, + }; + let expr9_0 = constructor_pmuludq(ctx, expr3_0, &expr8_0)?; + let expr10_0 = C::value_reg(ctx, expr9_0); + return Some( + expr10_0, + ); + } + } + } + } + } + } + } + } + } + } + &Opcode::UwidenHigh => { + let pattern14_0 = C::value_type(ctx, pattern12_1); + if let Some((pattern15_0, pattern15_1)) = + C::multi_lane(ctx, pattern14_0) + { + if pattern15_0 == 32 { + if pattern15_1 == 4 { + if let Some(pattern18_0) = + C::def_inst(ctx, pattern9_1) + { + let pattern19_0 = + C::inst_data(ctx, pattern18_0); + if let &InstructionData::Unary { + opcode: ref pattern20_0, + arg: pattern20_1, + } = &pattern19_0 + { + if let &Opcode::UwidenHigh = + &pattern20_0 + { + let pattern22_0 = + C::value_type( + ctx, + pattern20_1, + ); + if let Some(( + pattern23_0, + pattern23_1, + )) = C::multi_lane( + ctx, + pattern22_0, + ) { + if pattern23_0 == 32 { + if pattern23_1 == 4 + { + // Rule at src/isa/x64/lower.isle line 843. + let expr0_0 = C::put_in_reg_mem(ctx, pattern12_1); + let expr1_0: u8 = 250; + let expr2_0 = OperandSize::Size32; + let expr3_0 = constructor_pshufd(ctx, &expr0_0, expr1_0, &expr2_0)?; + let expr4_0 = C::put_in_reg_mem(ctx, pattern20_1); + let expr5_0: u8 = 250; + let expr6_0 = OperandSize::Size32; + let expr7_0 = constructor_pshufd(ctx, &expr4_0, expr5_0, &expr6_0)?; + let expr8_0 = RegMem::Reg { + reg: expr7_0, + }; + let expr9_0 = constructor_pmuludq(ctx, expr3_0, &expr8_0)?; + let expr10_0 = C::value_reg(ctx, expr9_0); + return Some( + expr10_0, + ); + } + } + } + } + } + } + } + } + } + } + _ => {} + } + } + } + // Rule at src/isa/x64/lower.isle line 719. + let expr0_0 = C::put_in_reg(ctx, pattern9_0); + let expr1_0 = C::put_in_reg(ctx, pattern9_1); + let expr2_0: u32 = 32; + let expr3_0 = RegMemImm::Imm { simm32: expr2_0 }; + let expr4_0 = constructor_psrlq(ctx, expr0_0, &expr3_0)?; + let expr5_0 = RegMem::Reg { reg: expr1_0 }; + let expr6_0 = constructor_pmuludq(ctx, expr4_0, &expr5_0)?; + let expr7_0: u32 = 32; + let expr8_0 = RegMemImm::Imm { simm32: expr7_0 }; + let expr9_0 = constructor_psrlq(ctx, expr1_0, &expr8_0)?; + let expr10_0 = RegMem::Reg { reg: expr9_0 }; + let expr11_0 = constructor_pmuludq(ctx, expr0_0, &expr10_0)?; + let expr12_0 = RegMem::Reg { reg: expr11_0 }; + let expr13_0 = constructor_paddq(ctx, expr6_0, &expr12_0)?; + let expr14_0: u32 = 32; + let expr15_0 = RegMemImm::Imm { simm32: expr14_0 }; + let expr16_0 = constructor_psllq(ctx, expr13_0, &expr15_0)?; + let expr17_0 = RegMem::Reg { reg: expr1_0 }; + let expr18_0 = constructor_pmuludq(ctx, expr0_0, &expr17_0)?; + let expr19_0 = RegMem::Reg { reg: expr16_0 }; + let expr20_0 = constructor_paddq(ctx, expr18_0, &expr19_0)?; + let expr21_0 = C::value_reg(ctx, expr20_0); + return Some(expr21_0); + } + _ => {} + } + } + } + } + let pattern4_0 = C::inst_data(ctx, pattern0_0); + if let &InstructionData::Binary { + opcode: ref pattern5_0, + args: ref pattern5_1, + } = &pattern4_0 + { + match &pattern5_0 { + &Opcode::Band => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 341. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_pand(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Bor => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 417. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_por(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + &Opcode::Bxor => { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 495. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_1); + let expr2_0 = constructor_pxor(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + _ => {} + } + } + } + if let Some(pattern3_0) = C::fits_in_64(ctx, pattern2_0) { + let pattern4_0 = C::inst_data(ctx, pattern0_0); + match &pattern4_0 { + &InstructionData::UnaryImm { + opcode: ref pattern5_0, + imm: pattern5_1, + } => { + if let &Opcode::Iconst = &pattern5_0 { + let pattern7_0 = C::u64_from_imm64(ctx, pattern5_1); + // Rule at src/isa/x64/lower.isle line 10. + let expr0_0 = constructor_imm(ctx, pattern3_0, pattern7_0)?; + let expr1_0 = C::value_reg(ctx, expr0_0); + return Some(expr1_0); + } + } + &InstructionData::UnaryBool { + opcode: ref pattern5_0, + imm: pattern5_1, + } => { + if let &Opcode::Bconst = &pattern5_0 { + if pattern5_1 == true { + // Rule at src/isa/x64/lower.isle line 28. + let expr0_0: u64 = 1; + let expr1_0 = constructor_imm(ctx, pattern3_0, expr0_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if pattern5_1 == false { + // Rule at src/isa/x64/lower.isle line 24. + let expr0_0: u64 = 0; + let expr1_0 = constructor_imm(ctx, pattern3_0, expr0_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + } + } + &InstructionData::Binary { + opcode: ref pattern5_0, + args: ref pattern5_1, + } => { + match &pattern5_0 { + &Opcode::Iadd => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 66. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = + constructor_add(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 78. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 62. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_add(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 72. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 54. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Isub => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 222. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_sub(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 227. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_sub(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 215. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_sub(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Imul => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 663. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = + constructor_mul(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 675. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_mul(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 659. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_mul(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 669. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_mul(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 652. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_mul(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::IaddIfcout => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 159. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = + constructor_add(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 171. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 155. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_add(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 165. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 147. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Band => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 325. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = + constructor_m_and(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 311. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = + constructor_m_and(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 319. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_m_and(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 305. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = + constructor_m_and(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 298. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_m_and(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Bor => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 401. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = + constructor_or(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 387. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_or(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 395. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_or(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 381. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_or(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 374. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_or(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Bxor => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 479. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = + constructor_xor(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_0) { + // Rule at src/isa/x64/lower.isle line 465. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_xor(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + if let Some(pattern8_0) = C::simm32_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 473. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_xor(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + if let Some(pattern8_0) = C::sinkable_load(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 459. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::sink_load(ctx, &pattern8_0); + let expr2_0 = constructor_xor(ctx, pattern3_0, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 452. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = C::put_in_reg(ctx, pattern7_1); + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_xor(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Rotl => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::imm8_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 624. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_m_rotl(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + // Rule at src/isa/x64/lower.isle line 618. + let expr0_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr1_0 = C::put_in_reg(ctx, pattern7_0); + let expr2_0 = Imm8Reg::Reg { reg: expr0_0 }; + let expr3_0 = constructor_m_rotl(ctx, pattern3_0, expr1_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Ishl => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::imm8_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 533. + let expr0_0 = C::put_in_reg(ctx, pattern7_0); + let expr1_0 = + constructor_shl(ctx, pattern3_0, expr0_0, &pattern8_0)?; + let expr2_0 = C::value_reg(ctx, expr1_0); + return Some(expr2_0); + } + // Rule at src/isa/x64/lower.isle line 527. + let expr0_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr1_0 = C::put_in_reg(ctx, pattern7_0); + let expr2_0 = Imm8Reg::Reg { reg: expr0_0 }; + let expr3_0 = constructor_shl(ctx, pattern3_0, expr1_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + &Opcode::Ushr => { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + if let Some(pattern8_0) = C::imm8_from_value(ctx, pattern7_1) { + // Rule at src/isa/x64/lower.isle line 579. + let expr0_0 = ExtendKind::Zero; + let expr1_0 = constructor_extend_to_reg( + ctx, pattern7_0, pattern3_0, &expr0_0, + )?; + let expr2_0 = + constructor_shr(ctx, pattern3_0, expr1_0, &pattern8_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } + // Rule at src/isa/x64/lower.isle line 572. + let expr0_0 = ExtendKind::Zero; + let expr1_0 = + constructor_extend_to_reg(ctx, pattern7_0, pattern3_0, &expr0_0)?; + let expr2_0 = constructor_lo_reg(ctx, pattern7_1)?; + let expr3_0 = Imm8Reg::Reg { reg: expr2_0 }; + let expr4_0 = constructor_shr(ctx, pattern3_0, expr1_0, &expr3_0)?; + let expr5_0 = C::value_reg(ctx, expr4_0); + return Some(expr5_0); + } + _ => {} + } + } + &InstructionData::BinaryImm64 { + opcode: ref pattern5_0, + arg: pattern5_1, + imm: pattern5_2, + } => { + if let &Opcode::IaddImm = &pattern5_0 { + let pattern7_0 = C::u64_from_imm64(ctx, pattern5_2); + // Rule at src/isa/x64/lower.isle line 188. + let expr0_0 = C::put_in_reg(ctx, pattern5_1); + let expr1_0 = constructor_imm(ctx, pattern3_0, pattern7_0)?; + let expr2_0 = RegMemImm::Reg { reg: expr1_0 }; + let expr3_0 = constructor_add(ctx, pattern3_0, expr0_0, &expr2_0)?; + let expr4_0 = C::value_reg(ctx, expr3_0); + return Some(expr4_0); + } + } + _ => {} + } + } + } + return None; +} + +// Generated as internal constructor for term or_i128. +pub fn constructor_or_i128( + ctx: &mut C, + arg0: ValueRegs, + arg1: ValueRegs, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/lower.isle line 425. + let expr0_0: usize = 0; + let expr1_0 = C::value_regs_get(ctx, pattern0_0, expr0_0); + let expr2_0: usize = 1; + let expr3_0 = C::value_regs_get(ctx, pattern0_0, expr2_0); + let expr4_0: usize = 0; + let expr5_0 = C::value_regs_get(ctx, pattern1_0, expr4_0); + let expr6_0: usize = 1; + let expr7_0 = C::value_regs_get(ctx, pattern1_0, expr6_0); + let expr8_0: Type = I64; + let expr9_0 = RegMemImm::Reg { reg: expr5_0 }; + let expr10_0 = constructor_or(ctx, expr8_0, expr1_0, &expr9_0)?; + let expr11_0: Type = I64; + let expr12_0 = RegMemImm::Reg { reg: expr7_0 }; + let expr13_0 = constructor_or(ctx, expr11_0, expr3_0, &expr12_0)?; + let expr14_0 = C::value_regs(ctx, expr10_0, expr13_0); + return Some(expr14_0); +} + +// Generated as internal constructor for term shl_i128. +pub fn constructor_shl_i128( + ctx: &mut C, + arg0: ValueRegs, + arg1: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/lower.isle line 539. + let expr0_0: usize = 0; + let expr1_0 = C::value_regs_get(ctx, pattern0_0, expr0_0); + let expr2_0: usize = 1; + let expr3_0 = C::value_regs_get(ctx, pattern0_0, expr2_0); + let expr4_0: Type = I64; + let expr5_0 = Imm8Reg::Reg { reg: pattern1_0 }; + let expr6_0 = constructor_shl(ctx, expr4_0, expr1_0, &expr5_0)?; + let expr7_0: Type = I64; + let expr8_0 = Imm8Reg::Reg { reg: pattern1_0 }; + let expr9_0 = constructor_shl(ctx, expr7_0, expr3_0, &expr8_0)?; + let expr10_0: Type = I64; + let expr11_0: Type = I64; + let expr12_0: Type = I64; + let expr13_0: u64 = 64; + let expr14_0 = constructor_imm(ctx, expr12_0, expr13_0)?; + let expr15_0 = RegMemImm::Reg { reg: pattern1_0 }; + let expr16_0 = constructor_sub(ctx, expr11_0, expr14_0, &expr15_0)?; + let expr17_0 = Imm8Reg::Reg { reg: expr16_0 }; + let expr18_0 = constructor_shr(ctx, expr10_0, expr1_0, &expr17_0)?; + let expr19_0: Type = I64; + let expr20_0: u64 = 0; + let expr21_0 = constructor_imm(ctx, expr19_0, expr20_0)?; + let expr22_0 = OperandSize::Size64; + let expr23_0: u32 = 127; + let expr24_0 = RegMemImm::Imm { simm32: expr23_0 }; + let expr25_0 = constructor_test(ctx, &expr22_0, &expr24_0, pattern1_0)?; + let expr26_0: Type = I64; + let expr27_0 = CC::Z; + let expr28_0 = RegMem::Reg { reg: expr21_0 }; + let expr29_0 = constructor_cmove(ctx, expr26_0, &expr27_0, &expr28_0, expr18_0)?; + let expr30_0 = constructor_with_flags_1(ctx, &expr25_0, &expr29_0)?; + let expr31_0: Type = I64; + let expr32_0 = RegMemImm::Reg { reg: expr9_0 }; + let expr33_0 = constructor_or(ctx, expr31_0, expr30_0, &expr32_0)?; + let expr34_0 = OperandSize::Size64; + let expr35_0: u32 = 64; + let expr36_0 = RegMemImm::Imm { simm32: expr35_0 }; + let expr37_0 = constructor_test(ctx, &expr34_0, &expr36_0, pattern1_0)?; + let expr38_0: Type = I64; + let expr39_0 = CC::Z; + let expr40_0 = RegMem::Reg { reg: expr6_0 }; + let expr41_0 = constructor_cmove(ctx, expr38_0, &expr39_0, &expr40_0, expr21_0)?; + let expr42_0: Type = I64; + let expr43_0 = CC::Z; + let expr44_0 = RegMem::Reg { reg: expr33_0 }; + let expr45_0 = constructor_cmove(ctx, expr42_0, &expr43_0, &expr44_0, expr6_0)?; + let expr46_0 = constructor_with_flags_2(ctx, &expr37_0, &expr41_0, &expr45_0)?; + return Some(expr46_0); +} + +// Generated as internal constructor for term shr_i128. +pub fn constructor_shr_i128( + ctx: &mut C, + arg0: ValueRegs, + arg1: Reg, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/lower.isle line 586. + let expr0_0: usize = 0; + let expr1_0 = C::value_regs_get(ctx, pattern0_0, expr0_0); + let expr2_0: usize = 1; + let expr3_0 = C::value_regs_get(ctx, pattern0_0, expr2_0); + let expr4_0: Type = I64; + let expr5_0 = Imm8Reg::Reg { reg: pattern1_0 }; + let expr6_0 = constructor_shr(ctx, expr4_0, expr1_0, &expr5_0)?; + let expr7_0: Type = I64; + let expr8_0 = Imm8Reg::Reg { reg: pattern1_0 }; + let expr9_0 = constructor_shr(ctx, expr7_0, expr3_0, &expr8_0)?; + let expr10_0: Type = I64; + let expr11_0: Type = I64; + let expr12_0: Type = I64; + let expr13_0: u64 = 64; + let expr14_0 = constructor_imm(ctx, expr12_0, expr13_0)?; + let expr15_0 = RegMemImm::Reg { reg: pattern1_0 }; + let expr16_0 = constructor_sub(ctx, expr11_0, expr14_0, &expr15_0)?; + let expr17_0 = Imm8Reg::Reg { reg: expr16_0 }; + let expr18_0 = constructor_shl(ctx, expr10_0, expr3_0, &expr17_0)?; + let expr19_0 = OperandSize::Size64; + let expr20_0: u32 = 127; + let expr21_0 = RegMemImm::Imm { simm32: expr20_0 }; + let expr22_0 = constructor_test(ctx, &expr19_0, &expr21_0, pattern1_0)?; + let expr23_0: Type = I64; + let expr24_0 = CC::Z; + let expr25_0: Type = I64; + let expr26_0: u64 = 0; + let expr27_0 = constructor_imm(ctx, expr25_0, expr26_0)?; + let expr28_0 = RegMem::Reg { reg: expr27_0 }; + let expr29_0 = constructor_cmove(ctx, expr23_0, &expr24_0, &expr28_0, expr18_0)?; + let expr30_0 = constructor_with_flags_1(ctx, &expr22_0, &expr29_0)?; + let expr31_0: Type = I64; + let expr32_0 = RegMemImm::Reg { reg: expr6_0 }; + let expr33_0 = constructor_or(ctx, expr31_0, expr30_0, &expr32_0)?; + let expr34_0 = OperandSize::Size64; + let expr35_0: u32 = 64; + let expr36_0 = RegMemImm::Imm { simm32: expr35_0 }; + let expr37_0 = constructor_test(ctx, &expr34_0, &expr36_0, pattern1_0)?; + let expr38_0: Type = I64; + let expr39_0 = CC::Z; + let expr40_0 = RegMem::Reg { reg: expr33_0 }; + let expr41_0 = constructor_cmove(ctx, expr38_0, &expr39_0, &expr40_0, expr9_0)?; + let expr42_0: Type = I64; + let expr43_0 = CC::Z; + let expr44_0 = RegMem::Reg { reg: expr9_0 }; + let expr45_0: Type = I64; + let expr46_0: u64 = 0; + let expr47_0 = constructor_imm(ctx, expr45_0, expr46_0)?; + let expr48_0 = constructor_cmove(ctx, expr42_0, &expr43_0, &expr44_0, expr47_0)?; + let expr49_0 = constructor_with_flags_2(ctx, &expr37_0, &expr41_0, &expr48_0)?; + return Some(expr49_0); +} diff --git a/cranelift/codegen/src/isle.rs b/cranelift/codegen/src/isle.rs new file mode 100644 index 0000000000..f8794716b9 --- /dev/null +++ b/cranelift/codegen/src/isle.rs @@ -0,0 +1,22 @@ +// GENERATED BY ISLE. DO NOT EDIT! +// +// Generated automatically from the instruction-selection DSL code in: +// - src/clif.isle + +#![allow(dead_code, unreachable_code, unreachable_patterns)] +#![allow(unused_imports, unused_variables, non_snake_case)] + +use super::*; // Pulls in all external types. + +/// Context during lowering: an implementation of this trait +/// must be provided with all external constructors and extractors. +/// A mutable borrow is passed along through all lowering logic. +pub trait Context { + fn value_list_slice(&mut self, arg0: &ValueList) -> (ValueSlice,); + fn unwrap_head_value_list_1(&mut self, arg0: &ValueList) -> (Value, ValueSlice,); + fn unwrap_head_value_list_2(&mut self, arg0: &ValueList) -> (Value, Value, ValueSlice,); + fn pack_value_array_2(&mut self, arg0: Value, arg1: Value) -> (ValueArray2,); + fn unpack_value_array_2(&mut self, arg0: &ValueArray2) -> (Value, Value,); + fn pack_value_array_3(&mut self, arg0: Value, arg1: Value, arg2: Value) -> (ValueArray3,); + fn unpack_value_array_3(&mut self, arg0: &ValueArray3) -> (Value, Value, Value,); +} diff --git a/cranelift/codegen/src/machinst/lower.rs b/cranelift/codegen/src/machinst/lower.rs index 0a35ad3ae6..e6e608ec3d 100644 --- a/cranelift/codegen/src/machinst/lower.rs +++ b/cranelift/codegen/src/machinst/lower.rs @@ -11,9 +11,9 @@ use crate::fx::{FxHashMap, FxHashSet}; use crate::inst_predicates::{has_lowering_side_effect, is_constant_64bit}; use crate::ir::instructions::BranchInfo; use crate::ir::{ - ArgumentPurpose, Block, Constant, ConstantData, ExternalName, Function, GlobalValueData, Inst, - InstructionData, MemFlags, Opcode, Signature, SourceLoc, Type, Value, ValueDef, - ValueLabelAssignments, ValueLabelStart, + ArgumentPurpose, Block, Constant, ConstantData, DataFlowGraph, ExternalName, Function, + GlobalValueData, Inst, InstructionData, MemFlags, Opcode, Signature, SourceLoc, Type, Value, + ValueDef, ValueLabelAssignments, ValueLabelStart, }; use crate::machinst::{ writable_value_regs, ABICallee, BlockIndex, BlockLoweringOrder, LoweredBlock, MachLabel, VCode, @@ -61,6 +61,8 @@ pub trait LowerCtx { /// The instruction type for which this lowering framework is instantiated. type I: VCodeInst; + fn dfg(&self) -> &DataFlowGraph; + // Function-level queries: /// Get the `ABICallee`. @@ -124,8 +126,12 @@ pub trait LowerCtx { /// instruction's result(s) must have *no* uses remaining, because it will /// not be codegen'd (it has been integrated into the current instruction). fn get_input_as_source_or_const(&self, ir_inst: Inst, idx: usize) -> NonRegInput; + /// Like `get_input_as_source_or_const` but with a `Value`. + fn get_value_as_source_or_const(&self, value: Value) -> NonRegInput; /// Put the `idx`th input into register(s) and return the assigned register. fn put_input_in_regs(&mut self, ir_inst: Inst, idx: usize) -> ValueRegs; + /// Put the given value into register(s) and return the assigned register. + fn put_value_in_regs(&mut self, value: Value) -> ValueRegs; /// Get the `idx`th output register(s) of the given IR instruction. When /// `backend.lower_inst_to_regs(ctx, inst)` is called, it is expected that /// the backend will write results to these output register(s). This @@ -1002,101 +1008,15 @@ impl<'func, I: VCodeInst> Lower<'func, I> { Ok((vcode, stack_map_info)) } - - fn put_value_in_regs(&mut self, val: Value) -> ValueRegs { - log::trace!("put_value_in_reg: val {}", val); - let mut regs = self.value_regs[val]; - log::trace!(" -> regs {:?}", regs); - assert!(regs.is_valid()); - - self.value_lowered_uses[val] += 1; - - // Pinned-reg hack: if backend specifies a fixed pinned register, use it - // directly when we encounter a GetPinnedReg op, rather than lowering - // the actual op, and do not return the source inst to the caller; the - // value comes "out of the ether" and we will not force generation of - // the superfluous move. - if let ValueDef::Result(i, 0) = self.f.dfg.value_def(val) { - if self.f.dfg[i].opcode() == Opcode::GetPinnedReg { - if let Some(pr) = self.pinned_reg { - regs = ValueRegs::one(pr); - } - } - } - - regs - } - - /// Get the actual inputs for a value. This is the implementation for - /// `get_input()` but starting from the SSA value, which is not exposed to - /// the backend. - fn get_value_as_source_or_const(&self, val: Value) -> NonRegInput { - log::trace!( - "get_input_for_val: val {} at cur_inst {:?} cur_scan_entry_color {:?}", - val, - self.cur_inst, - self.cur_scan_entry_color, - ); - let inst = match self.f.dfg.value_def(val) { - // OK to merge source instruction if (i) we have a source - // instruction, and: - // - It has no side-effects, OR - // - It has a side-effect, has one output value, that one output has - // only one use (this one), and the instruction's color is *one less - // than* the current scan color. - // - // This latter set of conditions is testing whether a - // side-effecting instruction can sink to the current scan - // location; this is possible if the in-color of this inst is - // equal to the out-color of the producing inst, so no other - // side-effecting ops occur between them (which will only be true - // if they are in the same BB, because color increments at each BB - // start). - // - // If it is actually sunk, then in `merge_inst()`, we update the - // scan color so that as we scan over the range past which the - // instruction was sunk, we allow other instructions (that came - // prior to the sunk instruction) to sink. - ValueDef::Result(src_inst, result_idx) => { - let src_side_effect = has_lowering_side_effect(self.f, src_inst); - log::trace!(" -> src inst {}", src_inst); - log::trace!(" -> has lowering side effect: {}", src_side_effect); - if !src_side_effect { - // Pure instruction: always possible to sink. - Some((src_inst, result_idx)) - } else { - // Side-effect: test whether this is the only use of the - // only result of the instruction, and whether colors allow - // the code-motion. - if self.cur_scan_entry_color.is_some() - && self.value_uses[val] == 1 - && self.value_lowered_uses[val] == 0 - && self.num_outputs(src_inst) == 1 - && self - .side_effect_inst_entry_colors - .get(&src_inst) - .unwrap() - .get() - + 1 - == self.cur_scan_entry_color.unwrap().get() - { - Some((src_inst, 0)) - } else { - None - } - } - } - _ => None, - }; - let constant = inst.and_then(|(inst, _)| self.get_constant(inst)); - - NonRegInput { inst, constant } - } } impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> { type I = I; + fn dfg(&self) -> &DataFlowGraph { + &self.f.dfg + } + fn abi(&mut self) -> &mut dyn ABICallee { self.vcode.abi() } @@ -1207,12 +1127,99 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> { self.get_value_as_source_or_const(val) } + fn get_value_as_source_or_const(&self, val: Value) -> NonRegInput { + log::trace!( + "get_input_for_val: val {} at cur_inst {:?} cur_scan_entry_color {:?}", + val, + self.cur_inst, + self.cur_scan_entry_color, + ); + let inst = match self.f.dfg.value_def(val) { + // OK to merge source instruction if (i) we have a source + // instruction, and: + // - It has no side-effects, OR + // - It has a side-effect, has one output value, that one output has + // only one use (this one), and the instruction's color is *one less + // than* the current scan color. + // + // This latter set of conditions is testing whether a + // side-effecting instruction can sink to the current scan + // location; this is possible if the in-color of this inst is + // equal to the out-color of the producing inst, so no other + // side-effecting ops occur between them (which will only be true + // if they are in the same BB, because color increments at each BB + // start). + // + // If it is actually sunk, then in `merge_inst()`, we update the + // scan color so that as we scan over the range past which the + // instruction was sunk, we allow other instructions (that came + // prior to the sunk instruction) to sink. + ValueDef::Result(src_inst, result_idx) => { + let src_side_effect = has_lowering_side_effect(self.f, src_inst); + log::trace!(" -> src inst {}", src_inst); + log::trace!(" -> has lowering side effect: {}", src_side_effect); + if !src_side_effect { + // Pure instruction: always possible to sink. + Some((src_inst, result_idx)) + } else { + // Side-effect: test whether this is the only use of the + // only result of the instruction, and whether colors allow + // the code-motion. + if self.cur_scan_entry_color.is_some() + && self.value_uses[val] == 1 + && self.value_lowered_uses[val] == 0 + && self.num_outputs(src_inst) == 1 + && self + .side_effect_inst_entry_colors + .get(&src_inst) + .unwrap() + .get() + + 1 + == self.cur_scan_entry_color.unwrap().get() + { + Some((src_inst, 0)) + } else { + None + } + } + } + _ => None, + }; + let constant = inst.and_then(|(inst, _)| self.get_constant(inst)); + + NonRegInput { inst, constant } + } + fn put_input_in_regs(&mut self, ir_inst: Inst, idx: usize) -> ValueRegs { let val = self.f.dfg.inst_args(ir_inst)[idx]; - let val = self.f.dfg.resolve_aliases(val); self.put_value_in_regs(val) } + fn put_value_in_regs(&mut self, val: Value) -> ValueRegs { + let val = self.f.dfg.resolve_aliases(val); + log::trace!("put_value_in_reg: val {}", val); + let mut regs = self.value_regs[val]; + log::trace!(" -> regs {:?}", regs); + assert!(regs.is_valid()); + + self.value_lowered_uses[val] += 1; + + // Pinned-reg hack: if backend specifies a fixed pinned register, use it + // directly when we encounter a GetPinnedReg op, rather than lowering + // the actual op, and do not return the source inst to the caller; the + // value comes "out of the ether" and we will not force generation of + // the superfluous move. + if let ValueDef::Result(i, 0) = self.f.dfg.value_def(val) { + if self.f.dfg[i].opcode() == Opcode::GetPinnedReg { + if let Some(pr) = self.pinned_reg { + regs = ValueRegs::one(pr); + } + } + } + + regs + } + fn get_output(&self, ir_inst: Inst, idx: usize) -> ValueRegs> { let val = self.f.dfg.inst_results(ir_inst)[idx]; writable_value_regs(self.value_regs[val]) diff --git a/cranelift/codegen/src/prelude.isle b/cranelift/codegen/src/prelude.isle new file mode 100644 index 0000000000..07cc34e87e --- /dev/null +++ b/cranelift/codegen/src/prelude.isle @@ -0,0 +1,202 @@ +;; This is a prelude of standard definitions for ISLE, the instruction-selector +;; DSL, as we use it bound to our interfaces. + +;;;; Primitive and External Types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `()` +(type Unit (primitive Unit)) + +;; `bool` is declared in `clif.isle`. +(extern const $true bool) +(extern const $false bool) + +(type u8 (primitive u8)) +(type u16 (primitive u16)) +(type u32 (primitive u32)) +(type u64 (primitive u64)) +(type u128 (primitive u128)) +(type usize (primitive usize)) + +(type i8 (primitive i8)) +(type i16 (primitive i16)) +(type i32 (primitive i32)) +(type i64 (primitive i64)) +(type i128 (primitive i128)) +(type isize (primitive isize)) + +;; `cranelift-entity`-based identifiers. +(type Inst (primitive Inst)) +(type Type (primitive Type)) +(type Value (primitive Value)) + +;; ISLE representation of `&[Value]`. +(type ValueSlice (primitive ValueSlice)) + +(type ValueList (primitive ValueList)) +(type ValueRegs (primitive ValueRegs)) + +;;;; Registers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(type Reg (primitive Reg)) +(type WritableReg (primitive WritableReg)) + +;; Construct a `ValueRegs` of one register. +(decl value_reg (Reg) ValueRegs) +(extern constructor value_reg value_reg) + +;; Construct a `ValueRegs` of two registers. +(decl value_regs (Reg Reg) ValueRegs) +(extern constructor value_regs value_regs) + +;; Get a temporary register for writing. +(decl temp_writable_reg (Type) WritableReg) +(extern constructor temp_writable_reg temp_writable_reg) + +;; Get a temporary register for reading. +(decl temp_reg (Type) Reg) +(rule (temp_reg ty) + (writable_reg_to_reg (temp_writable_reg ty))) + +;; Get the invalid register. +(decl invalid_reg () Reg) +(extern constructor invalid_reg invalid_reg) + +;; Put the given value into a register. +;; +;; Asserts that the value fits into a single register, and doesn't require +;; multiple registers for its representation (like `i128` on x64 for example). +;; +;; As a side effect, this marks the value as used. +(decl put_in_reg (Value) Reg) +(extern constructor put_in_reg put_in_reg) + +;; Put the given value into one or more registers. +;; +;; As a side effect, this marks the value as used. +(decl put_in_regs (Value) ValueRegs) +(extern constructor put_in_regs put_in_regs) + +;; Get the `n`th register inside a `ValueRegs`. +(decl value_regs_get (ValueRegs usize) Reg) +(extern constructor value_regs_get value_regs_get) + +;; Put the value into one or more registers and return the first register. +;; +;; Unlike `put_in_reg`, this does not assert that the value fits in a single +;; register. This is useful for things like a `i128` shift amount, where we mask +;; the shift amount to the bit width of the value being shifted, and so the high +;; half of the `i128` won't ever be used. +;; +;; As a side efect, this marks that value as used. +(decl lo_reg (Value) Reg) +(rule (lo_reg val) + (let ((regs ValueRegs (put_in_regs val))) + (value_regs_get regs 0))) + +;;;; Primitive Type Conversions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(decl u8_as_u64 (u8) u64) +(extern constructor u8_as_u64 u8_as_u64) + +(decl u16_as_u64 (u16) u64) +(extern constructor u16_as_u64 u16_as_u64) + +(decl u32_as_u64 (u32) u64) +(extern constructor u32_as_u64 u32_as_u64) + +;;;; `cranelift_codegen::ir::Type` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(extern const $B1 Type) +(extern const $B8 Type) +(extern const $B16 Type) +(extern const $B32 Type) +(extern const $B64 Type) +(extern const $B128 Type) + +(extern const $I8 Type) +(extern const $I16 Type) +(extern const $I32 Type) +(extern const $I64 Type) +(extern const $I128 Type) + +(extern const $B8X16 Type) +(extern const $B16X8 Type) +(extern const $B32X4 Type) +(extern const $B64X2 Type) + +(extern const $I8X16 Type) +(extern const $I16X8 Type) +(extern const $I32X4 Type) +(extern const $I64X2 Type) + +(extern const $F32X4 Type) +(extern const $F64X2 Type) + +;; Get the bit width of a given type. +(decl ty_bits (Type) u16) +(extern constructor ty_bits ty_bits) + +;;;; Helper Clif Extractors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; An extractor that only matches types that can fit in 64 bits. +(decl fits_in_64 (Type) Type) +(extern extractor fits_in_64 fits_in_64) + +;; Extractor to get a `ValueSlice` out of a `ValueList`. +(decl value_list_slice (ValueSlice) ValueList) +(extern extractor infallible value_list_slice value_list_slice) + +;; Extractor to get the first element from a value list, along with its tail as +;; a `ValueSlice`. +(decl unwrap_head_value_list_1 (Value ValueSlice) ValueList) +(extern extractor infallible unwrap_head_value_list_1 unwrap_head_value_list_1) + +;; Extractor to get the first two elements from a value list, along with its +;; tail as a `ValueSlice`. +(decl unwrap_head_value_list_2 (Value Value ValueSlice) ValueList) +(extern extractor infallible unwrap_head_value_list_2 unwrap_head_value_list_2) + +;; Turn a `Writable` into a `Reg` via `Writable::to_reg`. +(decl writable_reg_to_reg (WritableReg) Reg) +(extern constructor writable_reg_to_reg writable_reg_to_reg) + +;; Extract a `u64` from an `Imm64`. +(decl u64_from_imm64 (u64) Imm64) +(extern extractor infallible u64_from_imm64 u64_from_imm64) + +;; Extract the result values for the given instruction. +(decl inst_results (ValueSlice) Inst) +(extern extractor infallible inst_results inst_results) + +;; Extract the first result value of the given instruction. +(decl first_result (Value) Inst) +(extern extractor first_result first_result) + +;; Extract the `InstructionData` for an `Inst`. +(decl inst_data (InstructionData) Inst) +(extern extractor infallible inst_data inst_data) + +;; Extract the type of a `Value`. +(decl value_type (Type) Value) +(extern extractor infallible value_type value_type) + +;; Extract the type of the instruction's first result. +(decl result_type (Type) Inst) +(extractor (result_type ty) + (first_result (value_type ty))) + +;; Extract the type of the instruction's first result and pass along the +;; instruction as well. +(decl has_type (Type Inst) Inst) +(extractor (has_type ty inst) + (and (result_type ty) + inst)) + +;; Match a multi-lane type, extracting (# bits per lane, # lanes) from the given +;; type. Will only match when there is more than one lane. +(decl multi_lane (u8 u16) Type) +(extern extractor multi_lane multi_lane) + +;; Match the instruction that defines the given value, if any. +(decl def_inst (Inst) Value) +(extern extractor def_inst def_inst) diff --git a/cranelift/entity/src/list.rs b/cranelift/entity/src/list.rs index d37ea8ae09..d4a057bf4e 100644 --- a/cranelift/entity/src/list.rs +++ b/cranelift/entity/src/list.rs @@ -62,7 +62,7 @@ use serde::{Deserialize, Serialize}; /// /// The index stored in an `EntityList` points to part 2, the list elements. The value 0 is /// reserved for the empty list which isn't allocated in the vector. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct EntityList { index: u32, @@ -271,7 +271,7 @@ impl EntityList { } /// Get the list as a slice. - pub fn as_slice<'a>(&'a self, pool: &'a ListPool) -> &'a [T] { + pub fn as_slice<'a>(&self, pool: &'a ListPool) -> &'a [T] { let idx = self.index as usize; match pool.len_of(self) { None => &[], diff --git a/cranelift/filetests/filetests/isa/x64/i128.clif b/cranelift/filetests/filetests/isa/x64/i128.clif index 96520af906..4e6ce01a0d 100644 --- a/cranelift/filetests/filetests/isa/x64/i128.clif +++ b/cranelift/filetests/filetests/isa/x64/i128.clif @@ -700,31 +700,30 @@ block2(v6: i128): v8 = iadd.i128 v6, v7 return v8 -; check: pushq %rbp +; check: Block 0: +; check: pushq %rbp ; nextln: movq %rsp, %rbp +; nextln: xorq %rdi, %rdi +; nextln: xorq %rsi, %rsi ; nextln: testb $$1, %dl ; nextln: jnz label1; j label2 ; check: Block 1: -; check: movl $$0, %esi -; nextln: movl $$0, %edi -; nextln: movl $$1, %eax -; nextln: movl $$0, %ecx -; nextln: addq %rax, %rsi -; nextln: adcq %rcx, %rdi -; nextln: movq %rsi, %rax -; nextln: movq %rdi, %rdx +; check: movl $$1, %ecx +; nextln: xorq %rax, %rax +; nextln: addq %rcx, %rdi +; nextln: adcq %rax, %rsi +; nextln: movq %rdi, %rax +; nextln: movq %rsi, %rdx ; nextln: movq %rbp, %rsp ; nextln: popq %rbp ; nextln: ret ; check: Block 2: -; check: movl $$0, %esi -; nextln: movl $$0, %edi -; nextln: movl $$2, %eax -; nextln: movl $$0, %ecx -; nextln: addq %rax, %rsi -; nextln: adcq %rcx, %rdi -; nextln: movq %rsi, %rax -; nextln: movq %rdi, %rdx +; check: movl $$2, %ecx +; nextln: xorq %rax, %rax +; nextln: addq %rcx, %rdi +; nextln: adcq %rax, %rsi +; nextln: movq %rdi, %rax +; nextln: movq %rsi, %rdx ; nextln: movq %rbp, %rsp ; nextln: popq %rbp ; nextln: ret @@ -744,34 +743,32 @@ block0(v0: i128, v1: i128, v2: i64, v3: i128, v4: i128, v5: i128): ; check: pushq %rbp ; nextln: movq %rsp, %rbp -; nextln: subq $$32, %rsp +; nextln: subq $$16, %rsp ; nextln: movq %r12, 0(%rsp) ; nextln: movq %r13, 8(%rsp) -; nextln: movq %r14, 16(%rsp) -; nextln: movq %r8, %r14 -; nextln: movq 16(%rbp), %r10 +; nextln: movq %r9, %r11 +; nextln: movq 16(%rbp), %r13 ; nextln: movq 24(%rbp), %r12 -; nextln: movq 32(%rbp), %r11 -; nextln: movq 40(%rbp), %rax -; nextln: movq 48(%rbp), %r13 -; nextln: movq %rsi, %r8 +; nextln: movq 32(%rbp), %r10 +; nextln: movq 40(%rbp), %r9 +; nextln: movq 48(%rbp), %rax ; nextln: addq %rdx, %rdi -; nextln: adcq %rcx, %r8 +; nextln: movq %rsi, %rdx +; nextln: adcq %rcx, %rdx ; nextln: xorq %rsi, %rsi -; nextln: addq %r14, %r9 -; nextln: adcq %rsi, %r10 -; nextln: addq %rax, %r12 -; nextln: adcq %r13, %r11 -; nextln: addq %r9, %rdi -; nextln: adcq %r10, %r8 +; nextln: addq %r8, %r11 +; nextln: adcq %rsi, %r13 +; nextln: addq %r9, %r12 +; nextln: adcq %rax, %r10 +; nextln: addq %r11, %rdi +; nextln: adcq %r13, %rdx ; nextln: addq %rdi, %r12 -; nextln: adcq %r8, %r11 +; nextln: adcq %rdx, %r10 ; nextln: movq %r12, %rax -; nextln: movq %r11, %rdx +; nextln: movq %r10, %rdx ; nextln: movq 0(%rsp), %r12 ; nextln: movq 8(%rsp), %r13 -; nextln: movq 16(%rsp), %r14 -; nextln: addq $$32, %rsp +; nextln: addq $$16, %rsp ; nextln: movq %rbp, %rsp ; nextln: popq %rbp ; nextln: ret @@ -907,26 +904,25 @@ block0(v0: i128, v1: i128): ; check: pushq %rbp ; nextln: movq %rsp, %rbp -; nextln: movq %rsi, %rax -; nextln: movq %rdi, %rsi +; nextln: movq %rdi, %rax +; nextln: movq %rsi, %rdi +; nextln: movq %rax, %rsi ; nextln: movq %rdx, %rcx ; nextln: shlq %cl, %rsi ; nextln: movq %rdx, %rcx -; nextln: shlq %cl, %rax +; nextln: shlq %cl, %rdi ; nextln: movl $$64, %ecx ; nextln: subq %rdx, %rcx -; nextln: shrq %cl, %rdi +; nextln: shrq %cl, %rax ; nextln: xorq %rcx, %rcx ; nextln: testq $$127, %rdx -; nextln: cmovzq %rcx, %rdi -; nextln: orq %rax, %rdi -; nextln: xorq %rax, %rax -; nextln: andq $$64, %rdx -; nextln: cmovzq %rdi, %rax +; nextln: cmovzq %rcx, %rax +; nextln: orq %rdi, %rax +; nextln: testq $$64, %rdx ; nextln: cmovzq %rsi, %rcx -; nextln: cmovnzq %rsi, %rax -; nextln: movq %rax, %rdx +; nextln: cmovzq %rax, %rsi ; nextln: movq %rcx, %rax +; nextln: movq %rsi, %rdx ; nextln: movq %rbp, %rsp ; nextln: popq %rbp ; nextln: ret @@ -939,28 +935,26 @@ block0(v0: i128, v1: i128): ; check: pushq %rbp ; nextln: movq %rsp, %rbp -; nextln: movq %rdi, %rax -; nextln: movq %rsi, %rdi -; nextln: movq %rdi, %rsi +; nextln: movq %rsi, %rax +; nextln: movq %rdx, %rcx +; nextln: shrq %cl, %rdi +; nextln: movq %rax, %rsi ; nextln: movq %rdx, %rcx ; nextln: shrq %cl, %rsi -; nextln: movq %rdx, %rcx -; nextln: shrq %cl, %rax ; nextln: movl $$64, %ecx ; nextln: subq %rdx, %rcx -; nextln: shlq %cl, %rdi +; nextln: shlq %cl, %rax ; nextln: xorq %rcx, %rcx ; nextln: testq $$127, %rdx -; nextln: cmovzq %rcx, %rdi -; nextln: orq %rax, %rdi -; nextln: xorq %rax, %rax +; nextln: cmovzq %rcx, %rax +; nextln: orq %rdi, %rax ; nextln: xorq %rcx, %rcx -; nextln: andq $$64, %rdx -; nextln: cmovzq %rsi, %rax -; nextln: cmovzq %rdi, %rcx -; nextln: cmovnzq %rsi, %rcx -; nextln: movq %rax, %rdx -; nextln: movq %rcx, %rax +; nextln: testq $$64, %rdx +; nextln: movq %rsi, %rdi +; nextln: cmovzq %rax, %rdi +; nextln: cmovzq %rsi, %rcx +; nextln: movq %rdi, %rax +; nextln: movq %rcx, %rdx ; nextln: movq %rbp, %rsp ; nextln: popq %rbp ; nextln: ret @@ -1006,53 +1000,51 @@ block0(v0: i128, v1: i128): return v2 } -; check: pushq %rbp +; check: pushq %rbp ; nextln: movq %rsp, %rbp -; nextln: movq %rdi, %r8 -; nextln: movq %r8, %r9 -; nextln: movq %rdx, %rcx -; nextln: shlq %cl, %r9 -; nextln: movq %rsi, %rax +; nextln: movq %rdi, %rax ; nextln: movq %rdx, %rcx ; nextln: shlq %cl, %rax +; nextln: movq %rsi, %r8 +; nextln: movq %rdx, %rcx +; nextln: shlq %cl, %r8 ; nextln: movl $$64, %ecx ; nextln: subq %rdx, %rcx -; nextln: movq %r8, %r10 -; nextln: shrq %cl, %r10 -; nextln: xorq %rdi, %rdi +; nextln: movq %rdi, %r9 +; nextln: shrq %cl, %r9 +; nextln: xorq %rcx, %rcx ; nextln: testq $$127, %rdx -; nextln: cmovzq %rdi, %r10 -; nextln: orq %rax, %r10 -; nextln: xorq %rax, %rax -; nextln: movq %rdx, %rcx -; nextln: andq $$64, %rcx -; nextln: cmovzq %r10, %rax -; nextln: cmovzq %r9, %rdi -; nextln: cmovnzq %r9, %rax +; nextln: cmovzq %rcx, %r9 +; nextln: orq %r8, %r9 +; nextln: testq $$64, %rdx +; nextln: movq %rcx, %r8 +; nextln: cmovzq %rax, %r8 +; nextln: cmovzq %r9, %rax ; nextln: movl $$128, %r9d ; nextln: subq %rdx, %r9 -; nextln: movq %rsi, %rdx +; nextln: movq %rdi, %rdx ; nextln: movq %r9, %rcx ; nextln: shrq %cl, %rdx +; nextln: movq %rsi, %rdi ; nextln: movq %r9, %rcx -; nextln: shrq %cl, %r8 +; nextln: shrq %cl, %rdi ; nextln: movl $$64, %ecx ; nextln: subq %r9, %rcx ; nextln: shlq %cl, %rsi ; nextln: xorq %rcx, %rcx ; nextln: testq $$127, %r9 ; nextln: cmovzq %rcx, %rsi -; nextln: orq %r8, %rsi -; nextln: xorq %rcx, %rcx -; nextln: xorq %r8, %r8 -; nextln: andq $$64, %r9 -; nextln: cmovzq %rdx, %rcx -; nextln: cmovzq %rsi, %r8 -; nextln: cmovnzq %rdx, %r8 -; nextln: orq %rdi, %r8 -; nextln: orq %rax, %rcx +; nextln: orq %rdx, %rsi +; nextln: xorq %rdx, %rdx +; nextln: testq $$64, %r9 +; nextln: movq %rdi, %rcx +; nextln: cmovzq %rsi, %rcx +; nextln: movq %rdx, %rsi +; nextln: cmovzq %rdi, %rsi +; nextln: orq %rcx, %r8 +; nextln: orq %rsi, %rax +; nextln: movq %rax, %rdx ; nextln: movq %r8, %rax -; nextln: movq %rcx, %rdx ; nextln: movq %rbp, %rsp ; nextln: popq %rbp ; nextln: ret diff --git a/cranelift/isle/fuzz/README.md b/cranelift/isle/fuzz/README.md new file mode 100644 index 0000000000..a6d695df10 --- /dev/null +++ b/cranelift/isle/fuzz/README.md @@ -0,0 +1,4 @@ +# ISLE Fuzz Targets + +These are separate from the top-level `wasmtime/fuzz` fuzz targets because we +don't intend to run them on OSS-Fuzz. They are just for local ISLE hacking. diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 5c7acee3a4..14d709ef07 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["Chris Fallin ", "Nick Fitzgerald "] +authors = ["The Cranelift Project Developers"] description = "ISLE: Instruction Selection and Lowering Expressions. A domain-specific language for instruction selection in Cranelift." edition = "2018" license = "Apache-2.0 WITH LLVM-exception" diff --git a/cranelift/isle/islec/Cargo.toml b/cranelift/isle/islec/Cargo.toml index cc80b44fc0..3a6ab3d115 100644 --- a/cranelift/isle/islec/Cargo.toml +++ b/cranelift/isle/islec/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "islec" version = "0.1.0" -authors = ["Chris Fallin "] +authors = ["The Cranelift Project Developers"] edition = "2018" license = "Apache-2.0 WITH LLVM-exception" +publish = false [dependencies] log = "0.4" diff --git a/deny.toml b/deny.toml index 1bfbbf46c8..bcfd4b41a2 100644 --- a/deny.toml +++ b/deny.toml @@ -35,4 +35,5 @@ skip = [ { name = "wast" }, # old one pulled in by witx { name = "itertools" }, # 0.9 pulled in by criterion-plot { name = "quick-error" }, # transitive dependencies + { name = "textwrap" }, # `miette` and `clap` depend on different versions ] diff --git a/scripts/publish.rs b/scripts/publish.rs index e4aa342246..1b8083eb4a 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -26,6 +26,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "peepmatic", "peepmatic-souper", // cranelift + "isle", "cranelift-entity", "wasmtime-types", "cranelift-bforest", From 00e7ca206f7051c3f41173e5e6193cedbc09fe90 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 5 Nov 2021 14:05:38 -0700 Subject: [PATCH 82/95] ISLE: add a missing type check for whether terms used in expressions have a constructor --- cranelift/isle/isle/src/ast.rs | 19 +++++++++++++++++++ cranelift/isle/isle/src/sema.rs | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index 4b2aa84ca0..57f257a892 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -338,6 +338,25 @@ impl Expr { | &Expr::Let { pos, .. } => pos, } } + + /// Call `f` for each of the terms in this expression. + pub fn terms(&self, f: &mut dyn FnMut(Pos, &Ident)) { + match self { + Expr::Term { sym, args, pos } => { + f(*pos, sym); + for arg in args { + arg.terms(f); + } + } + Expr::Let { defs, body, .. } => { + for def in defs { + def.val.terms(f); + } + body.terms(f); + } + Expr::Var { .. } | Expr::ConstInt { .. } | Expr::ConstPrim { .. } => {} + } + } } /// One variable locally bound in a `(let ...)` expression. diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 0aae69a2b9..40210709fc 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -767,6 +767,7 @@ impl TermEnv { tyenv.return_errors()?; env.collect_rules(tyenv, defs); env.check_for_undefined_decls(tyenv, defs); + env.check_for_expr_terms_without_constructors(tyenv, defs); tyenv.return_errors()?; Ok(env) @@ -1283,6 +1284,28 @@ impl TermEnv { } } + fn check_for_expr_terms_without_constructors(&self, tyenv: &mut TypeEnv, defs: &ast::Defs) { + for def in &defs.defs { + if let ast::Def::Rule(rule) = def { + rule.expr.terms(&mut |pos, ident| { + let sym = tyenv.intern_mut(ident); + let term = self.term_map[&sym]; + let term = &self.terms[term.index()]; + if !term.has_constructor() { + tyenv.report_error( + pos, + format!( + "term `{}` cannot be used in an expression because \ + it does not have a constructor", + ident.0 + ), + ) + } + }); + } + } + } + fn translate_pattern( &self, tyenv: &mut TypeEnv, From 30d206779ec370eac8c0e2e527f44ced4aa0f912 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 5 Nov 2021 14:28:03 -0700 Subject: [PATCH 83/95] x64: Remove some unreachable code that's been ported to ISLE --- cranelift/codegen/src/isa/x64/lower.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs index 65e94e396a..312d3423aa 100644 --- a/cranelift/codegen/src/isa/x64/lower.rs +++ b/cranelift/codegen/src/isa/x64/lower.rs @@ -1545,20 +1545,7 @@ fn lower_insn_to_regs>( } else if ty == types::I64X2 { unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); } else if ty.lane_count() > 1 { - // Emit single instruction lowerings for the remaining vector - // multiplications. - let sse_op = match ty { - types::I16X8 => SseOpcode::Pmullw, - types::I32X4 => SseOpcode::Pmulld, - _ => panic!("Unsupported type for packed imul instruction: {}", ty), - }; - let lhs = put_input_in_reg(ctx, inputs[0]); - let rhs = input_to_reg_mem(ctx, inputs[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - - // Move the `lhs` to the same register as `dst`. - ctx.emit(Inst::gen_move(dst, lhs, ty)); - ctx.emit(Inst::xmm_rm_r(sse_op, rhs, dst)); + unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); } else if ty == types::I128 || ty == types::B128 { // Handle 128-bit multiplications. let lhs = put_input_in_regs(ctx, inputs[0]); From b8494822dcca1aa5c481b11a52030a7f3270e2b2 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 5 Nov 2021 15:41:24 -0700 Subject: [PATCH 84/95] ISLE: finish porting `imul` lowering to ISLE --- cranelift/codegen/src/isa/x64/inst.isle | 29 ++ cranelift/codegen/src/isa/x64/lower.isle | 38 +++ cranelift/codegen/src/isa/x64/lower.rs | 96 +----- .../src/isa/x64/lower/isle/generated_code.rs | 287 +++++++++++------- .../filetests/filetests/isa/x64/i128.clif | 12 +- cranelift/isle/isle/src/sema.rs | 8 +- 6 files changed, 265 insertions(+), 205 deletions(-) diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle index e89ccc3bcc..29bb6a9003 100644 --- a/cranelift/codegen/src/isa/x64/inst.isle +++ b/cranelift/codegen/src/isa/x64/inst.isle @@ -9,6 +9,12 @@ (src1 Reg) (src2 RegMemImm) (dst WritableReg)) + (MulHi (size OperandSize) + (signed bool) + (src1 Reg) + (src2 RegMem) + (dst_lo WritableReg) + (dst_hi WritableReg)) (XmmRmR (op SseOpcode) (src1 Reg) (src2 RegMem) @@ -931,3 +937,26 @@ (decl psrlq (Reg RegMemImm) Reg) (rule (psrlq src1 src2) (xmm_rmi_reg (SseOpcode.Psrlq) src1 src2)) + +;; Helper for creating `MInst.MulHi` instructions. +;; +;; Returns the (lo, hi) register halves of the multiplication. +(decl mul_hi (Type bool Reg RegMem) ValueRegs) +(rule (mul_hi ty signed src1 src2) + (let ((dst_lo WritableReg (temp_writable_reg ty)) + (dst_hi WritableReg (temp_writable_reg ty)) + (size OperandSize (operand_size_of_type ty)) + (_ Unit (emit (MInst.MulHi size + signed + src1 + src2 + dst_lo + dst_hi)))) + (value_regs (writable_reg_to_reg dst_lo) + (writable_reg_to_reg dst_hi)))) + +;; Helper for creating `mul` instructions that return both the lower and +;; (unsigned) higher halves of the result. +(decl mulhi_u (Type Reg RegMem) ValueRegs) +(rule (mulhi_u ty src1 src2) + (mul_hi ty $false src1 src2)) diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle index f89caccfe4..71a5e1c6c5 100644 --- a/cranelift/codegen/src/isa/x64/lower.isle +++ b/cranelift/codegen/src/isa/x64/lower.isle @@ -678,6 +678,44 @@ (put_in_reg y) (sink_load x)))) +;; `i128`. + +;; mul: +;; dst_lo = lhs_lo * rhs_lo +;; dst_hi = umulhi(lhs_lo, rhs_lo) + +;; lhs_lo * rhs_hi + +;; lhs_hi * rhs_lo +;; +;; so we emit: +;; lo_hi = mul x_lo, y_hi +;; hi_lo = mul x_hi, y_lo +;; hilo_hilo = add lo_hi, hi_lo +;; dst_lo:hi_lolo = mulhi_u x_lo, y_lo +;; dst_hi = add hilo_hilo, hi_lolo +;; return (dst_lo, dst_hi) +(rule (lower (has_type $I128 (imul x y))) + ;; Put `x` into registers and unpack its hi/lo halves. + (let ((x_regs ValueRegs (put_in_regs x)) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + ;; Put `y` into registers and unpack its hi/lo halves. + (y_regs ValueRegs (put_in_regs y)) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1)) + ;; lo_hi = mul x_lo, y_hi + (lo_hi Reg (mul $I64 x_lo (RegMemImm.Reg y_hi))) + ;; hi_lo = mul x_hi, y_lo + (hi_lo Reg (mul $I64 x_hi (RegMemImm.Reg y_lo))) + ;; hilo_hilo = add lo_hi, hi_lo + (hilo_hilo Reg (add $I64 lo_hi (RegMemImm.Reg hi_lo))) + ;; dst_lo:hi_lolo = mulhi_u x_lo, y_lo + (mul_regs ValueRegs (mulhi_u $I64 x_lo (RegMem.Reg y_lo))) + (dst_lo Reg (value_regs_get mul_regs 0)) + (hi_lolo Reg (value_regs_get mul_regs 1)) + ;; dst_hi = add hilo_hilo, hi_lolo + (dst_hi Reg (add $I64 hilo_hilo (RegMemImm.Reg hi_lolo)))) + (value_regs dst_lo dst_hi))) + ;; SSE. ;; (No i8x16 multiply.) diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs index 312d3423aa..1a70a043db 100644 --- a/cranelift/codegen/src/isa/x64/lower.rs +++ b/cranelift/codegen/src/isa/x64/lower.rs @@ -1518,7 +1518,8 @@ fn lower_insn_to_regs>( | Opcode::AvgRound | Opcode::Band | Opcode::Bor - | Opcode::Bxor => { + | Opcode::Bxor + | Opcode::Imul => { unreachable!( "implemented in ISLE: inst = `{}`, type = `{:?}`", ctx.dfg().display_inst(insn), @@ -1526,99 +1527,6 @@ fn lower_insn_to_regs>( ); } - Opcode::Imul => { - let ty = ty.unwrap(); - - // Check for ext_mul_* instructions which are being shared here under imul. We must - // check first for operands that are opcodes since checking for types is not enough. - if let Some(_) = matches_input_any( - ctx, - inputs[0], - &[ - Opcode::SwidenHigh, - Opcode::SwidenLow, - Opcode::UwidenHigh, - Opcode::UwidenLow, - ], - ) { - unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); - } else if ty == types::I64X2 { - unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); - } else if ty.lane_count() > 1 { - unreachable!("implemented in ISLE: {}", ctx.dfg().display_inst(insn)); - } else if ty == types::I128 || ty == types::B128 { - // Handle 128-bit multiplications. - let lhs = put_input_in_regs(ctx, inputs[0]); - let rhs = put_input_in_regs(ctx, inputs[1]); - let dst = get_output_reg(ctx, outputs[0]); - assert_eq!(lhs.len(), 2); - assert_eq!(rhs.len(), 2); - assert_eq!(dst.len(), 2); - - // mul: - // dst_lo = lhs_lo * rhs_lo - // dst_hi = umulhi(lhs_lo, rhs_lo) + lhs_lo * rhs_hi + lhs_hi * rhs_lo - // - // so we emit: - // mov dst_lo, lhs_lo - // mul dst_lo, rhs_lo - // mov dst_hi, lhs_lo - // mul dst_hi, rhs_hi - // mov tmp, lhs_hi - // mul tmp, rhs_lo - // add dst_hi, tmp - // mov rax, lhs_lo - // umulhi rhs_lo // implicit rax arg/dst - // add dst_hi, rax - let tmp = ctx.alloc_tmp(types::I64).only_reg().unwrap(); - ctx.emit(Inst::gen_move(dst.regs()[0], lhs.regs()[0], types::I64)); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - AluRmiROpcode::Mul, - RegMemImm::reg(rhs.regs()[0]), - dst.regs()[0], - )); - ctx.emit(Inst::gen_move(dst.regs()[1], lhs.regs()[0], types::I64)); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - AluRmiROpcode::Mul, - RegMemImm::reg(rhs.regs()[1]), - dst.regs()[1], - )); - ctx.emit(Inst::gen_move(tmp, lhs.regs()[1], types::I64)); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - AluRmiROpcode::Mul, - RegMemImm::reg(rhs.regs()[0]), - tmp, - )); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - AluRmiROpcode::Add, - RegMemImm::reg(tmp.to_reg()), - dst.regs()[1], - )); - ctx.emit(Inst::gen_move( - Writable::from_reg(regs::rax()), - lhs.regs()[0], - types::I64, - )); - ctx.emit(Inst::mul_hi( - OperandSize::Size64, - /* signed = */ false, - RegMem::reg(rhs.regs()[0]), - )); - ctx.emit(Inst::alu_rmi_r( - OperandSize::Size64, - AluRmiROpcode::Add, - RegMemImm::reg(regs::rdx()), - dst.regs()[1], - )); - } else { - unreachable!("implemented in ISLE") - } - } - Opcode::BandNot => { let ty = ty.unwrap(); debug_assert!(ty.is_vector() && ty.bytes() == 16); diff --git a/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs b/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs index 8050f87947..adeab6f063 100644 --- a/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs +++ b/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs @@ -57,19 +57,19 @@ pub trait Context { fn nonzero_u64_fits_in_u32(&mut self, arg0: u64) -> Option; } -/// Internal type ProducesFlags: defined at src/isa/x64/inst.isle line 368. +/// Internal type ProducesFlags: defined at src/isa/x64/inst.isle line 374. #[derive(Clone, Debug)] pub enum ProducesFlags { ProducesFlags { inst: MInst, result: Reg }, } -/// Internal type ConsumesFlags: defined at src/isa/x64/inst.isle line 371. +/// Internal type ConsumesFlags: defined at src/isa/x64/inst.isle line 377. #[derive(Clone, Debug)] pub enum ConsumesFlags { ConsumesFlags { inst: MInst, result: Reg }, } -/// Internal type ExtendKind: defined at src/isa/x64/inst.isle line 409. +/// Internal type ExtendKind: defined at src/isa/x64/inst.isle line 415. #[derive(Clone, Debug)] pub enum ExtendKind { Sign, @@ -100,22 +100,22 @@ pub fn constructor_operand_size_bits(ctx: &mut C, arg0: &OperandSize let pattern0_0 = arg0; match pattern0_0 { &OperandSize::Size8 => { - // Rule at src/isa/x64/inst.isle line 69. + // Rule at src/isa/x64/inst.isle line 75. let expr0_0: u16 = 8; return Some(expr0_0); } &OperandSize::Size16 => { - // Rule at src/isa/x64/inst.isle line 70. + // Rule at src/isa/x64/inst.isle line 76. let expr0_0: u16 = 16; return Some(expr0_0); } &OperandSize::Size32 => { - // Rule at src/isa/x64/inst.isle line 71. + // Rule at src/isa/x64/inst.isle line 77. let expr0_0: u16 = 32; return Some(expr0_0); } &OperandSize::Size64 => { - // Rule at src/isa/x64/inst.isle line 72. + // Rule at src/isa/x64/inst.isle line 78. let expr0_0: u16 = 64; return Some(expr0_0); } @@ -142,7 +142,7 @@ pub fn constructor_with_flags( result: pattern3_1, } = pattern2_0 { - // Rule at src/isa/x64/inst.isle line 381. + // Rule at src/isa/x64/inst.isle line 387. let expr0_0 = C::emit(ctx, &pattern1_0); let expr1_0 = C::emit(ctx, &pattern3_0); let expr2_0 = C::value_regs(ctx, pattern1_1, pattern3_1); @@ -170,7 +170,7 @@ pub fn constructor_with_flags_1( result: pattern3_1, } = pattern2_0 { - // Rule at src/isa/x64/inst.isle line 389. + // Rule at src/isa/x64/inst.isle line 395. let expr0_0 = C::emit(ctx, &pattern1_0); let expr1_0 = C::emit(ctx, &pattern3_0); return Some(pattern3_1); @@ -204,7 +204,7 @@ pub fn constructor_with_flags_2( result: pattern5_1, } = pattern4_0 { - // Rule at src/isa/x64/inst.isle line 399. + // Rule at src/isa/x64/inst.isle line 405. let expr0_0 = C::emit(ctx, &pattern1_0); let expr1_0 = C::emit(ctx, &pattern3_0); let expr2_0 = C::emit(ctx, &pattern5_0); @@ -228,12 +228,12 @@ pub fn constructor_extend_to_reg( let pattern2_0 = arg1; if pattern2_0 == pattern1_0 { let pattern4_0 = arg2; - // Rule at src/isa/x64/inst.isle line 421. + // Rule at src/isa/x64/inst.isle line 427. let expr0_0 = C::put_in_reg(ctx, pattern0_0); return Some(expr0_0); } let pattern3_0 = arg2; - // Rule at src/isa/x64/inst.isle line 424. + // Rule at src/isa/x64/inst.isle line 430. let expr0_0 = C::ty_bits(ctx, pattern1_0); let expr1_0 = C::operand_size_of_type(ctx, pattern2_0); let expr2_0 = constructor_operand_size_bits(ctx, &expr1_0)?; @@ -257,7 +257,7 @@ pub fn constructor_extend( let pattern2_0 = arg1; let pattern3_0 = arg2; let pattern4_0 = arg3; - // Rule at src/isa/x64/inst.isle line 444. + // Rule at src/isa/x64/inst.isle line 450. let expr0_0 = constructor_movsx(ctx, pattern2_0, pattern3_0, pattern4_0)?; return Some(expr0_0); } @@ -265,7 +265,7 @@ pub fn constructor_extend( let pattern2_0 = arg1; let pattern3_0 = arg2; let pattern4_0 = arg3; - // Rule at src/isa/x64/inst.isle line 440. + // Rule at src/isa/x64/inst.isle line 446. let expr0_0 = constructor_movzx(ctx, pattern2_0, pattern3_0, pattern4_0)?; return Some(expr0_0); } @@ -286,7 +286,7 @@ pub fn constructor_alu_rmi_r( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 462. + // Rule at src/isa/x64/inst.isle line 468. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = MInst::AluRmiR { @@ -311,7 +311,7 @@ pub fn constructor_add( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 470. + // Rule at src/isa/x64/inst.isle line 476. let expr0_0 = AluRmiROpcode::Add; let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -327,7 +327,7 @@ pub fn constructor_add_with_flags( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 478. + // Rule at src/isa/x64/inst.isle line 484. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = AluRmiROpcode::Add; @@ -356,7 +356,7 @@ pub fn constructor_adc( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 489. + // Rule at src/isa/x64/inst.isle line 495. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = AluRmiROpcode::Adc; @@ -385,7 +385,7 @@ pub fn constructor_sub( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 500. + // Rule at src/isa/x64/inst.isle line 506. let expr0_0 = AluRmiROpcode::Sub; let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -401,7 +401,7 @@ pub fn constructor_sub_with_flags( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 508. + // Rule at src/isa/x64/inst.isle line 514. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = AluRmiROpcode::Sub; @@ -430,7 +430,7 @@ pub fn constructor_sbb( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 519. + // Rule at src/isa/x64/inst.isle line 525. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = AluRmiROpcode::Sbb; @@ -459,7 +459,7 @@ pub fn constructor_mul( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 530. + // Rule at src/isa/x64/inst.isle line 536. let expr0_0 = AluRmiROpcode::Mul; let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -475,7 +475,7 @@ pub fn constructor_m_and( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 541. + // Rule at src/isa/x64/inst.isle line 547. let expr0_0 = AluRmiROpcode::And; let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -491,7 +491,7 @@ pub fn constructor_or( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 549. + // Rule at src/isa/x64/inst.isle line 555. let expr0_0 = AluRmiROpcode::Or; let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -507,7 +507,7 @@ pub fn constructor_xor( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 557. + // Rule at src/isa/x64/inst.isle line 563. let expr0_0 = AluRmiROpcode::Xor; let expr1_0 = constructor_alu_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -519,7 +519,7 @@ pub fn constructor_imm(ctx: &mut C, arg0: Type, arg1: u64) -> Option if pattern0_0 == I64 { let pattern2_0 = arg1; if let Some(pattern3_0) = C::nonzero_u64_fits_in_u32(ctx, pattern2_0) { - // Rule at src/isa/x64/inst.isle line 576. + // Rule at src/isa/x64/inst.isle line 582. let expr0_0: Type = I64; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = OperandSize::Size32; @@ -535,7 +535,7 @@ pub fn constructor_imm(ctx: &mut C, arg0: Type, arg1: u64) -> Option } let pattern1_0 = arg1; if pattern1_0 == 0 { - // Rule at src/isa/x64/inst.isle line 582. + // Rule at src/isa/x64/inst.isle line 588. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::writable_reg_to_reg(ctx, expr0_0); let expr2_0 = C::operand_size_of_type(ctx, pattern0_0); @@ -551,7 +551,7 @@ pub fn constructor_imm(ctx: &mut C, arg0: Type, arg1: u64) -> Option let expr6_0 = C::emit(ctx, &expr5_0); return Some(expr1_0); } - // Rule at src/isa/x64/inst.isle line 565. + // Rule at src/isa/x64/inst.isle line 571. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = MInst::Imm { @@ -576,7 +576,7 @@ pub fn constructor_shift_r( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 595. + // Rule at src/isa/x64/inst.isle line 601. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = MInst::ShiftR { @@ -601,7 +601,7 @@ pub fn constructor_m_rotl( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 604. + // Rule at src/isa/x64/inst.isle line 610. let expr0_0 = ShiftKind::RotateLeft; let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -617,7 +617,7 @@ pub fn constructor_shl( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 609. + // Rule at src/isa/x64/inst.isle line 615. let expr0_0 = ShiftKind::ShiftLeft; let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -633,7 +633,7 @@ pub fn constructor_shr( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 614. + // Rule at src/isa/x64/inst.isle line 620. let expr0_0 = ShiftKind::ShiftRightLogical; let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -649,7 +649,7 @@ pub fn constructor_sar( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 619. + // Rule at src/isa/x64/inst.isle line 625. let expr0_0 = ShiftKind::ShiftRightArithmetic; let expr1_0 = constructor_shift_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -667,7 +667,7 @@ pub fn constructor_cmp_rmi_r( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 624. + // Rule at src/isa/x64/inst.isle line 630. let expr0_0 = MInst::CmpRmiR { size: pattern0_0.clone(), opcode: pattern1_0.clone(), @@ -692,7 +692,7 @@ pub fn constructor_cmp( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 633. + // Rule at src/isa/x64/inst.isle line 639. let expr0_0 = CmpOpcode::Cmp; let expr1_0 = constructor_cmp_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -708,7 +708,7 @@ pub fn constructor_test( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 638. + // Rule at src/isa/x64/inst.isle line 644. let expr0_0 = CmpOpcode::Test; let expr1_0 = constructor_cmp_rmi_r(ctx, pattern0_0, &expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -726,7 +726,7 @@ pub fn constructor_cmove( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 643. + // Rule at src/isa/x64/inst.isle line 649. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::operand_size_of_type(ctx, pattern0_0); let expr2_0 = MInst::Cmove { @@ -754,7 +754,7 @@ pub fn constructor_movzx( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 651. + // Rule at src/isa/x64/inst.isle line 657. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = MInst::MovzxRmR { ext_mode: pattern1_0.clone(), @@ -776,7 +776,7 @@ pub fn constructor_movsx( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 658. + // Rule at src/isa/x64/inst.isle line 664. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = MInst::MovsxRmR { ext_mode: pattern1_0.clone(), @@ -800,7 +800,7 @@ pub fn constructor_xmm_rm_r( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 665. + // Rule at src/isa/x64/inst.isle line 671. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = MInst::XmmRmR { op: pattern1_0.clone(), @@ -817,7 +817,7 @@ pub fn constructor_xmm_rm_r( pub fn constructor_paddb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 672. + // Rule at src/isa/x64/inst.isle line 678. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Paddb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -828,7 +828,7 @@ pub fn constructor_paddb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_paddw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 677. + // Rule at src/isa/x64/inst.isle line 683. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Paddw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -839,7 +839,7 @@ pub fn constructor_paddw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_paddd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 682. + // Rule at src/isa/x64/inst.isle line 688. let expr0_0: Type = I32X4; let expr1_0 = SseOpcode::Paddd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -850,7 +850,7 @@ pub fn constructor_paddd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_paddq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 687. + // Rule at src/isa/x64/inst.isle line 693. let expr0_0: Type = I64X2; let expr1_0 = SseOpcode::Paddq; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -861,7 +861,7 @@ pub fn constructor_paddq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_paddsb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 692. + // Rule at src/isa/x64/inst.isle line 698. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Paddsb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -872,7 +872,7 @@ pub fn constructor_paddsb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_paddsw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 697. + // Rule at src/isa/x64/inst.isle line 703. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Paddsw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -883,7 +883,7 @@ pub fn constructor_paddsw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_paddusb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 702. + // Rule at src/isa/x64/inst.isle line 708. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Paddusb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -894,7 +894,7 @@ pub fn constructor_paddusb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_paddusw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 707. + // Rule at src/isa/x64/inst.isle line 713. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Paddusw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -905,7 +905,7 @@ pub fn constructor_paddusw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_psubb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 712. + // Rule at src/isa/x64/inst.isle line 718. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Psubb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -916,7 +916,7 @@ pub fn constructor_psubb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_psubw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 717. + // Rule at src/isa/x64/inst.isle line 723. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Psubw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -927,7 +927,7 @@ pub fn constructor_psubw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_psubd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 722. + // Rule at src/isa/x64/inst.isle line 728. let expr0_0: Type = I32X4; let expr1_0 = SseOpcode::Psubd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -938,7 +938,7 @@ pub fn constructor_psubd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_psubq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 727. + // Rule at src/isa/x64/inst.isle line 733. let expr0_0: Type = I64X2; let expr1_0 = SseOpcode::Psubq; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -949,7 +949,7 @@ pub fn constructor_psubq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_psubsb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 732. + // Rule at src/isa/x64/inst.isle line 738. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Psubsb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -960,7 +960,7 @@ pub fn constructor_psubsb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_psubsw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 737. + // Rule at src/isa/x64/inst.isle line 743. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Psubsw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -971,7 +971,7 @@ pub fn constructor_psubsw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_psubusb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 742. + // Rule at src/isa/x64/inst.isle line 748. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Psubusb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -982,7 +982,7 @@ pub fn constructor_psubusb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_psubusw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 747. + // Rule at src/isa/x64/inst.isle line 753. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Psubusw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -993,7 +993,7 @@ pub fn constructor_psubusw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_pavgb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 752. + // Rule at src/isa/x64/inst.isle line 758. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Pavgb; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1004,7 +1004,7 @@ pub fn constructor_pavgb(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_pavgw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 757. + // Rule at src/isa/x64/inst.isle line 763. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Pavgw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1015,7 +1015,7 @@ pub fn constructor_pavgw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_pand(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 762. + // Rule at src/isa/x64/inst.isle line 768. let expr0_0: Type = F32X4; let expr1_0 = SseOpcode::Pand; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1026,7 +1026,7 @@ pub fn constructor_pand(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Op pub fn constructor_andps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 767. + // Rule at src/isa/x64/inst.isle line 773. let expr0_0: Type = F32X4; let expr1_0 = SseOpcode::Andps; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1037,7 +1037,7 @@ pub fn constructor_andps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_andpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 772. + // Rule at src/isa/x64/inst.isle line 778. let expr0_0: Type = F64X2; let expr1_0 = SseOpcode::Andpd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1048,7 +1048,7 @@ pub fn constructor_andpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_por(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 777. + // Rule at src/isa/x64/inst.isle line 783. let expr0_0: Type = F32X4; let expr1_0 = SseOpcode::Por; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1059,7 +1059,7 @@ pub fn constructor_por(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Opt pub fn constructor_orps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 782. + // Rule at src/isa/x64/inst.isle line 788. let expr0_0: Type = F32X4; let expr1_0 = SseOpcode::Orps; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1070,7 +1070,7 @@ pub fn constructor_orps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Op pub fn constructor_orpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 787. + // Rule at src/isa/x64/inst.isle line 793. let expr0_0: Type = F64X2; let expr1_0 = SseOpcode::Orpd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1081,7 +1081,7 @@ pub fn constructor_orpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Op pub fn constructor_pxor(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 792. + // Rule at src/isa/x64/inst.isle line 798. let expr0_0: Type = I8X16; let expr1_0 = SseOpcode::Pxor; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1092,7 +1092,7 @@ pub fn constructor_pxor(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Op pub fn constructor_xorps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 797. + // Rule at src/isa/x64/inst.isle line 803. let expr0_0: Type = F32X4; let expr1_0 = SseOpcode::Xorps; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1103,7 +1103,7 @@ pub fn constructor_xorps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_xorpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 802. + // Rule at src/isa/x64/inst.isle line 808. let expr0_0: Type = F64X2; let expr1_0 = SseOpcode::Xorpd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1114,7 +1114,7 @@ pub fn constructor_xorpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> O pub fn constructor_pmullw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 807. + // Rule at src/isa/x64/inst.isle line 813. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Pmullw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1125,7 +1125,7 @@ pub fn constructor_pmullw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_pmulld(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 812. + // Rule at src/isa/x64/inst.isle line 818. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Pmulld; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1136,7 +1136,7 @@ pub fn constructor_pmulld(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_pmulhw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 817. + // Rule at src/isa/x64/inst.isle line 823. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Pmulhw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1147,7 +1147,7 @@ pub fn constructor_pmulhw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_pmulhuw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 822. + // Rule at src/isa/x64/inst.isle line 828. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Pmulhuw; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1158,7 +1158,7 @@ pub fn constructor_pmulhuw(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_pmuldq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 827. + // Rule at src/isa/x64/inst.isle line 833. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Pmuldq; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1169,7 +1169,7 @@ pub fn constructor_pmuldq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_pmuludq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 832. + // Rule at src/isa/x64/inst.isle line 838. let expr0_0: Type = I64X2; let expr1_0 = SseOpcode::Pmuludq; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1180,7 +1180,7 @@ pub fn constructor_pmuludq(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> pub fn constructor_punpckhwd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 837. + // Rule at src/isa/x64/inst.isle line 843. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Punpckhwd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1191,7 +1191,7 @@ pub fn constructor_punpckhwd(ctx: &mut C, arg0: Reg, arg1: &RegMem) pub fn constructor_punpcklwd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 842. + // Rule at src/isa/x64/inst.isle line 848. let expr0_0: Type = I16X8; let expr1_0 = SseOpcode::Punpcklwd; let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; @@ -1212,7 +1212,7 @@ pub fn constructor_xmm_rm_r_imm( let pattern2_0 = arg2; let pattern3_0 = arg3; let pattern4_0 = arg4; - // Rule at src/isa/x64/inst.isle line 847. + // Rule at src/isa/x64/inst.isle line 853. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmRmRImm { @@ -1240,7 +1240,7 @@ pub fn constructor_palignr( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 859. + // Rule at src/isa/x64/inst.isle line 865. let expr0_0 = SseOpcode::Palignr; let expr1_0 = constructor_xmm_rm_r_imm( ctx, &expr0_0, pattern0_0, pattern1_0, pattern2_0, pattern3_0, @@ -1258,7 +1258,7 @@ pub fn constructor_pshufd( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 868. + // Rule at src/isa/x64/inst.isle line 874. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = C::writable_reg_to_reg(ctx, expr1_0); @@ -1283,7 +1283,7 @@ pub fn constructor_xmm_unary_rm_r( ) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 881. + // Rule at src/isa/x64/inst.isle line 887. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmUnaryRmR { @@ -1299,7 +1299,7 @@ pub fn constructor_xmm_unary_rm_r( // Generated as internal constructor for term pmovsxbw. pub fn constructor_pmovsxbw(ctx: &mut C, arg0: &RegMem) -> Option { let pattern0_0 = arg0; - // Rule at src/isa/x64/inst.isle line 888. + // Rule at src/isa/x64/inst.isle line 894. let expr0_0 = SseOpcode::Pmovsxbw; let expr1_0 = constructor_xmm_unary_rm_r(ctx, &expr0_0, pattern0_0)?; return Some(expr1_0); @@ -1308,7 +1308,7 @@ pub fn constructor_pmovsxbw(ctx: &mut C, arg0: &RegMem) -> Option(ctx: &mut C, arg0: &RegMem) -> Option { let pattern0_0 = arg0; - // Rule at src/isa/x64/inst.isle line 893. + // Rule at src/isa/x64/inst.isle line 899. let expr0_0 = SseOpcode::Pmovzxbw; let expr1_0 = constructor_xmm_unary_rm_r(ctx, &expr0_0, pattern0_0)?; return Some(expr1_0); @@ -1324,7 +1324,7 @@ pub fn constructor_xmm_rm_r_evex( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 898. + // Rule at src/isa/x64/inst.isle line 904. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmRmREvex { @@ -1342,7 +1342,7 @@ pub fn constructor_xmm_rm_r_evex( pub fn constructor_vpmullq(ctx: &mut C, arg0: &RegMem, arg1: Reg) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 910. + // Rule at src/isa/x64/inst.isle line 916. let expr0_0 = Avx512Opcode::Vpmullq; let expr1_0 = constructor_xmm_rm_r_evex(ctx, &expr0_0, pattern0_0, pattern1_0)?; return Some(expr1_0); @@ -1358,7 +1358,7 @@ pub fn constructor_xmm_rmi_reg( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 917. + // Rule at src/isa/x64/inst.isle line 923. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmRmiReg { @@ -1376,7 +1376,7 @@ pub fn constructor_xmm_rmi_reg( pub fn constructor_psllq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 927. + // Rule at src/isa/x64/inst.isle line 933. let expr0_0 = SseOpcode::Psllq; let expr1_0 = constructor_xmm_rmi_reg(ctx, &expr0_0, pattern0_0, pattern1_0)?; return Some(expr1_0); @@ -1386,12 +1386,59 @@ pub fn constructor_psllq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) - pub fn constructor_psrlq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 932. + // Rule at src/isa/x64/inst.isle line 938. let expr0_0 = SseOpcode::Psrlq; let expr1_0 = constructor_xmm_rmi_reg(ctx, &expr0_0, pattern0_0, pattern1_0)?; return Some(expr1_0); } +// Generated as internal constructor for term mul_hi. +pub fn constructor_mul_hi( + ctx: &mut C, + arg0: Type, + arg1: bool, + arg2: Reg, + arg3: &RegMem, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + let pattern3_0 = arg3; + // Rule at src/isa/x64/inst.isle line 945. + let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr1_0 = C::temp_writable_reg(ctx, pattern0_0); + let expr2_0 = C::operand_size_of_type(ctx, pattern0_0); + let expr3_0 = MInst::MulHi { + size: expr2_0, + signed: pattern1_0, + src1: pattern2_0, + src2: pattern3_0.clone(), + dst_lo: expr0_0, + dst_hi: expr1_0, + }; + let expr4_0 = C::emit(ctx, &expr3_0); + let expr5_0 = C::writable_reg_to_reg(ctx, expr0_0); + let expr6_0 = C::writable_reg_to_reg(ctx, expr1_0); + let expr7_0 = C::value_regs(ctx, expr5_0, expr6_0); + return Some(expr7_0); +} + +// Generated as internal constructor for term mulhi_u. +pub fn constructor_mulhi_u( + ctx: &mut C, + arg0: Type, + arg1: Reg, + arg2: &RegMem, +) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + let pattern2_0 = arg2; + // Rule at src/isa/x64/inst.isle line 961. + let expr0_0: bool = false; + let expr1_0 = constructor_mul_hi(ctx, pattern0_0, expr0_0, pattern1_0, pattern2_0)?; + return Some(expr1_0); +} + // Generated as internal constructor for term lower. pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option { let pattern0_0 = arg0; @@ -1560,6 +1607,42 @@ pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option { + let (pattern7_0, pattern7_1) = + C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 696. + let expr0_0 = C::put_in_regs(ctx, pattern7_0); + let expr1_0: usize = 0; + let expr2_0 = C::value_regs_get(ctx, expr0_0, expr1_0); + let expr3_0: usize = 1; + let expr4_0 = C::value_regs_get(ctx, expr0_0, expr3_0); + let expr5_0 = C::put_in_regs(ctx, pattern7_1); + let expr6_0: usize = 0; + let expr7_0 = C::value_regs_get(ctx, expr5_0, expr6_0); + let expr8_0: usize = 1; + let expr9_0 = C::value_regs_get(ctx, expr5_0, expr8_0); + let expr10_0: Type = I64; + let expr11_0 = RegMemImm::Reg { reg: expr9_0 }; + let expr12_0 = constructor_mul(ctx, expr10_0, expr2_0, &expr11_0)?; + let expr13_0: Type = I64; + let expr14_0 = RegMemImm::Reg { reg: expr7_0 }; + let expr15_0 = constructor_mul(ctx, expr13_0, expr4_0, &expr14_0)?; + let expr16_0: Type = I64; + let expr17_0 = RegMemImm::Reg { reg: expr15_0 }; + let expr18_0 = constructor_add(ctx, expr16_0, expr12_0, &expr17_0)?; + let expr19_0: Type = I64; + let expr20_0 = RegMem::Reg { reg: expr7_0 }; + let expr21_0 = constructor_mulhi_u(ctx, expr19_0, expr2_0, &expr20_0)?; + let expr22_0: usize = 0; + let expr23_0 = C::value_regs_get(ctx, expr21_0, expr22_0); + let expr24_0: usize = 1; + let expr25_0 = C::value_regs_get(ctx, expr21_0, expr24_0); + let expr26_0: Type = I64; + let expr27_0 = RegMemImm::Reg { reg: expr25_0 }; + let expr28_0 = constructor_add(ctx, expr26_0, expr18_0, &expr27_0)?; + let expr29_0 = C::value_regs(ctx, expr23_0, expr28_0); + return Some(expr29_0); + } &Opcode::Band => { let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); @@ -1787,7 +1870,7 @@ pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option(ctx: &mut C, arg0: Inst) -> Option { + debug_assert!(!tyenv.errors.is_empty()); + return; + } + Some(t) => t, + }; let term = &self.terms[term.index()]; if !term.has_constructor() { tyenv.report_error( From 7a568b19b42ddb86da156e5566bbbde98307ccb4 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 5 Nov 2021 15:59:49 -0700 Subject: [PATCH 85/95] ISLE: run `rustfmt` on generated code --- cranelift/codegen/build.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index 5932030fc3..e39feed8af 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -218,9 +218,44 @@ fn rebuild_isle(crate_dir: &std::path::Path) -> Result<(), Box std::io::Result { + use std::io::Write; + + let mut rustfmt = std::process::Command::new("rustfmt") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn()?; + + let mut stdin = rustfmt.stdin.take().unwrap(); + stdin.write_all(code.as_bytes())?; + drop(stdin); + + let mut stdout = rustfmt.stdout.take().unwrap(); + let mut data = vec![]; + stdout.read_to_end(&mut data)?; + + let status = rustfmt.wait()?; + if !status.success() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("`rustfmt` exited with status {}", status), + )); + } + + Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout")) + } } From bbb4949128eff865b9a1408b896194ee9b55b1cc Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 10 Nov 2021 13:21:48 -0800 Subject: [PATCH 86/95] ISLE: use `lower_to_amode` inside `sink_load` implementation --- cranelift/codegen/src/isa/x64/lower/isle.rs | 22 ++++----------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs index 2a4c960301..1fe15ea976 100644 --- a/cranelift/codegen/src/isa/x64/lower/isle.rs +++ b/cranelift/codegen/src/isa/x64/lower/isle.rs @@ -8,13 +8,12 @@ use super::{ is_mergeable_load, lower_to_amode, AluRmiROpcode, Inst as MInst, OperandSize, Reg, RegMemImm, Writable, }; +use crate::isa::x64::inst::args::SyntheticAmode; use crate::isa::x64::settings as x64_settings; use crate::{ ir::{immediates::*, types::*, Inst, InstructionData, Opcode, Value, ValueList}, isa::x64::inst::{ - args::{ - Amode, Avx512Opcode, CmpOpcode, ExtMode, Imm8Reg, RegMem, ShiftKind, SseOpcode, CC, - }, + args::{Avx512Opcode, CmpOpcode, ExtMode, Imm8Reg, RegMem, ShiftKind, SseOpcode, CC}, x64_map_regs, RegMapper, }, machinst::{get_output_reg, InsnInput, InsnOutput, LowerCtx}, @@ -369,22 +368,9 @@ where fn sink_load(&mut self, load: &SinkableLoad) -> RegMemImm { self.lower_ctx.sink_inst(load.inst); - - let flags = self - .lower_ctx - .memflags(load.inst) - .expect("sinkable loads should have memflags"); - - let base = self - .lower_ctx - .put_input_in_regs(load.addr_input.insn, load.addr_input.input) - .only_reg() - .unwrap(); - + let addr = lower_to_amode(self.lower_ctx, load.addr_input, load.offset); RegMemImm::Mem { - addr: Amode::imm_reg(load.offset as u32, base) - .with_flags(flags) - .into(), + addr: SyntheticAmode::Real(addr), } } From b5105c025cfacc1d7eaed9736c510c3b31eaee80 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 10 Nov 2021 15:45:43 -0800 Subject: [PATCH 87/95] MachInst: always rematerialize constants, rather than assign them registers There were a few previous code paths that attempted to handle this, but this new check handles it for all callers. Rematerializing constants, rather than assigning and reusing a register, allows for lower register pressure. --- cranelift/codegen/src/machinst/lower.rs | 32 ++++++++++-- .../filetests/filetests/isa/x64/i128.clif | 50 ++++++++++--------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/cranelift/codegen/src/machinst/lower.rs b/cranelift/codegen/src/machinst/lower.rs index e6e608ec3d..8f65449cde 100644 --- a/cranelift/codegen/src/machinst/lower.rs +++ b/cranelift/codegen/src/machinst/lower.rs @@ -16,8 +16,9 @@ use crate::ir::{ ValueDef, ValueLabelAssignments, ValueLabelStart, }; use crate::machinst::{ - writable_value_regs, ABICallee, BlockIndex, BlockLoweringOrder, LoweredBlock, MachLabel, VCode, - VCodeBuilder, VCodeConstant, VCodeConstantData, VCodeConstants, VCodeInst, ValueRegs, + non_writable_value_regs, writable_value_regs, ABICallee, BlockIndex, BlockLoweringOrder, + LoweredBlock, MachLabel, VCode, VCodeBuilder, VCodeConstant, VCodeConstantData, VCodeConstants, + VCodeInst, ValueRegs, }; use crate::CodegenResult; use alloc::boxed::Box; @@ -1197,7 +1198,32 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> { fn put_value_in_regs(&mut self, val: Value) -> ValueRegs { let val = self.f.dfg.resolve_aliases(val); - log::trace!("put_value_in_reg: val {}", val); + log::trace!("put_value_in_regs: val {}", val); + + // If the value is a constant, then (re)materialize it at each use. This + // lowers register pressure. + if let Some(c) = self + .f + .dfg + .value_def(val) + .inst() + .and_then(|inst| self.get_constant(inst)) + { + let ty = self.f.dfg.value_type(val); + + let regs = self.alloc_tmp(ty); + log::trace!(" -> regs {:?}", regs); + assert!(regs.is_valid()); + + let insts = I::gen_constant(regs, c.into(), ty, |ty| { + self.alloc_tmp(ty).only_reg().unwrap() + }); + for inst in insts { + self.emit(inst); + } + return non_writable_value_regs(regs); + } + let mut regs = self.value_regs[val]; log::trace!(" -> regs {:?}", regs); assert!(regs.is_valid()); diff --git a/cranelift/filetests/filetests/isa/x64/i128.clif b/cranelift/filetests/filetests/isa/x64/i128.clif index 3bb3989d9d..fb49847e92 100644 --- a/cranelift/filetests/filetests/isa/x64/i128.clif +++ b/cranelift/filetests/filetests/isa/x64/i128.clif @@ -697,32 +697,34 @@ block2(v6: i128): return v8 ; check: Block 0: -; check: pushq %rbp -; nextln: movq %rsp, %rbp -; nextln: xorq %rdi, %rdi -; nextln: xorq %rsi, %rsi -; nextln: testb $$1, %dl -; nextln: jnz label1; j label2 +; check: pushq %rbp +; nextln: movq %rsp, %rbp +; nextln: testb $$1, %dl +; nextln: jnz label1; j label2 ; check: Block 1: -; check: movl $$1, %ecx -; nextln: xorq %rax, %rax -; nextln: addq %rcx, %rdi -; nextln: adcq %rax, %rsi -; nextln: movq %rdi, %rax -; nextln: movq %rsi, %rdx -; nextln: movq %rbp, %rsp -; nextln: popq %rbp -; nextln: ret +; check: movl $$0, %edi +; nextln: movl $$0, %esi +; nextln: movl $$1, %ecx +; nextln: movl $$0, %eax +; nextln: addq %rcx, %rdi +; nextln: adcq %rax, %rsi +; nextln: movq %rdi, %rax +; nextln: movq %rsi, %rdx +; nextln: movq %rbp, %rsp +; nextln: popq %rbp +; nextln: ret ; check: Block 2: -; check: movl $$2, %ecx -; nextln: xorq %rax, %rax -; nextln: addq %rcx, %rdi -; nextln: adcq %rax, %rsi -; nextln: movq %rdi, %rax -; nextln: movq %rsi, %rdx -; nextln: movq %rbp, %rsp -; nextln: popq %rbp -; nextln: ret +; check: movl $$0, %edi +; nextln: movl $$0, %esi +; nextln: movl $$2, %ecx +; nextln: movl $$0, %eax +; nextln: addq %rcx, %rdi +; nextln: adcq %rax, %rsi +; nextln: movq %rdi, %rax +; nextln: movq %rsi, %rdx +; nextln: movq %rbp, %rsp +; nextln: popq %rbp +; nextln: ret } From 33fcd6b4a50f2a63a05fd5e707914d904ce41587 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 10 Nov 2021 15:57:58 -0800 Subject: [PATCH 88/95] x64: special case `0` to use `xor` in `Inst::gen_constant` for `i128`s --- cranelift/codegen/src/isa/x64/inst/mod.rs | 34 +++++++++++++------ .../filetests/filetests/isa/x64/i128.clif | 12 +++---- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs index 5d67835941..d011cd2b97 100644 --- a/cranelift/codegen/src/isa/x64/inst/mod.rs +++ b/cranelift/codegen/src/isa/x64/inst/mod.rs @@ -3237,16 +3237,30 @@ impl MachInst for Inst { ) -> SmallVec<[Self; 4]> { let mut ret = SmallVec::new(); if ty == types::I128 { - ret.push(Inst::imm( - OperandSize::Size64, - value as u64, - to_regs.regs()[0], - )); - ret.push(Inst::imm( - OperandSize::Size64, - (value >> 64) as u64, - to_regs.regs()[1], - )); + let lo = value as u64; + let hi = (value >> 64) as u64; + let lo_reg = to_regs.regs()[0]; + let hi_reg = to_regs.regs()[1]; + if lo == 0 { + ret.push(Inst::alu_rmi_r( + OperandSize::Size64, + AluRmiROpcode::Xor, + RegMemImm::reg(lo_reg.to_reg()), + lo_reg, + )); + } else { + ret.push(Inst::imm(OperandSize::Size64, lo, lo_reg)); + } + if hi == 0 { + ret.push(Inst::alu_rmi_r( + OperandSize::Size64, + AluRmiROpcode::Xor, + RegMemImm::reg(hi_reg.to_reg()), + hi_reg, + )); + } else { + ret.push(Inst::imm(OperandSize::Size64, hi, hi_reg)); + } } else { let to_reg = to_regs .only_reg() diff --git a/cranelift/filetests/filetests/isa/x64/i128.clif b/cranelift/filetests/filetests/isa/x64/i128.clif index fb49847e92..5066404fe0 100644 --- a/cranelift/filetests/filetests/isa/x64/i128.clif +++ b/cranelift/filetests/filetests/isa/x64/i128.clif @@ -702,10 +702,10 @@ block2(v6: i128): ; nextln: testb $$1, %dl ; nextln: jnz label1; j label2 ; check: Block 1: -; check: movl $$0, %edi -; nextln: movl $$0, %esi +; check: xorq %rdi, %rdi +; nextln: xorq %rsi, %rsi ; nextln: movl $$1, %ecx -; nextln: movl $$0, %eax +; nextln: xorq %rax, %rax ; nextln: addq %rcx, %rdi ; nextln: adcq %rax, %rsi ; nextln: movq %rdi, %rax @@ -714,10 +714,10 @@ block2(v6: i128): ; nextln: popq %rbp ; nextln: ret ; check: Block 2: -; check: movl $$0, %edi -; nextln: movl $$0, %esi +; check: xorq %rdi, %rdi +; nextln: xorq %rsi, %rsi ; nextln: movl $$2, %ecx -; nextln: movl $$0, %eax +; nextln: xorq %rax, %rax ; nextln: addq %rcx, %rdi ; nextln: adcq %rax, %rsi ; nextln: movq %rdi, %rax From bfbf2f2f493dbd063e36a228f0c16eee925c0b12 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 11 Nov 2021 15:19:28 -0800 Subject: [PATCH 89/95] ISLE: implement x64 lowering for `band_not` in ISLE --- cranelift/codegen/src/isa/x64/inst.isle | 15 ++++ cranelift/codegen/src/isa/x64/lower.isle | 19 ++++ cranelift/codegen/src/isa/x64/lower.rs | 20 +---- .../src/isa/x64/lower/isle/generated_code.rs | 86 ++++++++++++++++--- 4 files changed, 109 insertions(+), 31 deletions(-) diff --git a/cranelift/codegen/src/isa/x64/inst.isle b/cranelift/codegen/src/isa/x64/inst.isle index 29bb6a9003..dc00a84cc4 100644 --- a/cranelift/codegen/src/isa/x64/inst.isle +++ b/cranelift/codegen/src/isa/x64/inst.isle @@ -848,6 +848,21 @@ (rule (punpcklwd src1 src2) (xmm_rm_r $I16X8 (SseOpcode.Punpcklwd) src1 src2)) +;; Helper for creating `andnps` instructions. +(decl andnps (Reg RegMem) Reg) +(rule (andnps src1 src2) + (xmm_rm_r $F32X4 (SseOpcode.Andnps) src1 src2)) + +;; Helper for creating `andnpd` instructions. +(decl andnpd (Reg RegMem) Reg) +(rule (andnpd src1 src2) + (xmm_rm_r $F64X2 (SseOpcode.Andnpd) src1 src2)) + +;; Helper for creating `pandn` instructions. +(decl pandn (Reg RegMem) Reg) +(rule (pandn src1 src2) + (xmm_rm_r $F64X2 (SseOpcode.Pandn) src1 src2)) + ;; Helper for creating `MInst.XmmRmRImm` instructions. (decl xmm_rm_r_imm (SseOpcode Reg RegMem u8 OperandSize) Reg) (rule (xmm_rm_r_imm op src1 src2 imm size) diff --git a/cranelift/codegen/src/isa/x64/lower.isle b/cranelift/codegen/src/isa/x64/lower.isle index 71a5e1c6c5..ee67b81f11 100644 --- a/cranelift/codegen/src/isa/x64/lower.isle +++ b/cranelift/codegen/src/isa/x64/lower.isle @@ -926,3 +926,22 @@ 0x50 (OperandSize.Size32)))) (value_reg (pmuludq x2 (RegMem.Reg y2))))) + +;;;; Rules for `band_not` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Note the flipping of operands below. CLIF specifies +;; +;; band_not(x, y) = and(x, not(y)) +;; +;; while x86 does +;; +;; pandn(x, y) = and(not(x), y) + +(rule (lower (has_type $F32X4 (band_not x y))) + (value_reg (andnps (put_in_reg y) (put_in_reg_mem x)))) + +(rule (lower (has_type $F64X2 (band_not x y))) + (value_reg (andnpd (put_in_reg y) (put_in_reg_mem x)))) + +(rule (lower (has_type (multi_lane _bits _lanes) (band_not x y))) + (value_reg (pandn (put_in_reg y) (put_in_reg_mem x)))) diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs index 1a70a043db..71500f7c7d 100644 --- a/cranelift/codegen/src/isa/x64/lower.rs +++ b/cranelift/codegen/src/isa/x64/lower.rs @@ -1519,7 +1519,8 @@ fn lower_insn_to_regs>( | Opcode::Band | Opcode::Bor | Opcode::Bxor - | Opcode::Imul => { + | Opcode::Imul + | Opcode::BandNot => { unreachable!( "implemented in ISLE: inst = `{}`, type = `{:?}`", ctx.dfg().display_inst(insn), @@ -1527,23 +1528,6 @@ fn lower_insn_to_regs>( ); } - Opcode::BandNot => { - let ty = ty.unwrap(); - debug_assert!(ty.is_vector() && ty.bytes() == 16); - let lhs = input_to_reg_mem(ctx, inputs[0]); - let rhs = put_input_in_reg(ctx, inputs[1]); - let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); - let sse_op = match ty { - types::F32X4 => SseOpcode::Andnps, - types::F64X2 => SseOpcode::Andnpd, - _ => SseOpcode::Pandn, - }; - // Note the flipping of operands: the `rhs` operand is used as the destination instead - // of the `lhs` as in the other bit operations above (e.g. `band`). - ctx.emit(Inst::gen_move(dst, rhs, ty)); - ctx.emit(Inst::xmm_rm_r(sse_op, lhs, dst)); - } - Opcode::Iabs => { let src = input_to_reg_mem(ctx, inputs[0]); let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap(); diff --git a/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs b/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs index adeab6f063..da0932027e 100644 --- a/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs +++ b/cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs @@ -1198,6 +1198,39 @@ pub fn constructor_punpcklwd(ctx: &mut C, arg0: Reg, arg1: &RegMem) return Some(expr2_0); } +// Generated as internal constructor for term andnps. +pub fn constructor_andnps(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 853. + let expr0_0: Type = F32X4; + let expr1_0 = SseOpcode::Andnps; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term andnpd. +pub fn constructor_andnpd(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 858. + let expr0_0: Type = F64X2; + let expr1_0 = SseOpcode::Andnpd; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + +// Generated as internal constructor for term pandn. +pub fn constructor_pandn(ctx: &mut C, arg0: Reg, arg1: &RegMem) -> Option { + let pattern0_0 = arg0; + let pattern1_0 = arg1; + // Rule at src/isa/x64/inst.isle line 863. + let expr0_0: Type = F64X2; + let expr1_0 = SseOpcode::Pandn; + let expr2_0 = constructor_xmm_rm_r(ctx, expr0_0, &expr1_0, pattern0_0, pattern1_0)?; + return Some(expr2_0); +} + // Generated as internal constructor for term xmm_rm_r_imm. pub fn constructor_xmm_rm_r_imm( ctx: &mut C, @@ -1212,7 +1245,7 @@ pub fn constructor_xmm_rm_r_imm( let pattern2_0 = arg2; let pattern3_0 = arg3; let pattern4_0 = arg4; - // Rule at src/isa/x64/inst.isle line 853. + // Rule at src/isa/x64/inst.isle line 868. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmRmRImm { @@ -1240,7 +1273,7 @@ pub fn constructor_palignr( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 865. + // Rule at src/isa/x64/inst.isle line 880. let expr0_0 = SseOpcode::Palignr; let expr1_0 = constructor_xmm_rm_r_imm( ctx, &expr0_0, pattern0_0, pattern1_0, pattern2_0, pattern3_0, @@ -1258,7 +1291,7 @@ pub fn constructor_pshufd( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 874. + // Rule at src/isa/x64/inst.isle line 889. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = C::writable_reg_to_reg(ctx, expr1_0); @@ -1283,7 +1316,7 @@ pub fn constructor_xmm_unary_rm_r( ) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 887. + // Rule at src/isa/x64/inst.isle line 902. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmUnaryRmR { @@ -1299,7 +1332,7 @@ pub fn constructor_xmm_unary_rm_r( // Generated as internal constructor for term pmovsxbw. pub fn constructor_pmovsxbw(ctx: &mut C, arg0: &RegMem) -> Option { let pattern0_0 = arg0; - // Rule at src/isa/x64/inst.isle line 894. + // Rule at src/isa/x64/inst.isle line 909. let expr0_0 = SseOpcode::Pmovsxbw; let expr1_0 = constructor_xmm_unary_rm_r(ctx, &expr0_0, pattern0_0)?; return Some(expr1_0); @@ -1308,7 +1341,7 @@ pub fn constructor_pmovsxbw(ctx: &mut C, arg0: &RegMem) -> Option(ctx: &mut C, arg0: &RegMem) -> Option { let pattern0_0 = arg0; - // Rule at src/isa/x64/inst.isle line 899. + // Rule at src/isa/x64/inst.isle line 914. let expr0_0 = SseOpcode::Pmovzxbw; let expr1_0 = constructor_xmm_unary_rm_r(ctx, &expr0_0, pattern0_0)?; return Some(expr1_0); @@ -1324,7 +1357,7 @@ pub fn constructor_xmm_rm_r_evex( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 904. + // Rule at src/isa/x64/inst.isle line 919. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmRmREvex { @@ -1342,7 +1375,7 @@ pub fn constructor_xmm_rm_r_evex( pub fn constructor_vpmullq(ctx: &mut C, arg0: &RegMem, arg1: Reg) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 916. + // Rule at src/isa/x64/inst.isle line 931. let expr0_0 = Avx512Opcode::Vpmullq; let expr1_0 = constructor_xmm_rm_r_evex(ctx, &expr0_0, pattern0_0, pattern1_0)?; return Some(expr1_0); @@ -1358,7 +1391,7 @@ pub fn constructor_xmm_rmi_reg( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 923. + // Rule at src/isa/x64/inst.isle line 938. let expr0_0: Type = I8X16; let expr1_0 = C::temp_writable_reg(ctx, expr0_0); let expr2_0 = MInst::XmmRmiReg { @@ -1376,7 +1409,7 @@ pub fn constructor_xmm_rmi_reg( pub fn constructor_psllq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 933. + // Rule at src/isa/x64/inst.isle line 948. let expr0_0 = SseOpcode::Psllq; let expr1_0 = constructor_xmm_rmi_reg(ctx, &expr0_0, pattern0_0, pattern1_0)?; return Some(expr1_0); @@ -1386,7 +1419,7 @@ pub fn constructor_psllq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) - pub fn constructor_psrlq(ctx: &mut C, arg0: Reg, arg1: &RegMemImm) -> Option { let pattern0_0 = arg0; let pattern1_0 = arg1; - // Rule at src/isa/x64/inst.isle line 938. + // Rule at src/isa/x64/inst.isle line 953. let expr0_0 = SseOpcode::Psrlq; let expr1_0 = constructor_xmm_rmi_reg(ctx, &expr0_0, pattern0_0, pattern1_0)?; return Some(expr1_0); @@ -1404,7 +1437,7 @@ pub fn constructor_mul_hi( let pattern1_0 = arg1; let pattern2_0 = arg2; let pattern3_0 = arg3; - // Rule at src/isa/x64/inst.isle line 945. + // Rule at src/isa/x64/inst.isle line 960. let expr0_0 = C::temp_writable_reg(ctx, pattern0_0); let expr1_0 = C::temp_writable_reg(ctx, pattern0_0); let expr2_0 = C::operand_size_of_type(ctx, pattern0_0); @@ -1433,7 +1466,7 @@ pub fn constructor_mulhi_u( let pattern0_0 = arg0; let pattern1_0 = arg1; let pattern2_0 = arg2; - // Rule at src/isa/x64/inst.isle line 961. + // Rule at src/isa/x64/inst.isle line 976. let expr0_0: bool = false; let expr1_0 = constructor_mul_hi(ctx, pattern0_0, expr0_0, pattern1_0, pattern2_0)?; return Some(expr1_0); @@ -1800,6 +1833,15 @@ pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 940. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_0); + let expr2_0 = constructor_andnps(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } _ => {} } } @@ -1839,6 +1881,15 @@ pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 943. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_0); + let expr2_0 = constructor_andnpd(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } _ => {} } } @@ -3010,6 +3061,15 @@ pub fn constructor_lower(ctx: &mut C, arg0: Inst) -> Option { + let (pattern7_0, pattern7_1) = C::unpack_value_array_2(ctx, &pattern5_1); + // Rule at src/isa/x64/lower.isle line 946. + let expr0_0 = C::put_in_reg(ctx, pattern7_1); + let expr1_0 = C::put_in_reg_mem(ctx, pattern7_0); + let expr2_0 = constructor_pandn(ctx, expr0_0, &expr1_0)?; + let expr3_0 = C::value_reg(ctx, expr2_0); + return Some(expr3_0); + } _ => {} } } From b6fd3d0c40c9470ee0d84c643505f2cd439f4bdc Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 15 Nov 2021 09:25:29 -0800 Subject: [PATCH 90/95] Remove extra, unused `Cargo.lock` --- cranelift/isle/Cargo.lock | 535 -------------------------------------- 1 file changed, 535 deletions(-) delete mode 100644 cranelift/isle/Cargo.lock diff --git a/cranelift/isle/Cargo.lock b/cranelift/isle/Cargo.lock deleted file mode 100644 index 3e8e8cb497..0000000000 --- a/cranelift/isle/Cargo.lock +++ /dev/null @@ -1,535 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - -[[package]] -name = "arbitrary" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577b08a4acd7b99869f863c50011b01eb73424ccc798ecd996f2e24817adfca7" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "2.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap 0.11.0", - "unicode-width", - "vec_map", -] - -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "log", -] - -[[package]] -name = "gimli" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "is_ci" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" - -[[package]] -name = "isle" -version = "0.1.0" -dependencies = [ - "log", - "miette", - "thiserror", -] - -[[package]] -name = "isle-fuzz" -version = "0.0.0" -dependencies = [ - "env_logger 0.9.0", - "isle", - "libfuzzer-sys", - "log", -] - -[[package]] -name = "islec" -version = "0.1.0" -dependencies = [ - "env_logger 0.8.4", - "isle", - "log", - "miette", - "structopt", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "miette" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "024831248cacc3305f5bb33d9daf9df54d6d95bf462c58f388845a17388c47fe" -dependencies = [ - "atty", - "backtrace", - "miette-derive", - "once_cell", - "owo-colors", - "supports-color", - "supports-hyperlinks", - "supports-unicode", - "term_size", - "textwrap 0.14.2", - "thiserror", -] - -[[package]] -name = "miette-derive" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074acd9c89172a516def5d82b8d90fb724fecba9dcae6fcdd88a75689601349f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "object" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "owo-colors" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a61765925aec40abdb23812a3a1a01fafc6ffb9da22768b2ce665a9e84e527c" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "smawk" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "supports-color" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f5b0f9e689dd52e27228469dd68b7416b60d75b7571ae9060a5f4c50048fee" -dependencies = [ - "atty", - "is_ci", -] - -[[package]] -name = "supports-hyperlinks" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" -dependencies = [ - "atty", -] - -[[package]] -name = "supports-unicode" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" -dependencies = [ - "atty", -] - -[[package]] -name = "syn" -version = "1.0.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" -dependencies = [ - "smawk", - "unicode-linebreak", - "unicode-width", -] - -[[package]] -name = "thiserror" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-linebreak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" -dependencies = [ - "regex", -] - -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" From 4a34d2c55b93d2f1a6e60473e651eed883196976 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 15 Nov 2021 11:14:06 -0800 Subject: [PATCH 91/95] Remove old ISLE-generated file --- cranelift/codegen/src/isle.rs | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 cranelift/codegen/src/isle.rs diff --git a/cranelift/codegen/src/isle.rs b/cranelift/codegen/src/isle.rs deleted file mode 100644 index f8794716b9..0000000000 --- a/cranelift/codegen/src/isle.rs +++ /dev/null @@ -1,22 +0,0 @@ -// GENERATED BY ISLE. DO NOT EDIT! -// -// Generated automatically from the instruction-selection DSL code in: -// - src/clif.isle - -#![allow(dead_code, unreachable_code, unreachable_patterns)] -#![allow(unused_imports, unused_variables, non_snake_case)] - -use super::*; // Pulls in all external types. - -/// Context during lowering: an implementation of this trait -/// must be provided with all external constructors and extractors. -/// A mutable borrow is passed along through all lowering logic. -pub trait Context { - fn value_list_slice(&mut self, arg0: &ValueList) -> (ValueSlice,); - fn unwrap_head_value_list_1(&mut self, arg0: &ValueList) -> (Value, ValueSlice,); - fn unwrap_head_value_list_2(&mut self, arg0: &ValueList) -> (Value, Value, ValueSlice,); - fn pack_value_array_2(&mut self, arg0: Value, arg1: Value) -> (ValueArray2,); - fn unpack_value_array_2(&mut self, arg0: &ValueArray2) -> (Value, Value,); - fn pack_value_array_3(&mut self, arg0: Value, arg1: Value, arg2: Value) -> (ValueArray3,); - fn unpack_value_array_3(&mut self, arg0: &ValueArray3) -> (Value, Value, Value,); -} From f27b357702762ccd51494e2a86dd5d57ac0eaf5b Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 15 Nov 2021 11:16:24 -0800 Subject: [PATCH 92/95] ISLE: update repo in Cargo.toml --- cranelift/isle/isle/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 14d709ef07..501f75ba1a 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" license = "Apache-2.0 WITH LLVM-exception" name = "isle" readme = "../README.md" -repository = "https://github.com/cfallin.isle" +repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/isle" version = "0.1.0" [dependencies] From 6164ba38145f9f2b6b12a44d5e12c2aadcd580e4 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 15 Nov 2021 11:17:31 -0800 Subject: [PATCH 93/95] ISLE: match cranelift version --- Cargo.lock | 2 +- cranelift/codegen/Cargo.toml | 2 +- cranelift/isle/isle/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e6102c16a..5b5e8d40c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1431,7 +1431,7 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" [[package]] name = "isle" -version = "0.1.0" +version = "0.78.0" dependencies = [ "log", "miette", diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index 85a7dc3789..86306d7f5c 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -39,7 +39,7 @@ criterion = "0.3" [build-dependencies] cranelift-codegen-meta = { path = "meta", version = "0.78.0" } -isle = { path = "../isle/isle", version = "0.1.0", optional = true } +isle = { path = "../isle/isle", version = "0.78.0", optional = true } miette = { version = "3", features = ["fancy"] } [features] diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 501f75ba1a..c57c6f43dc 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -6,7 +6,7 @@ license = "Apache-2.0 WITH LLVM-exception" name = "isle" readme = "../README.md" repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/isle" -version = "0.1.0" +version = "0.78.0" [dependencies] log = "0.4" From 48ef2c0b844936d218f05df3799f28ba98114142 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 15 Nov 2021 11:26:41 -0800 Subject: [PATCH 94/95] Clarify comment about `force_graphical` in `miette` --- cranelift/codegen/build.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index e39feed8af..e4a287acb8 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -139,8 +139,9 @@ fn rebuild_isle(crate_dir: &std::path::Path) -> Result<(), Box Date: Mon, 15 Nov 2021 14:53:51 -0800 Subject: [PATCH 95/95] Update `miette` graphical-forcing comment to reference upstream issue --- cranelift/codegen/build.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index e4a287acb8..47b63c50a1 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -139,9 +139,8 @@ fn rebuild_isle(crate_dir: &std::path::Path) -> Result<(), Box