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:
Chris Fallin
2022-02-23 13:15:27 -08:00
committed by GitHub
parent 4e26c13bbe
commit 9dbb8c25c5
23 changed files with 492 additions and 9 deletions

View File

@@ -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: