1# ISLE: Instruction Selection Lowering Expressions
2
3This document will describe ISLE (Instruction Selection Lowering
4Expressions), a DSL (domain-specific language) that we have developed
5in order to help us express certain parts of the Cranelift compiler
6backend more naturally. ISLE was first [described in RFC
7#15](https://github.com/bytecodealliance/rfcs/pull/15) and now is used
8by and lives in the Cranelift tree in
9[cranelift/isle](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/isle).
10
11Documentation on how ISLE is used in Cranelift can be found
12[here](../../docs/isle-integration.md).
13
14## Intro and Whirlwind Tour: DSL for Instruction Lowering
15
16The goal of ISLE is to represent *instruction lowering patterns*. An
17instruction lowering pattern is a specification that a certain
18combination of operators in the IR (CLIF), when combined under certain
19conditions, can be compiled down into a certain sequence of machine
20instructions. For example:
21
22- An `iadd` (integer add) operator can always be lowered to an x86
23  `ADD` instruction with two register sources.
24
25- An `iadd` operator with one `iconst` (integer-constant) argument can
26  be lowered to an x86 `ADD` instruction with a register and an
27  immediate.
28
29One could write something like the following in ISLE (simplified from
30the real code [here](https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/codegen/src/isa/x64/lower.isle)):
31
32```lisp
33;; Add two registers.
34(rule (lower (iadd x y))
35      (value_reg (add
36                   (put_in_reg x)
37                   (RegMemImm.Reg (put_in_reg y)))))
38
39;; Add a register and an immediate.
40(rule (lower (iadd x (simm32_from_value y))
41      (value_reg (add
42                   (put_in_reg x)
43                   ;; `y` is a `RegMemImm.Imm`.
44                   y))))
45```
46
47ISLE lets the compiler backend developer express this information in a
48declarative way -- i.e., just write down a list of patterns, without
49worrying how the compilation process tries them out -- and the ISLE
50DSL compiler will convert this list of patterns into efficient Rust
51code that becomes part of Cranelift.
52
53The rest of this document will describe the semantics of the DSL
54itself. ISLE has been designed to be a general-purpose DSL that can
55apply to any sort of backtracking pattern-matching problem, and will
56generate a decision tree in Rust that can call into arbitrary
57interface code.
58
59Separate documentation will describe how we have written *bindings*
60and *helpers* to allow ISLE to be specifically used to write Cranelift
61lowering patterns like the above. (TODO: link this documentation)
62
63## Outline of This Document
64
65This document is organized into the following sections:
66
67* Term-Rewriting Systems: a general overview of how term-rewriting
68  systems work, how to think about nested terms, patterns and rewrite
69  rules, how they provide a general mechanism for computation, and how
70  term-rewriting is often used in a compiler-implementation context.
71
72* Core ISLE: the foundational concepts of the ISLE DSL, building upon
73  a general-purpose term-rewriting base. Covers the type system (typed
74  terms) and how rules are written.
75
76* ISLE with Rust: covers how ISLE provides an "FFI" (foreign function
77  interface) of sorts to allow interaction with Rust code, and
78  describes the scheme by which ISLE execution is mapped onto Rust
79  (data structures and control flow).[^1]
80
81* ISLE Internals: describes how the ISLE compiler works. Provides
82  insight into how an unordered collection of rewrite rules are
83  combined into executable Rust code that efficiently traverses the
84  input and matches on it.
85
86[^1]: One might call this the BRIDGE (Basic Rust Interface Designed
87    for Good Efficiency) to the ISLE, but unfortunately we missed the
88    chance to introduce that backronym when we wrote the initial
89    implementation.
90
91## Background: Term-Rewriting Systems
92
93*Note: this section provides general background on term-rewriting
94systems that is useful to better understand the context for ISLE and
95how to develop systems using it. Readers already familiar with
96term-rewriting systems, or wishing to skip to details on ISLE's
97version of term rewriting, can skip to the [next
98section](#core-isle-a-term-rewriting-system).*
99
100A [term-rewriting
101system](https://en.wikipedia.org/wiki/Rewriting#Term_rewriting_systems),
102or TRS, is a system that works by representing data as *terms* and
103then applying *rules* to "rewrite" the terms. This rewrite process
104continues until some application-specific end-condition is met, for
105example until no more rules are applicable or until the term reaches a
106"lowered" state by some definition, at which point the resulting term
107is the system's output.
108
109Term-rewriting systems are a general kind of computing system, at the
110same level as (e.g.) Turing machines or other abstract computing
111machines. Term-rewriting is actually Turing-complete, or in other
112words, can express any program, if no limits are placed on term length
113or recursion.[^2]
114
115[^2]: In fact, the [lambda
116      calculus](https://en.wikipedia.org/wiki/Lambda_calculus)
117      introduced by Alonzo Church is actually a term-rewriting system
118      and was developed at the same time as Turing's concepts of
119      universal computation!
120
121Why might one want to use a TRS rather than some other, more
122conventional, way of computing an answer? One reason is that they are
123highly applicable to *pattern-matching* problems: for example,
124translating data in one domain to data in another domain, where the
125translation consists of a bunch of specific equivalences. This is part
126of why term-rewriting is so interesting in the compiler domain:
127compiler backends work to lower certain patterns in the program (e.g.:
128a multiply-add combination) into instructions that the target machine
129provides (e.g.: a dedicated multiply-add instruction).
130
131Term rewriting as a process also naturally handles issues of
132*priority*, i.e. applying a more specific rule before a less specific
133one. This is because the abstraction allows for multiple rules to be
134"applicable", and so there is a natural place to reason about priority
135when we choose which rule to apply. This permits a nice separation of
136concerns: we can specify which rewrites are *possible* to apply
137separately from which are *desirable* to apply, and adjust or tune the
138latter (the "strategy") at will without breaking the system's
139correctness.
140
141Additionally, term rewriting allows for a sort of *modularity* that is
142not present in hand-written pattern-matching code: the specific rules
143can be specified in any order, and the term-rewriting engine "weaves"
144them together so that in any state, when we have partially matched the
145input and are narrowing down which rule will apply, we consider all
146the related rules at once. Said another way: hand-written code tends
147to accumulate a lot of nested conditionals and switch/match
148statements, i.e., resembles a very large decision tree, while
149term-rewriting code tends to resemble a flat list of simple patterns
150that, when composed and combined, become that more complex tree. This
151allows the programmer to more easily maintain and update the set of
152lowering rules, considering each in isolation.
153
154### Data: Nested Trees of Constructors
155
156A term-rewriting system typically operates on data that is in a *tree*
157form, or at least can be interpreted that way.[^3]
158
159[^3]: In the most fundamental and mathematical sense, a TRS just
160      operates on a sequence of symbols, but we can talk about
161      structure that is present in those symbols in any well-formed
162      sequence. For example, we can define a TRS that only operates on
163      terms with balanced parentheses; then we have our tree.
164
165In ISLE and hence in this document, we operate on terms that are
166written in an
167[S-expression](https://en.wikipedia.org/wiki/S-expression) syntax,
168borrowed from the Lisp world. So we might have a term:
169
170```lisp
171    (a (b c 1 2) (d) (e 3 4))
172```
173
174which we can write more clearly as the tree:
175
176```lisp
177    (a
178      (b
179        c 1 2)
180      (d)
181      (e
182        3 4))
183```
184
185Each term consists of either a *constructor* (which looks like a
186function call to Lisp-trained eyes) or a *primitive*. In the above,
187the `(a ...)`, `(b ...)`, `(d)`, and `(e ...)` terms/subterms are
188constructor invocations. A constructor takes some number of arguments
189(its *arity*), each of which is itself a term. Primitives can be
190things like integer, string, or boolean constants, or variable names.
191
192Some term-rewriting systems have other syntax conventions: for
193example, systems based on
194[Prolog](https://en.wikipedia.org/wiki/Prolog) tend to write terms
195like `a(b(c, 1, 2), d, e(3, 4))`, i.e., with the name of the term on
196the outside of the parentheses. This is just a cosmetic difference to
197the above, but we note it to make clear that the term structure is
198important, not the syntax.
199
200It may not be immediately clear how to use this data representation,
201but we can give a small flavor here: if one defines *constructors* for
202each instruction or operator in a compiler's intermediate
203representation (IR), one can start to write expressions from that IR
204as terms; for example:
205
206```lisp
207    v1 = imul y, z
208    v2 = iadd x, v1
209```
210
211could become:
212
213```lisp
214    (iadd x (imul y z))
215```
216
217This will become much more useful once we have rewrite rules to
218perform transformations on the terms!
219
220Representing an IR is, of course, just one possible use of term data
221(albeit the original "MVP" that guided ISLE's design); there are many
222others, too. Interested readers are encouraged to read more on, e.g.,
223[Prolog](https://en.wikipedia.org/wiki/Prolog), which has been used to
224represent logical predicates, "facts" in expert systems, symbolic
225mathematical terms, and more.
226
227### Rules: Left-hand-side Patterns, Right-hand-side Expressions
228
229The heart of a term-rewriting system is in the set of *rules* that
230actually perform the rewrites. The "program" itself, in a
231term-rewriting DSL, consists simply of an unordered list of
232rules. Each rule may or may not apply; if it applies, then it can be
233used to edit the term. Execution consists of repeated application of
234rules until some criteria are met.
235
236A rule consists of two parts: the left-hand side (LHS), or *pattern*,
237and right-hand side (RHS), or *expression*. The left-hand and
238right-hand nomenclature comes from a common way of writing rules as:
239
240```plain
241    A -> B              ;; any term "A" is rewritten to "B"
242
243    (A x) -> (B (C x))  ;; any term (A x), for some x, is rewritten to (B (C x)).
244
245    (A _) -> (D)        ;; any term (A _), where `_` is a wildcard (any subterm),
246                        ;; is rewritten to (D).
247```
248
249#### Left-hand Sides: Patterns
250
251Each left-hand side is written in a pattern language that commonly has
252a few different kinds of "matchers", or operators that can match
253subterms:
254
255* `(A pat1 pat2 ...)` matches a constructor `A` with patterms for each
256  of its arguments.
257
258* `x` matches any subterm and captures its value in a variable
259  binding, which can be used later when we specify the right-hand side
260  (so that the rewrite contains parts of the original term).
261
262* `_` is a wildcard and matches anything, without capturing it.
263
264* Primitive constant values, such as `42` or `$Symbol`, match only if
265  the term is exactly equal to this constant.
266
267These pattern-matching operators can be combined, so we could write,
268for example, `(A (B x _) z)`. This pattern would match the term `(A (B
2691 2) 3)` but not `(A (C 4 5) 6)`.
270
271A pattern can properly be seen as a partial function from input term
272to captured (bound) variables: it either matches or it doesn't, and if
273it does, it provides specific term values for each variable binding
274that can be used by the right-hand side.
275
276A fully-featured term rewriting system usually has other operators as
277well, for convenience: for example, "match already-captured value", or
278"bind variable to subterm and also match it with subpattern", or
279"match subterm with all of these subpatterns". But even the above is
280powerful enough for Turing-complete term reduction, surprisingly; the
281essence of term-rewriting's power is just its ability to trigger
282different rules on different "shapes" of the tree of constructors in
283the input, and on special cases for certain argument values.
284
285Pattern-based term rewriting has a notable and important feature: it
286typically allows *overlapping* rules. This means that more than one
287pattern might match on the input. For example, the two rules:
288
289```plain
290    (A (B x)) -> (C x)
291    (A _) -> (D)
292```
293
294could *both* apply to an input term `(A (B 1))`. The first rule would
295rewrite this input to `(C 1)`, and the second rule would rewrite it to
296`(D)`. Either rewrite would be an acceptable execution step under the
297base semantics of most term-rewriting systems; ordinarily, the
298*correctness* of the rewrite should not depend on which rule is
299chosen, only possibly the "optimality" of the output (whatever that
300means for the application domain in question) or the number of rewrite
301steps to get there.
302
303However, in order to provide a deterministic answer, the system must
304somehow specify which rule will be applied in such a situation based
305on precedence, or specificity, or some other tie-breaker. A common
306heuristic is "more specific rule wins". We will see how ISLE resolves
307this question below by using both this heuristic and an explicit
308priority mechanism.[^4]
309
310[^4]: Some term-rewriting systems actually elaborate the entire space
311      of possibilities, following *all* possible rule application
312      sequences / rewrite paths. For example, the *equality
313      saturation* technique
314      ([paper](https://cseweb.ucsd.edu/~lerner/papers/popl09.pdf),
315      [example implementation
316      Egg](https://blog.sigplan.org/2021/04/06/equality-saturation-with-egg/))
317      builds a data structure that represents all equivalent terms
318      under a set of rewrite rules, from which a heuristic
319      (cost/goodness function) can be used to extract one answer when
320      needed.
321
322#### Right-hand Sides: Rewrite Expressions
323
324Given a rule whose pattern has matched, we now need to compute the
325rewritten term that replaces the original input term. This rewrite is
326specified by the right-hand side (RHS), which consists of an
327*expression* that generates a new term. This expression can use parts
328of the input term that have been captured by variables in the
329pattern.
330
331We have already seen a few examples of this above: simple term
332expressions, with variables used in place of concrete subterms where
333desired. A typical term-rewrite system allows just a few options in
334the output expression:
335
336* Terms, with sub-expressions as arguments;
337* Constant primitives (`42` or `$Symbol`); and
338* Captured variable values (`x`).
339
340The options are more limited in expressions than in patterns (e.g.,
341there are no wildcards) because a pattern is matching on a range of
342possible terms while an expression must specify a specific rewrite
343result.
344
345### Rewrite Steps and Intermediate Terms
346
347Now that we can specify rewrites via a list of rules, we can study how
348the top-level execution of a term-rewriting system proceeds. Much of
349the power of term-rewriting comes from the fact that rewrites can
350*chain together* into a multi-step traversal through several
351intermediate terms before the final answer is computed.
352
353For a simple example, consider the following rules:
354
355```plain
356    (A (B x)) -> (C x)
357    (C (D x)) -> (E x)
358    (C (F x)) -> (G x)
359```
360
361This set of rules will rewrite `(A (B (D 42)))` to `(C (D 42))`, then
362to `(E 42)` (via the first and second rules respectively).
363
364How is this useful? First, rewriting one term to another (here, `C` at
365the top level) that in turn appears in the left-hand side of other
366rules allows us to *factor* a "program" of term-rewriting rules in the
367same way that imperative programs are factored into separate
368functions.[^5] The usual advantages of a well-factored program, where
369each problem is solved with a small step that "reduces to a previously
370solved problem", apply here.
371
372Second, repeating the rewrite step is actually what grants
373term-rewriting its Turing-completeness: it allows for arbitrary
374control flow.[^6] This might be useful in cases where, for example, a
375term-rewriting program needs "loop" over a list of elements in the
376input: it can recurse and use intermediate terms to store state.
377
378While this full generality may not be used often in the
379domain-specific applications of term-rewriting that emphasize its
380pattern-matching (such as instruction selectors), the user should not
381be afraid to define and use intermediate terms -- rewriting into them,
382then defining additional rules to rewrite further -- when it helps to
383factor common behavior out of multiple rules, or aids in conceptual
384clarity.
385
386[^5]: In fact, ISLE actually compiles rules for different top-level
387      pattern terms (`(A ...)` and `(C ...)` in the example) into
388      separate Rust functions, so factoring rules to use intermediate
389      terms can provide code-size and compile-time benefits for the
390      ISLE-generated Rust code as well.
391
392[^6]: The [lambda calculus' reduction
393      rules](https://en.wikipedia.org/wiki/Lambda_calculus#Reduction)
394      are a good example of this.
395
396### Application to Compilers: A Term is a Value; Rewrites are Lowerings
397
398So far this has been a fairly abstract introduction to term-rewriting
399systems as a general computing paradigm. How does this relate (or, how
400is it commonly mapped) to the instruction-selection problem?
401
402In a domain such as instruction selection, we manipulate terms that
403represent computations described by an IR, and the terms are
404eventually rewritten into terms that name specific machine
405instructions. We can think of each term as having a denotational value
406that that *is* that program value. Then, any rewrite is correct if it
407preserves the denotational value of the term.
408
409In other words, terms are just values, and rules specify alternative
410ways to compute the same values. We might have rewrite rules that
411correspond to common algebraic identities (`a + b` == `b + a`, and
412`a + 0` == `a`), for example. The main sort of rewrite rule, however,
413will be one that takes a machine-*independent* operator term and
414rewrites it into a machine-*dependent* instruction term. For example:
415
416```plain
417    (iadd a b) -> (isa.add_reg_reg a b)
418
419    (iadd a (iconst 0)) -> a
420
421    (iadd a (iconst n)) (isa.add_reg_imm a n)
422```
423
424These rules specify three ways to convert an IR `iadd` operator into
425an ISA-specific instruction. Recall from above that in general, an
426application of a term-rewriting system should not depend for
427correctness on the order or choice of rule application: when multiple
428rules are applicable, then any sequence of rewrites that ends in a
429terminating state (a state with no further-applicable rules) should be
430considered a "correct" answer.[^7] Here, this is true: if, for
431example, we choose the register-plus-register form (the first rule)
432for an `iadd` operation, but the second argument is actually an
433`iconst`, then that is still valid, and the `iconst` will separately
434be rewritten by some other rule that generates a constant into a
435register. It simply may not be as efficient as the more specific third
436rule (or second rule, if the constant is zero). Hence, rule ordering
437and prioritization is nevertheless important for the quality of the
438instruction selector.
439
440[^7]: Note that this suggests an interesting testing strategy: we
441      could choose arbitrary (random) orders of lowering rules to
442      apply, or even deliberately worst-case orders according to some
443      heuristic. If we can differentially test programs compiled with
444      such randomized lowerings against "normally" compiled programs
445      and show that the results are always the same, then we have
446      shown that are lowering rules are "internally consistent",
447      without any other external oracle. This will have a similar
448      effect to
449      [wasm-mutate](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-mutate),
450      but takes mutations implicitly from the pool of rules rather
451      than a separate externally-defined pool of mutations. This idea
452      remains future work.
453
454## Core ISLE: a Term-Rewriting System
455
456This section describes the core ISLE language. ISLE's core is a
457term-rewriting system, with a design that very closely follows the
458generic concepts that we have described above.
459
460In the core language, ISLE's key departure from many other
461term-rewriting systems is that it is *strongly typed*. A classical
462term-rewriting system, especially one designed for instruction
463rewriting, will typically have just one type of term, corresponding to
464a "value" in the program. In contrast, ISLE is designed so that terms
465can represent various concepts in a compiler backend: values, but also
466machine instructions or parts of those instructions ("integer
467immediate value encodable in machine's particular format"), or
468abstract bundles of information with invariants or guarantees encoded
469in the type system ("load that I can sink", "instruction that produces
470flags").
471
472ISLE's other key departure from many other systems is its first-class
473integration with Rust, including a well-defined "FFI" mapping that
474allows ISLE rules to call into Rust in both their patterns and
475expressions, and to operate directly on types that are defined in the
476surrounding Rust code. This allows for easy and direct embedding into
477an existing compiler backend. We will cover this aspect more in the
478next section, [ISLE to Rust](#isle-to-rust).
479
480### Rules
481
482ISLE allows the user to specify rewrite rules, with a syntax similar
483in spirit to that shown above:
484
485```lisp
486    (rule
487      ;; left-hand side (pattern): if the input matches this ...
488      (A (B _ x) (C y))
489      ;; ... then rewrite to this:
490      (D x y))
491```
492
493The pattern (left-hand side) is made up of the following match
494operators:
495
496* Wildcards (`_`).
497* Integer constants (decimal/hex/binary/octal, positive/negative: `1`, `-1`,
498  `0x80`, `-0x80`). Hex constants can start with either `0x` or `0X`.
499  Binary constants start with `0b`. Octal constants start with `0o`.
500  Integers can also be interspersed with `_` as a separator, for example
501  `1_000` or `0x1234_5678`, for readability.
502* constants imported from the embedding, of arbitrary type
503  (`$MyConst`).
504* Variable captures and matches (bare identifiers like `x`; an
505  identifier consists of alphanumeric characters and underscores, and
506  does not start with a digit). The first occurrence of a variable `x`
507  captures the value; each subsequent occurrence matches on the
508  already-captured value, rejecting the match if not equal.
509* Variable captures with sub-patterns: `x @ PAT`, which captures the
510  subterm in `x` as above but also matches `PAT` against the
511  subterm. For example, `x @ (A y z)` matches an `A` term and captures
512  its arguments as `y` and `z`, but also captures the whole term as
513  `x`.
514* conjunctions of subpatterns: `(and PAT1 PAT2 ...)` matches all of
515  the subpatterns against the term. If any subpattern does not match,
516  then this matcher fails.
517* Term deconstruction: `(term PAT1 PAT2 ...)`, where `term` is a
518  defined term (type variant or constructor) and the subpatterns are
519  applied to each argument value in turn. Note that `term` cannot be a
520  wildcard; it must be a specific, concrete term.
521
522The expression (right-hand side) is made up of the following
523expression operators:
524
525* Integer and symbolic constants, as above.
526* Variable uses (bare `x` identifier).
527* Term constructors (`(term EXPR1 EXPR2 ...)`, where each
528  subexpression is evaluated first and then the term is constructed).
529* `let`-expressions that bind new variables, possibly using the values
530  multiple times within the body: `(let ((var1 type1 EXPR1) (var2 ...)
531  ...) BODY ...)`. Each variable's initialization expression can refer
532  to the immediately previous variable bindings (i.e., this is like a
533  `let*` in Scheme). `let`s are lexically-scoped, meaning that bound
534  variables are available only within the body of the `let`.
535
536When multiple rules are applicable to rewrite a particular term, ISLE
537will choose the "more specific" rule according to a particular
538heuristic: in the lowered sequence of matching steps, when one
539left-hand side completes a match while another with the same prefix
540continues with further steps, the latter (more specific) is chosen.
541
542The more-specific-first heuristic is usually good enough, but when an
543undesirable choice occurs, explicit priorities can be specified.
544Rules with explicit priorities are written as `(rule PRIO lhs rhs)`
545where `PRIO` is a signed (positive or negative) integer. An applicable
546rule with a higher priority will always match before a rule with a
547lower priority. The default priority for all rules if not otherwise
548specified is `0`.
549
550Note that the system allows multiple applicable rules to exist with
551the same priority: that is, while the priority system allows for
552manual tie-breaking, this tie-breaking is not required.
553
554Finally, one important note: the priority system is considered part of
555the core language semantics and execution of rules with different
556priorities is well-defined, so can be relied upon when specifying
557correct rules. However, the tie-breaking heuristic is *not* part of
558the specified language semantics, and so the user should never write
559rules whose correctness depends on one rule overriding another
560according to the heuristic.
561
562### Typed Terms
563
564ISLE allows the programmer to define types, and requires every term to
565have *parameter types* and a *return type* (analogous to first-order
566functions).
567
568The universe of types is very simple: there are *primitives*, which
569can be integers or symbolic constants (imported from the Rust
570embedding), and *enums*, which correspond directly to Rust enums with
571variants that have named fields. There is no subtyping. Some examples
572of type definitions are:
573
574```lisp
575
576    (type u32 (primitive u32))  ;; u32 is a primitive, and is
577                                ;; spelled `u32` in the generated Rust code.
578
579    (type MyType (enum
580                   (A (x u32) (y u32))
581                   (B (z u32))
582                   (C)))        ;; MyType is an enum, with variants
583                                ;; `MyType::A { x, y }`, `MyType::B { z }`,
584                                ;; and `MyType::C`.
585
586    (type MyType2 extern (enum (A)))
587                                ;; MyType2 is an enum with variant `MyType2::A`.
588                                ;; Its type definition is not included in the
589                                ;; generated Rust, but rather, assumed to exist
590                                ;; in surrounding code. Useful for binding to
591                                ;; existing definitions.
592```
593
594We then declare constructors with their parameter and return types as
595follows:
596
597```lisp
598
599    (decl Term1 (u32 u32) MyType)  ;; Term1 has two `u32`-typed parameters,
600                                   ;; and itself has type `MyType`.
601    (decl Term2 () u32)            ;; Term2 has no parameters and type `u32`.
602```
603
604Note that when an enum type is defined, its variants are implicitly
605defined as constructors as well. These constructors are namespaced
606under the name of the type, to avoid ambiguity (or the need to do
607type-dependent lookups in the compiler, which can complicate type
608inference). For example, given the above `MyType` definitions, we
609automatically have the following constructors:
610
611```lisp
612
613    ;; These definitions are implicit and do not need to be written (doing
614    ;; so is a compile-time error, actually). We write them here just to
615    ;; show what they would look like.
616
617    (decl MyType.A (u32 u32) MyType)
618    (decl MyType.B (u32) MyType)
619    (decl MyType.C () MyType)
620
621    (decl MyType2.A () MyType2)
622```
623
624### Why Types?
625
626For terms that are not enum variants, the notion that a term "has a
627type" is somewhat foreign to a classical term-rewriting system. In
628most formal symbolic systems, the terms are manipulated as opaque
629sequences or trees of symbols; they have no inherent meaning other
630than what the user implicitly defines with the given rules. What does
631it mean for a term to "have a type" when it is just data? Or, said
632another way: why isn't the type of `Term2` just `Term2`?
633
634The types come into play when we define *rules*: one term of type `T`
635can only be rewritten into another term of type `T`, and when a
636parameter has a certain type, only subterms with that type can
637appear. Without explicit types on terms and their parameters, any term
638could be rewritten to any other, or substituted in as a parameter, and
639there is thus a kind of dynamic typing about which the programmer must
640have some caution. In most applications of a term-rewriting system,
641there is already some de-facto "schema": some parameter of a term
642representing a machine instruction can only take on one of a few
643subterms (representing, say, different addressing modes). ISLE's types
644just make this explicit.
645
646Thus, the first answer to "why types" is that they enforce a schema on
647the terms, allowing the programmer to have stronger well-formed-data
648invariants.
649
650The second reason is that the types are an integral part of the
651compilation-to-Rust strategy: every constructor actually does evaluate
652to a Rust value of the given "return value" type, given actual Rust
653values for its parameters of the appropriate parameter types. We will
654see more on this below.
655
656### Well-Typed Rules and Type Inference
657
658Now that we understand how to define types, let's examine in more
659detail how they are used to verify that the pattern and rewrite
660expression of a rule have the same type.
661
662ISLE uses a simple unidirectional type-inference algorithm that
663propagates type information through the pattern, resulting in a "type
664environment" that specifies the type for each captured variable, and
665then uses this to typecheck the rewrite expression. The result of this
666is that types are almost completely inferred, and are only annotated
667in a few places (`let` bindings specifically).
668
669The typing rules for patterns in ISLE are:
670
671* At the root of the pattern, we require that a *constructor* pattern
672  is used, rather than some other match operation (a wildcard, integer
673  constant, etc.). This is because compilation and dispatch into rules
674  is organized by the top-level constructor of the term being
675  rewritten.
676
677* At each part of the pattern except the root, there is an "expected
678  type" that is inferred from the surrounding context. We check that
679  this matches the actual type of the pattern.
680
681* A constructor pattern `(C x y z)`, given a constructor `(decl C (T1
682  T2 T2) R)`, has type `R` and provides expected types `T1`, `T2`, and
683  `T3` to its subpatterns.
684
685* A variable capture pattern `x` is compatible with any expected type
686  the first time it appears, and captures this expected type under the
687  variable identifier `x` in the type environment. Subsequent
688  appearances of `x` check that the expected type matches the
689  already-captured type.
690
691* A conjunction `(and PAT1 PAT2 ...)` checks that each subpattern is
692  compatible with the expected type.
693
694* Integer constants are compatible with any primitive expected
695  type. (This may change in the future if we add non-numeric
696  primitives, such as strings.)
697
698If we are able to typecheck the pattern, we have a type environment
699that is a map from variable bindings to types: e.g., `{ x: MyType, y:
700MyType2, z: u32 }`. We then typecheck the rewrite expression.
701
702* Every expression also has an expected type, from the surrounding
703  context. We check that the provided expression matches this type.
704
705* The top-level rewrite expression must have the same type as the
706  top-level constructor in the pattern. (In other words, a term can
707  only be rewritten to another term of the same type.)
708
709* Constructors check their return values against the expected type,
710  and typecheck their argument expressions against their parameter
711  types.
712
713* A `let` expression provides types for additional variable bindings;
714  these are added to the type environment while typechecking the
715  body. The expected type for the body is the same as the expected
716  type for the `let` itself.
717
718### A Note on Heterogeneous Types
719
720We should illuminate one particular aspect of the ISLE type system
721that we described above. We have said that a term must be rewritten to
722another term of the same type. Note that this does *not* mean that,
723for example, a set of ISLE rules cannot be used to translate a term of
724type `T1` to a term of type `T2`. The trick is to define a top-level
725"driver" that wraps the `T1`, such that reducing this term results in
726a `T2`. Concretely:
727
728```lisp
729    (type T1 ...)
730    (type T2 ...)
731
732    (decl Translate (T1) T2)
733
734    (rule (Translate (T1.A ...))
735          (T2.X ...))
736    (rule (Translate (T1.B ...))
737          (T2.Y ...))
738```
739
740This gets to the heart of rewrite-system-based computation, and has
741relevance for applications of ISLE to compiler backends. A common
742technique in rewrite systems is to "kick off" a computation by
743wrapping a term in some intermediate term that then drives a series of
744reductions. Here we are using `Translate` as this top-level term. A
745difference between ISLE and some other rewrite-based instruction
746selectors is that rewrites are always driven by term reduction from
747such a toplevel term, rather than a series of equivalences directly
748from IR instruction to machine instruction forms.
749
750In other words, a conventional instruction selection pattern engine
751might let one specify `(Inst.A ...) -> (Inst.X ...)`. In this
752conventional design, the instruction/opcode type on the LHS and RHS
753must be the same single instruction type (otherwise rewrites could not
754be chained), and rewrite relation (which we wrote as `->`) is in
755essence a single privileged relation. One can see ISLE as a
756generalization: we can define many different types, and many different
757toplevel terms from which we can start the reduction. In principle,
758one could have:
759
760```lisp
761
762    (type IR ...)
763    (type Machine1 ...)
764    (type Machine2 ...)
765
766    (decl TranslateToMachine1 (IR) Machine1)
767    (decl TranslateToMachine2 (IR) Machine2)
768
769    (rule (TranslateToMachine1 (IR.add a b)) (Machine1.add a b))
770    (rule (TranslateToMachine2 (IR.add a b)) (Machine2.weird_inst a b))
771```
772
773and then both translations are available. We are "rewriting" from `IR`
774to `Machine1` and from `IR` to `Machine2`, even if rewrites always
775preserve the same type; we get around the rule by using a constructor.
776
777### Constructors and Extractors
778
779So far, we have spoken of terms and constructors: a term is a schema
780for data, like `(A arg1 arg2)`, while we have used the term
781"constructor" to refer to the `A`, like a function. We now refine this
782notion somewhat and define what it means for a term to appear in the
783left-hand (pattern) or right-hand (expression) side of a rule.
784
785More precisely, a term, like `A`, can have three kinds of behavior
786associated with it: it can be an enum type variant, it can be a
787constructor, or it can be an *extractor*, which we will define in a
788moment. A term can be both an extractor and constructor
789simultaneously, but the enum type variant case is mutually exclusive
790with the others.
791
792The distinction between a "constructor" and an "extractor" is whether
793a term is being deconstructed (matched on) -- by an extractor -- or
794constructed -- by a constructor.
795
796#### Constructors
797
798Constructor behavior on a term allows it to be invoked in the
799right-hand side of a rule. A term can have either an "external
800constructor" (see below) or an "internal constructor", defined in
801ISLE. Any term `A` that has one or more `(rule (A ...) RHS)` rules in
802the ISLE source implicitly has an internal constructor, and this
803constructor can be invoked from the right-hand side of other rules.
804
805#### Extractors
806
807Extractor behavior on a term allows it to be used in a pattern in the
808left-hand side of a rule. If one considers a constructor to be a
809function that goes from argument values to the complete term, then an
810extractor is a function that takes a complete term and possibly
811matches on it (it is fallible). If it does match, it provides the
812arguments *as results*.
813
814One can see extractors as "programmable match operators". They are a
815generalization of enum-variant deconstruction. Where a traditional
816term-rewriting system operates on a term data-structure that exists in
817memory, and can discover that a pattern `(A x y)` matches a term `A`
818at a particular point in the input, an extractor-based system instead
819sees `A` as an *arbitrary programmable operator* that is invoked
820wherever a pattern-match is attempted, and can return "success" with
821the resulting "fields" as if it were actually an enum variant. For
822more on this topic, see the motivation and description in [RFC 15
823under "programmable matching on virtual
824nodes"](https://github.com/bytecodealliance/rfcs/blob/main/accepted/cranelift-isel-isle-peepmatic.md#extractors-programmable-matching-on-virtual-nodes).
825
826To provide a concrete example, if we have the term declarations
827
828```lisp
829    (decl A (u32 u32) T)
830    (decl B (T) U)
831```
832
833then if we write a rule like
834
835```lisp
836    (rule (B (A x y))
837          (U.Variant1 x y))
838```
839
840then we have used `A` as an *extractor*. When `B` is invoked as a
841constructor with some `T`, this rule uses `A` as an extractor and
842attempts (via whatever programmable matching behavior) to use `A` to
843turn the `T` into two `u32`s, binding `x` and `y`. `A` can succeed or
844fail, just as any other part of a pattern-match can.
845
846Just as for constructors, there are *internal* and *external*
847extractors. Most of the interesting programmable behavior occurs in
848external extractors, which are defined in Rust; we will discuss this
849further in a section below. Internal extractors, in contrast, behave
850like macros, and can be defined for convenience: for example, we can
851write
852
853```lisp
854    (decl A (u32 u32) T)
855    (extractor (A pat1 pat2)
856               (and
857                 (extractArg1 pat1)
858                 (extractArg2 pat2)))
859```
860
861which will, for example, expand a pattern `(A (subterm ...) _)` into
862`(and (extractArg1 (subterm ...)) (extractArg2 _))`: in other words,
863the arguments to `A` are substituted into the extractor body and then
864this body is inlined.
865
866#### Implicit Type Conversions
867
868For convenience, ISLE allows the program to associate terms with pairs
869of types, so that type mismatches are *automatically resolved* by
870inserting that term.
871
872For example, if one is writing a rule such as
873
874```lisp
875    (decl u_to_v (U) V)
876    (rule ...)
877
878    (decl MyTerm (T) V)
879    (rule (MyTerm t)
880          (u_to_v t))
881```
882
883the `(u_to_v t)` term would not typecheck given the ISLE language
884functionality that we have seen so far, because it expects a `U` for
885its argument but `t` has type `T`. However, if we define
886
887
888```lisp
889    (convert T U t_to_u)
890
891    ;; For the above to be valid, `t_to_u` should be declared with the
892    ;; signature:
893    (decl t_to_u (T) U)
894    (rule ...)
895```
896
897then the DSL compiler will implicitly understand the above `MyTerm` rule as:
898
899```lisp
900    (rule (MyTerm t)
901          (u_to_v (t_to_u t)))
902```
903
904This also works in the extractor position: for example, if one writes
905
906```lisp
907    (decl defining_instruction (Inst) Value)
908    (extern extractor defining_instruction ...)
909
910    (decl iadd (Value Value) Inst)
911
912    (rule (lower (iadd (iadd a b) c))
913          ...)
914
915    (convert Inst Value defining_instruction)
916```
917
918then the `(iadd (iadd a b) c)` form will be implicitly handled like
919`(iadd (defining_instruction (iadd a b)) c)`. Note that the conversion
920insertion needs to have local type context in order to find the right
921converter: so, for example, it cannot infer a target type from a
922pattern where just a variable binding occurs, even if the variable is
923used in some typed context on the right-hand side. Instead, the
924"inner" and "outer" types have to come from explicitly typed terms.
925
926#### Summary: Terms, Constructors, and Extractors
927
928We start with a `term`, which is just a schema for data:
929
930```lisp
931    (decl Term (A B C u32 u32) T)
932```
933
934A term can have:
935
9361. A single internal extractor body, via a toplevel `(extractor ...)`
937   form, OR
938
9392. A single external extractor binding (see next section); AND
940
9413. One or more `(rule (Term ...) ...)` toplevel forms, which together
942   make up an internal constructor definition, OR
943
9444. A single external constructor binding (see next section).
945
946### If-Let Clauses
947
948As an extension to the basic left-hand-side / right-hand-side rule
949idiom, ISLE allows *if-let clauses* to be used. These add additional
950pattern-matching steps, and can be used to perform additional tests
951and also to use constructors in place of extractors during the match
952phase when this is more convenient.
953
954To introduce the concept, an example follows (this is taken from the
955[RFC](https://github.com/bytecodealliance/rfcs/tree/main/accepted/isle-extended-patterns.md)
956that proposed if-lets):
957
958```lisp
959;; `u32_fallible_add` can now be used in patterns in `if-let` clauses
960(decl pure u32_fallible_add (u32 u32) u32)
961(extern constructor u32_fallible_add u32_fallible_add)
962
963(rule (lower (load (iadd addr
964                         (iadd (uextend (iconst k1))
965                               (uextend (iconst k2))))))
966      (if-let k (u32_fallible_add k1 k2))
967      (isa_load (amode_reg_offset addr k)))
968```
969
970The key idea is that we allow a `rule` form to contain the following
971sub-forms:
972
973```lisp
974(rule LHS_PATTERN
975  (if-let PAT2 EXPR2)
976  (if-let PAT3 EXPR3)
977  ...
978  RHS)
979```
980
981The matching proceeds as follows: the main pattern (`LHS_PATTERN`)
982matches against the input value (the term to be rewritten), as
983described in detail above. Then, if this matches, execution proceeds
984to the if-let clauses in the order they are specified. For each, we
985evaluate the expression (`EXPR2` or `EXPR3` above) first. An
986expression in an if-let context is allowed to be "fallible": the
987constructors return `Option<T>` at the Rust level and can return
988`None`, in which case the whole rule application fails and we move on
989to the next rule as if the main pattern had failed to match. (More on
990the fallible constructors below.) If the expression evaluation
991succeeds, we match the associated pattern (`PAT2` or `PAT3` above)
992against the resulting value. This too can fail, causing the whole rule
993to fail. If it succeeds, any resulting variable bindings are
994available. Variables bound in the main pattern are available for all
995if-let expressions and patterns, and variables bound by a given if-let
996clause are available for all subsequent clauses. All bound variables
997(from the main pattern and if-let clauses) are available in the
998right-hand side expression.
999
1000#### Pure Expressions and Constructors
1001
1002In order for an expression to be used in an if-let clause, it has to
1003be *pure*: it cannot have side-effects. A pure expression is one that
1004uses constants and pure constructors only. Enum variant constructors
1005are always pure. In general constructors that invoke function calls,
1006however (either as internal or external constructor calls), can lead
1007to arbitrary Rust code and have side-effects. So, we add a new
1008annotation to declarations as follows:
1009
1010```lisp
1011;; `u32_fallible_add` can now be used in patterns in `if-let` clauses
1012(decl pure u32_fallible_add (u32 u32) u32)
1013
1014;; This adds a method
1015;; `fn u32_fallible_add(&mut self, _: u32, _: u32) -> Option<u32>`
1016;; to the `Context` trait.
1017(extern constructor u32_fallible_add u32_fallible_add)
1018```
1019
1020The `pure` keyword here is a declaration that the term, when used as a
1021constructor, has no side-effects. Declaring an external constructor on
1022a pure term is a promise by the ISLE programmer that the external Rust
1023function we are naming (here `u32_fallible_add`) has no side-effects
1024and is thus safe to invoke during the match phase of a rule, when we
1025have not committed to a given rule yet.
1026
1027When an internal constructor body is generated for a term that is pure
1028(i.e., if we had `(rule (u32_fallible_add x y) ...)` in our program
1029after the above declaration instead of the `extern`), the right-hand
1030side expression of each rule that rewrites the term is also checked
1031for purity.
1032
1033#### `partial` Expressions
1034
1035ISLE's `partial` keyword on a term indicates that the term's
1036constructors may fail to match, otherwise, the ISLE compiler assumes
1037the term's constructors are infallible.
1038
1039For example, the following term's constructor only matches if the value
1040is zero:
1041
1042```
1043;; Match any zero value.
1044(decl pure partial is_zero_value (Value) Value)
1045(extern constructor is_zero_value is_zero_value)
1046```
1047
1048Internal constructors without the `partial` keyword can
1049only use other constructors that also do not have the `partial` keyword.
1050
1051#### `if` Shorthand
1052
1053It is a fairly common idiom that if-let clauses are used as predicates
1054on rules, such that their only purpose is to allow a rule to match,
1055and not to perform any destructuring with a sub-pattern. For example,
1056one might want to write:
1057
1058```lisp
1059(rule (lower (special_inst ...))
1060      (if-let _ (isa_extension_enabled))
1061      (isa_special_inst ...))
1062```
1063
1064where `isa_extension_enabled` is a pure constructor that is fallible,
1065and succeeds only when a condition is true.
1066
1067To enable more succinct expression of this idiom, we allow the
1068following shorthand notation using `if` instead:
1069
1070```lisp
1071(rule (lower (special_inst ...))
1072      (if (isa_extension_enabled))
1073      (isa_special_inst ...))
1074```
1075
1076#### Recursion
1077
1078ISLE terms may be recursive: a rewrite rule's RHS can reference the term it
1079matches on, either directly or via a reference cycle.  However, recursive terms
1080present a risk of potentially unbounded term rewriting. In the compilation
1081context, it is possible that certain recursive rules could be exploited to
1082induce a stack overflow with a malicious input program.  Therefore, ISLE
1083disallows recursion by default.
1084
1085Recursion can still be justified when it can be shown to be bounded, therefore
1086ISLE allows certain terms to opt-in to recursive definitions.  To permit
1087recursive references in a term's rules, declare the term with the `rec`
1088attribute: `(decl rec A ...)`. In the case of a reference cycle, all terms in
1089the cycle must have the `rec` attribute. When using the `rec` attribute,
1090developers should provide a `; Recursion: ...` comment explaining why this use
1091is bounded.
1092
1093
1094## ISLE to Rust
1095
1096Now that we have described the core ISLE language, we will document
1097how it interacts with Rust code. We consider these interactions to be
1098semantically as important as the core language: they are not
1099implementation details, but rather, a well-defined interface by which
1100ISLE can interface with the outside world (an "FFI" of sorts).
1101
1102### Mapping to Rust: Constructors, Functions, and Control Flow
1103
1104ISLE was designed to have a simple, easy-to-understand mapping from
1105its language semantics to Rust semantics. This means that the
1106execution of ISLE rewriting has a well-defined implementation in
1107Rust. The basic principles are:
1108
11091. Every term with rules in ISLE becomes a single Rust function. The
1110   arguments are the Rust function arguments. The term's "return
1111   value" is the Rust function's return value (wrapped in an `Option`
1112   because pattern coverage can be incomplete).
1113
11142. One rewrite step is one Rust function call.
1115
11163. Rewriting is thus eager, and reified through ordinary Rust control
1117   flow. When we construct a term that appears on the left-hand side
1118   of rules, we do so by calling a function (the "constructor body");
1119   and this function *is* the rewrite logic, so the term is rewritten
1120   as soon as it exists. The code that embeds the ISLE generated code
1121   will kick off execution by calling a top-level "driver"
1122   constructor. The body of the constructor will eventually choose one
1123   of several rules to apply, and execute code to build the right-hand
1124   side expression; this can invoke further constructors for its
1125   subparts, kicking off more rewrites, until eventually a value is
1126   returned.
1127
11284. This design means that "intermediate terms" -- constructed terms
1129   that are then further rewritten -- are never actually built as
1130   in-memory data-structures. Rather, they exist only as ephemeral
1131   stack-frames while the corresponding Rust function executes. This
1132   means that there is very little or no performance penalty to
1133   factoring code into many sub-rules (subject only to function-call
1134   overhead and/or the effectiveness of the Rust inliner).
1135
11365. Backtracking -- attempting to match rules, and backing up to follow
1137   a different path when a match fails -- exists, but is entirely
1138   internal to the generated Rust function for rewriting one
1139   term. Once we are rewriting a term, we have committed to that term
1140   existing as a rewrite step; we cannot backtrack further. However,
1141   backtracking can occur within the delimited scope of this one
1142   term's rewrite; we have a phase during which we evaluate left-hand
1143   sides, trying to find a matching rule, and once we find one, we
1144   commit and start to invoke constructors to build the right-hand
1145   side.
1146
1147   Said another way, the principle is that left-hand sides can be
1148   fallible, and have no side-effects as they execute; right-hand
1149   sides, in contrast, are infallible. This simplifies the control
1150   flow and makes reasoning about side-effects (especially with
1151   respect to external Rust actions) easier.
1152
1153This will become more clear as we look at how Rust interfaces are
1154defined, and how the generated code appears, below.
1155
1156### Extern Constructors and Extractors
1157
1158ISLE programs interact with the surrounding Rust code in which they
1159are embedded by allowing the programmer to define a term to have
1160*external constructors* and an *external extractor*.
1161
1162The design philosophy of ISLE is that while internally it operates as
1163a fairly standard term-rewriting system, on the boundaries the "terms"
1164should be virtual, and defined procedurally rather than reified into
1165data structures, in order to allow for very flexible binding to the
1166embedding application. Thus, when term-rewriting bottoms out on the
1167input side, it just calls "extractors" to match on whatever ultimate
1168input the user provides, and these are fully programmable; and when it
1169bottoms out on the output side, the "term tree" is reified as a tree
1170of Rust function calls rather than plain data.
1171
1172#### Constructors
1173
1174As we defined above, a "constructor" is a term form that appears in an
1175expression and builds its return value from its argument
1176values. During the rewriting process, a constructor that can trigger
1177further rewriting rules results in a Rust function call to the body of
1178the "internal constructor" built from these rules; the term thus never
1179exists except as argument values on the stack. However, ultimately the
1180ISLE code needs to return some result to the outside world, and this
1181result may be built up of many parts; this is where *external
1182constructors* come into play.
1183
1184For any term declared like
1185
1186```lisp
1187    (decl T (A B C) U)
1188```
1189
1190the programmer can declare
1191
1192```lisp
1193    (extern constructor T ctor_func)
1194```
1195
1196which means that there is a Rust function `ctor_func` on the context
1197trait (see below) that can be *invoked* with arguments of type `A`,
1198`B`, `C` (actually borrows `&A`, `&B`, `&C`, for non-primitive types)
1199and returns a `U`.
1200
1201External constructors are infallible: that is, they must succeed, and
1202always return their return type. In contrast, internal constructors
1203can be fallible because they are implemented by a list of rules whose
1204patterns may not cover the entire domain (in which case, the term
1205should be marked `partial`). If fallible behavior is needed when
1206invoking external Rust code, that behavior should occur in an extractor
1207(see below) instead: only pattern left-hand sides are meant to be
1208fallible.
1209
1210#### Extractors
1211
1212An *external extractor* is an implementation of matching behavior in
1213left-hand sides (patterns) that is fully programmable to interface
1214with the embedding application. When the generated pattern-matching
1215code is attempting to match a rule, and has a value to match against
1216an extractor pattern defined as an external extractor, it simply calls
1217a Rust function with the value of the term to be deconstructed, and
1218receives an `Option<(arg1, arg2, ...)>` in return. In other words, the
1219external extractor can choose to match or not, and if it does, it
1220provides the values that are in turn matched by sub-patterns.
1221
1222For any term declared like
1223
1224```lisp
1225    (decl T (A B C) U)`
1226```
1227
1228the programmer can declare
1229
1230```lisp
1231    (extern extractor T etor_func)
1232```
1233
1234which means that there is a Rust function `etor_func` on the context
1235trait (see below) that can be *invoked* with an argument of type `&U`,
1236and returns an `Option<(A, B, C)>`.
1237
1238If an extractor returns `None`, then the generated matching code
1239proceeds just as if an enum variant match had failed: it moves on to
1240try the next rule in turn.
1241
1242### Mapping Type Declarations to Rust
1243
1244When we declare a type like
1245
1246```lisp
1247    (decl MyEnum (enum
1248                   (A (x u32) (y u32))
1249                   (B)))
1250```
1251
1252ISLE will generate the Rust type definition
1253
1254```rust
1255#[derive(Clone, Debug)]
1256pub enum MyEnum {
1257    A { x: u32, y: u32, },
1258    B,
1259}
1260```
1261
1262Note that enum variants with no fields take on the brace-less form,
1263while those with fields use the named-struct-field `A { x: ... }`
1264form. If all variants are field-less, then the type will additionally
1265derive `Copy`, `PartialEq`, and `Eq`.
1266
1267If the type is declared as extern (`(decl MyEnum extern (enum ...))`)
1268then the same definition is assumed to exist. Primitives (`(decl u32
1269(primitive u32))`) are assumed to be defined already, and are required
1270to be `Copy`.
1271
1272All imported/extern types are pulled in via `use super::*` at the top
1273of the generated code; thus, these types should exist in (or be
1274re-exported from) the parent module.
1275
1276### Symbolic Constants
1277
1278ISLE allows the user to refer to external constants as follows:
1279
1280```lisp
1281    (extern const $I32 Type)
1282```
1283
1284This allows code to refer to `$I32` whenever a value of type `Type` is
1285needed, in either a pattern (LHS) or an expression (RHS). These
1286constants are pulled in via the same `use super::*` that imports all
1287external types.
1288
1289### Exported Interface: Functions and Context Trait
1290
1291The generated ISLE code provides an interface that is designed to be
1292agnostic to the embedding application. This means that ISLE knows
1293nothing about, e.g., Cranelift or compiler concepts in
1294general. Rather, the generated code provides function entry points
1295with well-defined signatures based on the terms, and imports the
1296extern constructors and extractors via a context trait that the
1297embedder must implement.
1298
1299When a term `T` is defined like
1300
1301```lisp
1302    (decl T (A B C) U)
1303```
1304
1305and has an internal constructor (provided by `rule` bodies), then a
1306function with the following signature will be exported from the
1307generated code:
1308
1309```rust
1310    pub fn constructor_T<C: Context>(ctx: &mut C, arg0: &A, arg1: &B, arg2: &C) -> Option<U>;
1311```
1312
1313In other words, `constructor_` is prepended, and the function takes
1314the expected arguments, along with a "context" (more on this
1315below). It returns an `Option<U>` because internal constructors can be
1316partial: if no rule's pattern matches, then the constructor
1317fails. Note that if a sub-constructor fails, no backtracking occurs;
1318rather, the failure propagates all the way up to the entry point.
1319
1320What is this "context" for? The context argument is used to allow
1321external extractors and constructors to access the necessary state of
1322the embedding application. (For example, in Cranelift, it might be the
1323`LowerCtx` that controls the code-lowering process.)
1324
1325The context is a trait because we want to decouple the generated code
1326from the application as much as possible. The trait will have a method
1327for each defined external extractor and constructor. For example, if
1328we have the following terms and declarations:
1329
1330```lisp
1331    (decl A (u32 u32) T)
1332    (extern constructor A build_a)
1333
1334    (decl B (T) U)
1335    (external extractor B disassemble_b)
1336```
1337
1338then the `Context` trait will include these methods:
1339
1340```rust
1341    trait Context {
1342        fn build_a(&mut self, arg0: u32, arg1: u32) -> T;
1343        fn disassemble_b(&mut self, arg0: &U) -> Option<T>;
1344    }
1345```
1346
1347These functions should be implemented as described above for external
1348constructors and extractors.
1349
1350Note that some external extractors are known to always succeed, for
1351example if they are just fetching some information that is always
1352present; in this case, the generated code can be made slightly more
1353efficient if we tell the ISLE compiler that this is so. By declaring
1354
1355```lisp
1356    (external extractor infallible B disassemble_b)
1357```
1358
1359we eliminate the `Option` on the return type, so the method is instead
1360
1361```rust
1362    trait Context {
1363        // ...
1364        fn disassemble_b(&mut self, arg0: &U) -> T;
1365    }
1366```
1367
1368## ISLE Internals
1369
1370### Compiler Stages
1371
1372Some detail and pointers to the compilation stages can be found in the
1373[README](../isle/README.md). The sequence starts as any ordinary
1374compiler: lexing, parsing, semantic analysis, and generation of an
1375IR. The most unique part is the "decision trie generation", which is
1376what converts the unordered-list-of-rule representation into something
1377that corresponds to the final Rust code's control flow and order of
1378matching operations.
1379
1380We describe this data structure below with the intent to provide an
1381understanding of how the DSL compiler weaves rules together into Rust
1382control flow. While this understanding shouldn't be strictly necessary
1383to use the DSL, it may be helpful. (The ultimate answer to "how does
1384the generated code work" is, of course, found by reading the generated
1385code; some care has been taken to ensure it is reasonably legible for
1386human consumption!)
1387
1388### Decision Trie
1389
1390The heart of the ISLE transformation lies in how the compiler converts
1391a list of rules into a scheme to attempt to match rules in some order,
1392possibly sharing match operations between similar rules to reduce
1393work.
1394
1395The core data structure we produce is a "decision trie" per internal
1396constructor body. This is an intermediate representation of sorts that
1397is built from individual-rule IR (LHS + RHS) sequences, and is then
1398used to generate Rust source.
1399
1400The decision trie is, as the name implies, a kind of decision tree, in
1401the sense that we start at the root and move down the tree based on
1402the result of match operations (each feeding one "decision").
1403
1404It is a "trie" (which is a kind of tree) because at each level, its
1405edges are labeled with match operations; a trie is a tree where one
1406input character from an alphabet is used to index children at each
1407level.
1408
1409Each node in the tree is either an internal decision node, or a leaf
1410"expression" node (which we reach once we have a successful rule
1411match). The "execution semantics" of the trie are
1412backtracking-based. We attempt to find some path down the tree through
1413edges whose match ops run successfully; when we do this to reach a
1414leaf, we have the values generated by all of the match ops, and we can
1415execute the sequence of "expression instructions" in the leaf. Each
1416rule's left-hand side becomes a series of edges (merged into the
1417existing tree as we process rules) and each rule's right-hand side
1418becomes one leaf node with expression instructions.
1419
1420At any point, if a match op does not succeed, we try the next out-edge
1421in sequence. If we have tried all out-edges from a decision node and
1422none were successful, then we backtrack one level further. Thus, we
1423simply perform an in-order tree traversal and find the first
1424successful match.
1425
1426Though this sounds possibly very inefficient if some decision node has
1427a high fan-out, in practice it is not because the edges are often
1428known to be *mutually exclusive*. The canonical example of this is
1429when an enum-typed value is destructured into different variants by
1430various edges; we can use a Rust `match` statement in the generated
1431source and have `O(1)` (or close to it) cost for the dispatch at this
1432level.[^8]
1433
1434[^8]: The worst-case complexity for a single term rewriting operation
1435      is still the cost of evaluating each rule's left-hand side
1436      sequentially, because in general there is no guarantee of
1437      overlap between the patterns. Ordering of the edges out of a
1438      decision node also affects complexity: if mutually-exclusive
1439      match operations are not adjacent, then they cannot be merged
1440      into a single `match` with `O(1)` dispatch. In general this
1441      ordering problem is quite difficult. We could do better with
1442      stronger heuristics; this is an open area for improvement in the
1443      DSL compiler!
1444
1445## Reference: ISLE Language Grammar
1446
1447Baseline: allow arbitrary whitespace, and wasm-style comments (`;` to
1448newline, or nested block-comments with `(;` and `;)`).
1449
1450The grammar accepted by the parser is as follows:
1451
1452```bnf
1453<skip> ::= <whitespace> | <comment>
1454
1455<whitespace> ::= " "
1456               | "\t"
1457               | "\n"
1458               | "\r"
1459
1460<comment> ::= <line-comment> | <block-comment>
1461
1462<line-comment> ::= ";" <line-char>* (<newline> | eof)
1463<line-char> ::= <any character other than "\n" or "\r">
1464<newline> ::= "\n" | "\r"
1465
1466<block-comment> ::= "(;" <block-char>* ";)"
1467<block-char> ::= <any character other than ";" or "(">
1468               | ";" if the next character is not ")"
1469               | "(" if the next character is not ";"
1470               | <block-comment>
1471
1472<ISLE> ::= <def>*
1473
1474<def> ::= "(" "pragma" <pragma> ")"
1475        | "(" "type" <typedecl> ")"
1476        | "(" "decl" <decl> ")"
1477        | "(" "rule" <rule> ")"
1478        | "(" "extractor" <extractor> ")"
1479        | "(" "extern" <extern> ")"
1480        | "(" "convert" <converter> ")"
1481
1482;; No pragmas are defined yet
1483<pragma> ::= <ident>
1484
1485<typedecl> ::= <ident> [ "extern" | "nodebug" ] <type-body>
1486
1487<ident> ::= <ident-start> <ident-cont>*
1488<const-ident> ::= "$" <ident-cont>*
1489<ident-start> ::= <any non-whitespace character other than "-", "0".."9", "(", ")", ";", "#" or "$">
1490<ident-cont>  ::= <any non-whitespace character other than "(", ")", ";" or "@">
1491
1492<type-body> ::= "(" "primitive" <ident> ")"
1493              | "(" "enum" <enum-variant>* ")"
1494
1495<enum-variant> ::= <ident>
1496                 | "(" <ident> <variant-field>* ")"
1497
1498<variant-field> ::= "(" <ident> <ty> ")"
1499
1500<ty> ::= <ident>
1501
1502<decl> ::= [ "pure" ] [ "multi" ] [ "partial" ] [ "rec" ] <ident> "(" <ty>* ")" <ty>
1503
1504<rule> ::= [ <ident> ] [ <prio> ] <pattern> <stmt>* <expr>
1505
1506<prio> ::= <int>
1507
1508<int> ::= [ "-" ] ( "0".."9" ) ( "0".."9" | "_" )*
1509        | [ "-" ] "0" ("x" | "X") ( "0".."9" | "A".."F" | "a".."f" | "_" )+
1510        | [ "-" ] "0" ("o" | "O") ( "0".."7" | "_" )+
1511        | [ "-" ] "0" ("b" | "B") ( "0".."1" | "_" )+
1512
1513<pattern> ::= <int>
1514            | "true" | "false"
1515            | <const-ident>
1516            | "_"
1517            | <ident>
1518            | <ident> "@" <pattern>
1519            | "(" "and" <pattern>* ")"
1520            | "(" <ident> <pattern>* ")"
1521
1522<stmt> ::= "(" "if-let" <pattern> <expr> ")"
1523         | "(" "if" <expr> ")"
1524
1525<expr> ::= <int>
1526         | "true" | "false"
1527         | <const-ident>
1528         | <ident>
1529         | "(" "let" "(" <let-binding>* ")" <expr> ")"
1530         | "(" <ident> <expr>* ")"
1531
1532<let-binding> ::= "(" <ident> <ty> <expr> ")"
1533
1534<extractor> ::= "(" <ident> <ident>* ")" <pattern>
1535
1536<extern> ::= "constructor" <ident> <ident>
1537           | "extractor" [ "infallible" ] <ident> <ident>
1538           | "const" <const-ident> <ty>
1539
1540<converter> ::= <ty> <ty> <ident>
1541```
1542
1543## Reference: ISLE Language Grammar verification extensions
1544
1545```bnf
1546<def> += "(" "spec" <spec> ")"
1547       | "(" "model" <model> ")"
1548       | "(" "form" <form> ")"
1549       | "(" "instantiate" <instantiation> ")"
1550
1551<spec> ::= "(" <ident> <ident>* ")" <provide> [ <require> ]
1552<provide> ::= "(" "provide" <spec-expr>* ")"
1553<require> ::= "(" "require" <spec-expr>* ")"
1554
1555<model> ::= <ty> "(" "type" <model-ty> ")"
1556          | <ty> "(" "enum" <model-variant>* ")"
1557
1558<model-ty> ::= "Bool"
1559             | "Int"
1560             | "Unit"
1561             | "(" "bv" [ <int> ] ")"
1562
1563<model-variant> ::= "(" <ident> [ <spec-expr> ] ")"
1564
1565<form> ::= <ident> <signature>*
1566
1567<instantiation> ::= <ident> <signature>*
1568                  | <ident> <ident>
1569
1570<spec-expr> ::= <int>
1571              | <spec-bv>
1572              | "true" | "false"
1573              | <ident>
1574              | "(" "switch" <spec-expr> <spec-pair>* ")"
1575              | "(" <spec-op> <spec-expr>* ")"
1576              | "(" <ident> ")"
1577              | "(" ")"
1578
1579<spec-bv> ::= "#b" [ "+" | "-" ] ("0".."1")+
1580            | "#x" [ "+" | "-" ] ("0".."9" | "A".."F" | "a".."f")+
1581
1582<spec-pair> ::= "(" <spec-expr> <spec-expr> ")"
1583
1584<spec-op> ::= "and" | "not" | "or" | "=>"
1585            | "=" | "<=" | "<" | ">=" | ">"
1586            | "bvnot" | "bvand" | "bvor" | "bvxor"
1587            | "bvneg" | "bvadd" | "bvsub" | "bvmul"
1588            | "bvudiv" | "bvurem" | "bvsdiv" | "bvsrem"
1589            | "bvshl" | "bvlshr" | "bvashr"
1590            | "bvsaddo" | "subs"
1591            | "bvule" | "bvult" | "bvugt" | "bvuge"
1592            | "bvsle" | "bvslt" | "bvsgt" | "bvsge"
1593            | "rotr" | "rotl"
1594            | "extract" | "concat" | "conv_to"
1595            | "zero_ext" | "sign_ext"
1596            | "int2bv" | "bv2int"
1597            | "widthof"
1598            | "if" | "switch"
1599            | "popcnt" | "rev" | "cls" | "clz"
1600            | "load_effect" | "store_effect"
1601
1602<signature>  ::= "(" <sig-args> <sig-ret> <sig-canon> ")"
1603<sig-args>   ::= "(" "args" <model-ty>* ")"
1604<sig-ret>    ::= "(" "ret" <model-ty>* ")"
1605<sig-canon>  ::= "(" "canon" <model-ty>* ")"
1606```
1607