From 9dbb8c25c5894cf569163263c35b479db684ed03 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Wed, 23 Feb 2022 13:15:27 -0800 Subject: [PATCH] Implicit type conversions in ISLE (#3807) Add support for implicit type conversions to ISLE. This feature allows the DSL user to register to the compiler that a particular term (used as a constructor or extractor) converts from one type to another. The compiler will then *automatically* insert this term whenever a type mismatch involving that specific pair of types occurs. This significantly cleans up many uses of the ISLE DSL. For example, when defining the compiler backends, we often have newtypes like `Gpr` around `Reg` (signifying a particular type of register); we can define a conversion from Gpr to Reg automatically. Conversions can also have side-effects, as long as these side-effects are idempotent. For example, `put_value_in_reg` in a compiler backend has the effect of marking the value as used, causing codegen to produce it, and assigns a register to the value; but multiple invocations of this will return the same register for the same value. Thus it is safe to use it as an implicit conversion that may be invoked multiple times. This is documented in the ISLE-Cranelift integration document. This PR also adds some testing infrastructure to the ISLE compiler, checking that "pass" tests pass through the DSL compiler, "fail" tests do not, and "link" tests are able to generate code and link that code with corresponding Rust code. --- Cargo.lock | 1 + cranelift/docs/isle-integration.md | 56 ++++++ cranelift/isle/docs/language-reference.md | 60 ++++++ cranelift/isle/isle/Cargo.toml | 3 + cranelift/isle/isle/build.rs | 33 ++++ .../isle_examples/fail/bad_converters.isle | 10 + .../fail/converter_extractor_constructor.isle | 29 +++ .../isle_examples/fail}/error1.isle | 0 .../isle_examples/link}/test.isle | 0 .../isle_examples/link}/test_main.rs | 4 +- .../pass/construct_and_extract.isle} | 0 .../isle/isle_examples/pass/conversions.isle | 28 +++ .../isle_examples/pass}/let.isle | 0 .../isle_examples/pass}/nodebug.isle | 0 .../isle_examples/pass}/test2.isle | 0 .../isle_examples/pass}/test3.isle | 0 .../isle_examples/pass}/test4.isle | 0 .../isle_examples/pass}/tutorial.isle | 0 cranelift/isle/isle/src/ast.rs | 21 ++ cranelift/isle/isle/src/parser.rs | 14 ++ cranelift/isle/isle/src/sema.rs | 186 +++++++++++++++++- cranelift/isle/isle/tests/run_tests.rs | 54 +++++ cranelift/isle/islec/src/main.rs | 2 +- 23 files changed, 492 insertions(+), 9 deletions(-) create mode 100644 cranelift/isle/isle/build.rs create mode 100644 cranelift/isle/isle/isle_examples/fail/bad_converters.isle create mode 100644 cranelift/isle/isle/isle_examples/fail/converter_extractor_constructor.isle rename cranelift/isle/{isle_examples => isle/isle_examples/fail}/error1.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/link}/test.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/link}/test_main.rs (59%) rename cranelift/isle/{isle_examples/construct-and-extract.isle => isle/isle_examples/pass/construct_and_extract.isle} (100%) create mode 100644 cranelift/isle/isle/isle_examples/pass/conversions.isle rename cranelift/isle/{isle_examples => isle/isle_examples/pass}/let.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/pass}/nodebug.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/pass}/test2.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/pass}/test3.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/pass}/test4.isle (100%) rename cranelift/isle/{isle_examples => isle/isle_examples/pass}/tutorial.isle (100%) create mode 100644 cranelift/isle/isle/tests/run_tests.rs diff --git a/Cargo.lock b/Cargo.lock index d4b8e7c343..cce701b9e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -614,6 +614,7 @@ version = "0.81.0" dependencies = [ "log", "miette", + "tempfile", "thiserror", ] diff --git a/cranelift/docs/isle-integration.md b/cranelift/docs/isle-integration.md index 65de395097..040407b590 100644 --- a/cranelift/docs/isle-integration.md +++ b/cranelift/docs/isle-integration.md @@ -233,3 +233,59 @@ instructions with flags-consuming instructions, ensuring that no errant instructions are ever inserted between our flags-using instructions, clobbering their flags. See `with_flags`, `ProducesFlags`, and `ConsumesFlags` inside `cranelift/codegen/src/prelude.isle` for details. + +## Implicit type conversions + +ISLE supports implicit type conversions, and we will make use of these +where possible to simplify the lowering rules. For example, we have +`Value` and `ValueRegs`; the former denotes SSA values in CLIF, and +the latter denotes the register or registers that hold that value in +lowered code. Prior to introduction of implicit type conversions, we +had many occurrences of expressions like `(value_regs r1 r2)` or +`(value_reg r)`. Given that we have already defined a term +`value_reg`, we can define a conversion such as + +```lisp + (convert Reg ValueRegs value_reg) +``` + +and the ISLE compiler will automatically insert it where the types +imply that it is necessary. When properly defined, converters allow us +to write rules like + +```lisp + (rule (lower (has_type (fits_in_64 ty) + (iadd x y))) + (add ty x y)) +``` + +### Implicit type conversions and side-effects + +While implicit conversions are very convenient, we take care to manage +how they introduce invisible side-effects. + +Particularly important here is the fact that implicit conversions can +occur more than once. For example, if we define `(convert Value Reg +put_value_in_reg)`, and we emit an instruction `(add a a)` (say, to +double the value of `a`) where `a` is a `Value`, then we will invoke +`put_value_in_reg` twice. + +If this were an external constructor that performed some unique action +per invocation, for example allocating a fresh register, then this +would not be desirable. So we follow a convention when defining +implicit type conversions: the conversion must be *idempotent*, i.e., +must return the same value if invoked more than +once. `put_value_in_reg` in particular already has this property, so +we are safe to use it as an implicit converter. + +Note that this condition is *not* the same as saying that the +converter cannot have side-effects. It is perfectly fine for this to +be the case, and can add significant convenience, even. The small loss +in efficiency from invoking the converter twice is tolerable, and we +could optimize it later if we needed to do so. + +Even given this, there may be times where it is still clearer to be +explicit. For example, sinking a load is an explicit and important +detail of some lowering patterns; while we could define an implicit +conversion from `SinkableLoad` to `Reg`, it is probably better for now +to retain the `(sink_load ...)` form for clarity. diff --git a/cranelift/isle/docs/language-reference.md b/cranelift/isle/docs/language-reference.md index e3336787fa..e7e37feb8d 100644 --- a/cranelift/isle/docs/language-reference.md +++ b/cranelift/isle/docs/language-reference.md @@ -862,6 +862,66 @@ which will, for example, expand a pattern `(A (subterm ...) _)` into the arguments to `A` are substituted into the extractor body and then this body is inlined. +#### Implicit Type Conversions + +For convenience, ISLE allows the program to associate terms with pairs +of types, so that type mismatches are *automatically resolved* by +inserting that term. + +For example, if one is writing a rule such as + +```lisp + (decl u_to_v (U) V) + (rule ...) + + (decl MyTerm (T) V) + (rule (MyTerm t) + (u_to_v t)) +``` + +the `(u_to_v t)` term would not typecheck given the ISLE language +functionality that we have seen so far, because it expects a `U` for +its argument but `t` has type `T`. However, if we define + + +```lisp + (convert T U t_to_u) + + ;; For the above to be valid, `t_to_u` should be declared with the + ;; signature: + (decl t_to_u (T) U) + (rule ...) +``` + +then the DSL compiler will implicitly understand the above `MyTerm` rule as: + +```lisp + (rule (MyTerm t) + (u_to_v (t_to_u t))) +``` + +This also works in the extractor position: for example, if one writes + +```lisp + (decl defining_instruction (Inst) Value) + (extern extractor definining_instruction ...) + + (decl iadd (Value Value) Inst) + + (rule (lower (iadd (iadd a b) c)) + ...)) + + (convert Inst Value defining_instruction) +``` + +then the `(iadd (iadd a b) c)` form will be implicitly handled like +`(iadd (defining_instruction (iadd a b)) c)`. Note that the conversion +insertion needs to have local type context in order to find the right +converter: so, for example, it cannot infer a target type from a +pattern where just a variable binding occurs, even if the variable is +used in some typed context on the right-hand side. Instead, the +"inner" and "outer" types have to come from explicitly typed terms. + #### Summary: Terms, Constructors, and Extractors We start with a `term`, which is just a schema for data: diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 0e86701cc4..fab0c35877 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -12,3 +12,6 @@ version = "0.81.0" log = "0.4" miette = "3.0.0" thiserror = "1.0.29" + +[dev-dependencies] +tempfile = "3" diff --git a/cranelift/isle/isle/build.rs b/cranelift/isle/isle/build.rs new file mode 100644 index 0000000000..d3081c8536 --- /dev/null +++ b/cranelift/isle/isle/build.rs @@ -0,0 +1,33 @@ +use std::fmt::Write; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + let out_dir = std::path::PathBuf::from( + std::env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"), + ); + + let mut out = String::new(); + + emit_tests(&mut out, "isle_examples/pass", "run_pass"); + emit_tests(&mut out, "isle_examples/fail", "run_fail"); + emit_tests(&mut out, "isle_examples/link", "run_link"); + + let output = out_dir.join("isle_tests.rs"); + std::fs::write(output, out).unwrap(); +} + +fn emit_tests(out: &mut String, dir_name: &str, runner_func: &str) { + for test_file in std::fs::read_dir(dir_name).unwrap() { + let test_file = test_file.unwrap().file_name().into_string().unwrap(); + if !test_file.ends_with(".isle") { + continue; + } + let test_file_base = test_file.replace(".isle", ""); + + writeln!(out, "#[test]").unwrap(); + writeln!(out, "fn test_{}_{}() {{", runner_func, test_file_base).unwrap(); + writeln!(out, " {}(\"{}/{}\");", runner_func, dir_name, test_file).unwrap(); + writeln!(out, "}}").unwrap(); + } +} diff --git a/cranelift/isle/isle/isle_examples/fail/bad_converters.isle b/cranelift/isle/isle/isle_examples/fail/bad_converters.isle new file mode 100644 index 0000000000..027dd088dc --- /dev/null +++ b/cranelift/isle/isle/isle_examples/fail/bad_converters.isle @@ -0,0 +1,10 @@ +(type T (enum)) +(type U (enum)) + +(decl t_to_u_1 (T) U) +(decl t_to_u_2 (T) U) + +(convert T U t_to_u_1) +(convert T U t_to_u_2) + +(convert T Undef undefined_term) diff --git a/cranelift/isle/isle/isle_examples/fail/converter_extractor_constructor.isle b/cranelift/isle/isle/isle_examples/fail/converter_extractor_constructor.isle new file mode 100644 index 0000000000..b204d813e5 --- /dev/null +++ b/cranelift/isle/isle/isle_examples/fail/converter_extractor_constructor.isle @@ -0,0 +1,29 @@ +(type T (enum)) +(type U (enum)) +(type V (enum)) + +(convert T U t_to_u) +(convert U V u_to_v) + +(decl t_to_u (T) U) +(decl u_to_v (U) V) +(decl t_to_v (T) V) +(decl v_to_t (V) T) + +(extern constructor t_to_u t_to_u) +(extern extractor u_to_v u_to_v) +(extern constructor t_to_v t_to_v_ctor) +(extern extractor t_to_v t_to_v_etor) +(extern extractor v_to_t v_to_u_etor) + +;; We should fail to find a converter here. Given only the types, we +;; might expect u_to_v to be implicitly inserted in the RHS, but +;; u_to_v has only an extractor, not a constructor, associated. +(decl Test1 (U) V) +(rule (Test1 u) u) + +;; We should fail to find a converter here. Given only the types, we +;; might expect t_to_u to be implicitly inserted in the LHS, but t_to_u +;; has only a constructor, not an extractor, associated. +(decl Test2 (U) V) +(rule (Test2 (v_to_t v)) v) diff --git a/cranelift/isle/isle_examples/error1.isle b/cranelift/isle/isle/isle_examples/fail/error1.isle similarity index 100% rename from cranelift/isle/isle_examples/error1.isle rename to cranelift/isle/isle/isle_examples/fail/error1.isle diff --git a/cranelift/isle/isle_examples/test.isle b/cranelift/isle/isle/isle_examples/link/test.isle similarity index 100% rename from cranelift/isle/isle_examples/test.isle rename to cranelift/isle/isle/isle_examples/link/test.isle diff --git a/cranelift/isle/isle_examples/test_main.rs b/cranelift/isle/isle/isle_examples/link/test_main.rs similarity index 59% rename from cranelift/isle/isle_examples/test_main.rs rename to cranelift/isle/isle/isle_examples/link/test_main.rs index 5bec55a798..733e2a2019 100644 --- a/cranelift/isle/isle_examples/test_main.rs +++ b/cranelift/isle/isle/isle_examples/link/test_main.rs @@ -2,8 +2,8 @@ mod test; 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 get_input(&mut self, x: u32) -> Option { + Some(test::A::A1 { x: x + 1 }) } } diff --git a/cranelift/isle/isle_examples/construct-and-extract.isle b/cranelift/isle/isle/isle_examples/pass/construct_and_extract.isle similarity index 100% rename from cranelift/isle/isle_examples/construct-and-extract.isle rename to cranelift/isle/isle/isle_examples/pass/construct_and_extract.isle diff --git a/cranelift/isle/isle/isle_examples/pass/conversions.isle b/cranelift/isle/isle/isle_examples/pass/conversions.isle new file mode 100644 index 0000000000..1b3e6bfb13 --- /dev/null +++ b/cranelift/isle/isle/isle_examples/pass/conversions.isle @@ -0,0 +1,28 @@ +(type T (enum (A) (B))) +(type U (enum (C) (D))) +(type V (enum (E) (F))) +(type u32 (primitive u32)) + +(convert T U t_to_u) +(convert U T u_to_t) +(convert U V u_to_v) + +(decl t_to_u (T) U) +(decl u_to_t (U) T) +(decl u_to_v (U) V) + +(rule (t_to_u (T.A)) (U.C)) +(rule (t_to_u (T.B)) (U.D)) + +(rule (u_to_t (U.C)) (T.A)) +(rule (u_to_t (U.D)) (T.B)) + +(rule (u_to_v (U.C)) (V.E)) +(rule (u_to_v (U.D)) (V.F)) + +(extern extractor u_to_t u_to_t) + + +(decl X (T U) V) +(rule (X (U.C) (U.D)) + (U.D)) diff --git a/cranelift/isle/isle_examples/let.isle b/cranelift/isle/isle/isle_examples/pass/let.isle similarity index 100% rename from cranelift/isle/isle_examples/let.isle rename to cranelift/isle/isle/isle_examples/pass/let.isle diff --git a/cranelift/isle/isle_examples/nodebug.isle b/cranelift/isle/isle/isle_examples/pass/nodebug.isle similarity index 100% rename from cranelift/isle/isle_examples/nodebug.isle rename to cranelift/isle/isle/isle_examples/pass/nodebug.isle diff --git a/cranelift/isle/isle_examples/test2.isle b/cranelift/isle/isle/isle_examples/pass/test2.isle similarity index 100% rename from cranelift/isle/isle_examples/test2.isle rename to cranelift/isle/isle/isle_examples/pass/test2.isle diff --git a/cranelift/isle/isle_examples/test3.isle b/cranelift/isle/isle/isle_examples/pass/test3.isle similarity index 100% rename from cranelift/isle/isle_examples/test3.isle rename to cranelift/isle/isle/isle_examples/pass/test3.isle diff --git a/cranelift/isle/isle_examples/test4.isle b/cranelift/isle/isle/isle_examples/pass/test4.isle similarity index 100% rename from cranelift/isle/isle_examples/test4.isle rename to cranelift/isle/isle/isle_examples/pass/test4.isle diff --git a/cranelift/isle/isle_examples/tutorial.isle b/cranelift/isle/isle/isle_examples/pass/tutorial.isle similarity index 100% rename from cranelift/isle/isle_examples/tutorial.isle rename to cranelift/isle/isle/isle_examples/pass/tutorial.isle diff --git a/cranelift/isle/isle/src/ast.rs b/cranelift/isle/isle/src/ast.rs index dd69628eed..04bb648106 100644 --- a/cranelift/isle/isle/src/ast.rs +++ b/cranelift/isle/isle/src/ast.rs @@ -21,6 +21,7 @@ pub enum Def { Extractor(Extractor), Decl(Decl), Extern(Extern), + Converter(Converter), } /// An identifier -- a variable, term symbol, or type. @@ -419,3 +420,23 @@ pub enum ArgPolarity { /// *from* the extractor op. Output, } + +/// An implicit converter: the given term, which must have type +/// (inner_ty) -> outer_ty, is used either in extractor or constructor +/// position as appropriate when a type mismatch with the given pair +/// of types would otherwise occur. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Converter { + /// The term name. + pub term: Ident, + /// The "inner type": the type to convert *from*, on the + /// right-hand side, or *to*, on the left-hand side. Must match + /// the singular argument type of the term. + pub inner_ty: Ident, + /// The "outer type": the type to convert *to*, on the right-hand + /// side, or *from*, on the left-hand side. Must match the ret_ty + /// of the term. + pub outer_ty: Ident, + /// The position of this converter decl. + pub pos: Pos, +} diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 1f8de5ccff..8ddc43dcf5 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -140,6 +140,7 @@ impl<'a> Parser<'a> { "rule" => Def::Rule(self.parse_rule()?), "extractor" => Def::Extractor(self.parse_etor()?), "extern" => Def::Extern(self.parse_extern()?), + "convert" => Def::Converter(self.parse_converter()?), s => { return Err(self.error(pos, format!("Unexpected identifier: {}", s))); } @@ -515,4 +516,17 @@ impl<'a> Parser<'a> { self.rparen()?; Ok(LetDef { var, ty, val, pos }) } + + fn parse_converter(&mut self) -> Result { + let pos = self.pos(); + let inner_ty = self.parse_ident()?; + let outer_ty = self.parse_ident()?; + let term = self.parse_ident()?; + Ok(Converter { + term, + inner_ty, + outer_ty, + pos, + }) + } } diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 646f0baf74..3dff008b5b 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -14,8 +14,10 @@ //! the opposite). use crate::ast; +use crate::ast::Ident; use crate::error::*; use crate::lexer::Pos; +use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::sync::Arc; @@ -191,6 +193,11 @@ pub struct TermEnv { /// /// This is indexed by `RuleId`. pub rules: Vec, + + /// Map from (inner_ty, outer_ty) pairs to term IDs, giving the + /// defined implicit type-converter terms we can try to use to fit + /// types together. + pub converters: BTreeMap<(TypeId, TypeId), TermId>, } /// A term. @@ -775,6 +782,7 @@ impl TermEnv { terms: vec![], term_map: BTreeMap::new(), rules: vec![], + converters: BTreeMap::new(), }; env.collect_term_sigs(tyenv, defs); @@ -783,6 +791,8 @@ impl TermEnv { env.collect_constructors(tyenv, defs); env.collect_extractor_templates(tyenv, defs); tyenv.return_errors()?; + env.collect_converters(tyenv, defs); + tyenv.return_errors()?; env.collect_rules(tyenv, defs); env.check_for_undefined_decls(tyenv, defs); env.check_for_expr_terms_without_constructors(tyenv, defs); @@ -1088,6 +1098,72 @@ impl TermEnv { } } + fn collect_converters(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { + for def in &defs.defs { + match def { + &ast::Def::Converter(ast::Converter { + ref term, + ref inner_ty, + ref outer_ty, + pos, + }) => { + let inner_ty_sym = tyenv.intern_mut(inner_ty); + let inner_ty_id = match tyenv.type_map.get(&inner_ty_sym) { + Some(ty) => *ty, + None => { + tyenv.report_error( + inner_ty.1, + format!("Unknown inner type for converter: '{}'", inner_ty.0), + ); + continue; + } + }; + + let outer_ty_sym = tyenv.intern_mut(outer_ty); + let outer_ty_id = match tyenv.type_map.get(&outer_ty_sym) { + Some(ty) => *ty, + None => { + tyenv.report_error( + outer_ty.1, + format!("Unknown outer type for converter: '{}'", outer_ty.0), + ); + continue; + } + }; + + let term_sym = tyenv.intern_mut(term); + let term_id = match self.term_map.get(&term_sym) { + Some(term_id) => *term_id, + None => { + tyenv.report_error( + term.1, + format!("Unknown term for converter: '{}'", term.0), + ); + continue; + } + }; + + match self.converters.entry((inner_ty_id, outer_ty_id)) { + Entry::Vacant(v) => { + v.insert(term_id); + } + Entry::Occupied(_) => { + tyenv.report_error( + pos, + format!( + "Converter already exists for this type pair: '{}', '{}'", + inner_ty.0, outer_ty.0 + ), + ); + continue; + } + } + } + _ => {} + } + } + } + fn collect_rules(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) { for def in &defs.defs { match def { @@ -1327,6 +1403,36 @@ impl TermEnv { } } + fn maybe_implicit_convert_pattern( + &self, + tyenv: &mut TypeEnv, + pattern: &ast::Pattern, + inner_ty: TypeId, + outer_ty: TypeId, + ) -> Option { + if let Some(converter_term) = self.converters.get(&(inner_ty, outer_ty)) { + if self.terms[converter_term.index()].has_extractor() { + // This is a little awkward: we have to + // convert back to an Ident, to be + // re-resolved. The pos doesn't matter + // as it shouldn't result in a lookup + // failure. + let converter_term_ident = Ident( + tyenv.syms[self.terms[converter_term.index()].name.index()].clone(), + pattern.pos(), + ); + let expanded_pattern = ast::Pattern::Term { + sym: converter_term_ident, + pos: pattern.pos(), + args: vec![ast::TermArgPattern::Pattern(pattern.clone())], + }; + + return Some(expanded_pattern); + } + } + None + } + fn translate_pattern( &self, tyenv: &mut TypeEnv, @@ -1485,12 +1591,31 @@ impl TermEnv { // Get the return type and arg types. Verify the // expected type of this pattern, if any, against the - // return type of the term. + // return type of the term. Insert an implicit + // converter if needed. 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) => { + // Can we do an implicit type conversion? Look + // up the converter term, if any. If one has + // been registered, and the term has an + // extractor, then build an expanded AST node + // right here and recurse on it. + if let Some(expanded_pattern) = + self.maybe_implicit_convert_pattern(tyenv, pat, ret_ty, expected_ty) + { + return self.translate_pattern( + tyenv, + rule_term, + &expanded_pattern, + Some(expected_ty), + bindings, + /* is_root = */ false, + ); + } + tyenv.report_error( pos, format!( @@ -1685,6 +1810,30 @@ impl TermEnv { } } + fn maybe_implicit_convert_expr( + &self, + tyenv: &mut TypeEnv, + expr: &ast::Expr, + inner_ty: TypeId, + outer_ty: TypeId, + ) -> Option { + // Is there a converter for this type mismatch? + if let Some(converter_term) = self.converters.get(&(inner_ty, outer_ty)) { + if self.terms[converter_term.index()].has_constructor() { + let converter_ident = ast::Ident( + tyenv.syms[self.terms[converter_term.index()].name.index()].clone(), + expr.pos(), + ); + return Some(ast::Expr::Term { + sym: converter_ident, + pos: expr.pos(), + args: vec![expr.clone()], + }); + } + } + None + } + fn translate_expr( &self, tyenv: &mut TypeEnv, @@ -1712,11 +1861,29 @@ impl TermEnv { // Get the return type and arg types. Verify the // expected type of this pattern, if any, against the - // return type of the term. + // return type of the term, and determine whether we + // are doing an implicit conversion. Report an error + // if types don't match and no conversion is possible. let ret_ty = self.terms[tid.index()].ret_ty; - if ret_ty != ty { - 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))); - } + let ty = if ret_ty != ty { + // Is there a converter for this type mismatch? + if let Some(expanded_expr) = + self.maybe_implicit_convert_expr(tyenv, expr, ret_ty, ty) + { + return self.translate_expr(tyenv, &expanded_expr, ty, bindings); + } + + 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))); + + // Keep going, to discover more errors. + ret_ty + } else { + ty + }; // Check that we have the correct argument count. if self.terms[tid.index()].arg_tys.len() != args.len() { @@ -1754,8 +1921,15 @@ impl TermEnv { Some(bv) => bv, }; - // Verify type. + // Verify type. Maybe do an implicit conversion. if bv.ty != ty { + // Is there a converter for this type mismatch? + if let Some(expanded_expr) = + self.maybe_implicit_convert_expr(tyenv, expr, bv.ty, ty) + { + return self.translate_expr(tyenv, &expanded_expr, ty, bindings); + } + tyenv.report_error( pos, format!( diff --git a/cranelift/isle/isle/tests/run_tests.rs b/cranelift/isle/isle/tests/run_tests.rs new file mode 100644 index 0000000000..b328597bd3 --- /dev/null +++ b/cranelift/isle/isle/tests/run_tests.rs @@ -0,0 +1,54 @@ +//! Helper for autogenerated unit tests. + +use cranelift_isle::error::Result; +use cranelift_isle::{compile, lexer, parser}; + +fn build(filename: &str) -> Result { + let lexer = lexer::Lexer::from_files(vec![filename])?; + let defs = parser::parse(lexer)?; + compile::compile(&defs) +} + +pub fn run_pass(filename: &str) { + assert!(build(filename).is_ok()); +} + +pub fn run_fail(filename: &str) { + assert!(build(filename).is_err()); +} + +pub fn run_link(isle_filename: &str) { + let tempdir = tempfile::tempdir().unwrap(); + let code = build(isle_filename).unwrap(); + + let isle_filename_base = std::path::Path::new(isle_filename) + .file_stem() + .unwrap() + .to_str() + .unwrap() + .to_string(); + let isle_generated_code = tempdir + .path() + .to_path_buf() + .join(isle_filename_base.clone() + ".rs"); + std::fs::write(isle_generated_code, code).unwrap(); + + let rust_filename = isle_filename.replace(".isle", "").to_string() + "_main.rs"; + let rust_filename_base = std::path::Path::new(&rust_filename).file_name().unwrap(); + let rust_driver = tempdir.path().to_path_buf().join(&rust_filename_base); + println!("copying {} to {:?}", rust_filename, rust_driver); + std::fs::copy(&rust_filename, &rust_driver).unwrap(); + + let output = tempdir.path().to_path_buf().join("out"); + + let mut rustc = std::process::Command::new("rustc") + .arg(&rust_driver) + .arg("-o") + .arg(output) + .spawn() + .unwrap(); + assert!(rustc.wait().unwrap().success()); +} + +// Generated by build.rs. +include!(concat!(env!("OUT_DIR"), "/isle_tests.rs")); diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs index 1ca37d1c1c..0f75922e8b 100644 --- a/cranelift/isle/islec/src/main.rs +++ b/cranelift/isle/islec/src/main.rs @@ -15,7 +15,7 @@ struct Opts { output: Option, /// The input ISLE DSL source files. - #[structopt(parse(from_os_str))] + #[structopt(parse(from_os_str), required(true))] inputs: Vec, }