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.
This commit is contained in:
@@ -12,3 +12,6 @@ version = "0.81.0"
|
||||
log = "0.4"
|
||||
miette = "3.0.0"
|
||||
thiserror = "1.0.29"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
33
cranelift/isle/isle/build.rs
Normal file
33
cranelift/isle/isle/build.rs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
10
cranelift/isle/isle/isle_examples/fail/bad_converters.isle
Normal file
10
cranelift/isle/isle/isle_examples/fail/bad_converters.isle
Normal file
@@ -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)
|
||||
@@ -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)
|
||||
36
cranelift/isle/isle/isle_examples/fail/error1.isle
Normal file
36
cranelift/isle/isle/isle_examples/fail/error1.isle
Normal file
@@ -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))
|
||||
21
cranelift/isle/isle/isle_examples/link/test.isle
Normal file
21
cranelift/isle/isle/isle_examples/link/test.isle
Normal file
@@ -0,0 +1,21 @@
|
||||
(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)
|
||||
(extern extractor Input get_input) ;; fn get_input<C>(ctx: &mut C, ret: u32) -> Option<(A,)>
|
||||
|
||||
(decl Lower (A) B)
|
||||
|
||||
(rule
|
||||
(Lower (A.A1 sub @ (Input (A.A2 42))))
|
||||
(B.B2 sub))
|
||||
|
||||
(decl Extractor (B) A)
|
||||
(extractor
|
||||
(Extractor x)
|
||||
(A.A2 x))
|
||||
|
||||
(rule
|
||||
(Lower (Extractor b))
|
||||
(B.B1 b))
|
||||
12
cranelift/isle/isle/isle_examples/link/test_main.rs
Normal file
12
cranelift/isle/isle/isle_examples/link/test_main.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
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 main() {
|
||||
test::constructor_Lower(&mut Context, &test::A::A1 { x: 42 });
|
||||
}
|
||||
@@ -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)
|
||||
28
cranelift/isle/isle/isle_examples/pass/conversions.isle
Normal file
28
cranelift/isle/isle/isle_examples/pass/conversions.isle
Normal file
@@ -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))
|
||||
21
cranelift/isle/isle/isle_examples/pass/let.isle
Normal file
21
cranelift/isle/isle/isle_examples/pass/let.isle
Normal file
@@ -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)))
|
||||
4
cranelift/isle/isle/isle_examples/pass/nodebug.isle
Normal file
4
cranelift/isle/isle/isle_examples/pass/nodebug.isle
Normal file
@@ -0,0 +1,4 @@
|
||||
(type DoesNotDeriveDebug nodebug
|
||||
(enum A
|
||||
B
|
||||
C))
|
||||
24
cranelift/isle/isle/isle_examples/pass/test2.isle
Normal file
24
cranelift/isle/isle/isle_examples/pass/test2.isle
Normal file
@@ -0,0 +1,24 @@
|
||||
(type u32 (primitive u32))
|
||||
(type A (enum
|
||||
(A1 (x B) (y B))))
|
||||
(type B (enum
|
||||
(B1 (x u32))
|
||||
(B2 (x u32))))
|
||||
|
||||
(decl A2B (A) B)
|
||||
|
||||
(rule 1
|
||||
(A2B (A.A1 _ (B.B1 x)))
|
||||
(B.B1 x))
|
||||
|
||||
(rule 0
|
||||
(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))
|
||||
66
cranelift/isle/isle/isle_examples/pass/test3.isle
Normal file
66
cranelift/isle/isle/isle_examples/pass/test3.isle
Normal file
@@ -0,0 +1,66 @@
|
||||
(type Opcode extern (enum
|
||||
Iadd
|
||||
Isub
|
||||
Load
|
||||
Store))
|
||||
|
||||
(type Inst (primitive Inst))
|
||||
(type InstInput (primitive InstInput))
|
||||
(type Reg (primitive Reg))
|
||||
(type u32 (primitive u32))
|
||||
|
||||
(decl Op (Opcode) Inst)
|
||||
(extern extractor infallible Op get_opcode)
|
||||
|
||||
(decl InstInput (InstInput u32) Inst)
|
||||
(extern extractor infallible 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)
|
||||
|
||||
;; 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 (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 (UseInput ra) (UseInput rb)))
|
||||
(rule
|
||||
(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)))
|
||||
42
cranelift/isle/isle/isle_examples/pass/test4.isle
Normal file
42
cranelift/isle/isle/isle_examples/pass/test4.isle
Normal file
@@ -0,0 +1,42 @@
|
||||
(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)
|
||||
|
||||
(extern const $A u32)
|
||||
(extern const $B u32)
|
||||
|
||||
(decl C (bool) A)
|
||||
(extern constructor C c)
|
||||
|
||||
(decl Lower (A) A)
|
||||
|
||||
(rule
|
||||
(Lower
|
||||
(and
|
||||
a
|
||||
(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))
|
||||
|
||||
(decl F (Opcode) u32)
|
||||
(rule
|
||||
(F _)
|
||||
$B)
|
||||
96
cranelift/isle/isle/isle_examples/pass/tutorial.isle
Normal file
96
cranelift/isle/isle/isle_examples/pass/tutorial.isle
Normal file
@@ -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)))
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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<Converter> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Rule>,
|
||||
|
||||
/// 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<ast::Pattern> {
|
||||
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<ast::Expr> {
|
||||
// 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!(
|
||||
|
||||
54
cranelift/isle/isle/tests/run_tests.rs
Normal file
54
cranelift/isle/isle/tests/run_tests.rs
Normal file
@@ -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<String> {
|
||||
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"));
|
||||
Reference in New Issue
Block a user