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 definining_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/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## ISLE to Rust
1077
1078Now that we have described the core ISLE language, we will document
1079how it interacts with Rust code. We consider these interactions to be
1080semantically as important as the core language: they are not
1081implementation details, but rather, a well-defined interface by which
1082ISLE can interface with the outside world (an "FFI" of sorts).
1083
1084### Mapping to Rust: Constructors, Functions, and Control Flow
1085
1086ISLE was designed to have a simple, easy-to-understand mapping from
1087its language semantics to Rust semantics. This means that the
1088execution of ISLE rewriting has a well-defined implementation in
1089Rust. The basic principles are:
1090
10911. Every term with rules in ISLE becomes a single Rust function. The
1092   arguments are the Rust function arguments. The term's "return
1093   value" is the Rust function's return value (wrapped in an `Option`
1094   because pattern coverage can be incomplete).
1095
10962. One rewrite step is one Rust function call.
1097
10983. Rewriting is thus eager, and reified through ordinary Rust control
1099   flow. When we construct a term that appears on the left-hand side
1100   of rules, we do so by calling a function (the "constructor body");
1101   and this function *is* the rewrite logic, so the term is rewritten
1102   as soon as it exists. The code that embeds the ISLE generated code
1103   will kick off execution by calling a top-level "driver"
1104   constructor. The body of the constructor will eventually choose one
1105   of several rules to apply, and execute code to build the right-hand
1106   side expression; this can invoke further constructors for its
1107   subparts, kicking off more rewrites, until eventually a value is
1108   returned.
1109
11104. This design means that "intermediate terms" -- constructed terms
1111   that are then further rewritten -- are never actually built as
1112   in-memory data-structures. Rather, they exist only as ephemeral
1113   stack-frames while the corresponding Rust function executes. This
1114   means that there is very little or no performance penalty to
1115   factoring code into many sub-rules (subject only to function-call
1116   overhead and/or the effectiveness of the Rust inliner).
1117
11185. Backtracking -- attempting to match rules, and backing up to follow
1119   a different path when a match fails -- exists, but is entirely
1120   internal to the generated Rust function for rewriting one
1121   term. Once we are rewriting a term, we have committed to that term
1122   existing as a rewrite step; we cannot backtrack further. However,
1123   backtracking can occur within the delimited scope of this one
1124   term's rewrite; we have a phase during which we evaluate left-hand
1125   sides, trying to find a matching rule, and once we find one, we
1126   commit and start to invoke constructors to build the right-hand
1127   side.
1128
1129   Said another way, the principle is that left-hand sides can be
1130   fallible, and have no side-effects as they execute; right-hand
1131   sides, in contrast, are infallible. This simplifies the control
1132   flow and makes reasoning about side-effects (especially with
1133   respect to external Rust actions) easier.
1134
1135This will become more clear as we look at how Rust interfaces are
1136defined, and how the generated code appears, below.
1137
1138### Extern Constructors and Extractors
1139
1140ISLE programs interact with the surrounding Rust code in which they
1141are embedded by allowing the programmer to define a term to have
1142*external constructors* and an *external extractor*.
1143
1144The design philosophy of ISLE is that while internally it operates as
1145a fairly standard term-rewriting system, on the boundaries the "terms"
1146should be virtual, and defined procedurally rather than reified into
1147data structures, in order to allow for very flexible binding to the
1148embedding application. Thus, when term-rewriting bottoms out on the
1149input side, it just calls "extractors" to match on whatever ultimate
1150input the user provides, and these are fully programmable; and when it
1151bottoms out on the output side, the "term tree" is reified as a tree
1152of Rust function calls rather than plain data.
1153
1154#### Constructors
1155
1156As we defined above, a "constructor" is a term form that appears in an
1157expression and builds its return value from its argument
1158values. During the rewriting process, a constructor that can trigger
1159further rewriting rules results in a Rust function call to the body of
1160the "internal constructor" built from these rules; the term thus never
1161exists except as argument values on the stack. However, ultimately the
1162ISLE code needs to return some result to the outside world, and this
1163result may be built up of many parts; this is where *external
1164constructors* come into play.
1165
1166For any term declared like
1167
1168```lisp
1169    (decl T (A B C) U)
1170```
1171
1172the programmer can declare
1173
1174```lisp
1175    (extern constructor T ctor_func)
1176```
1177
1178which means that there is a Rust function `ctor_func` on the context
1179trait (see below) that can be *invoked* with arguments of type `A`,
1180`B`, `C` (actually borrows `&A`, `&B`, `&C`, for non-primitive types)
1181and returns a `U`.
1182
1183External constructors are infallible: that is, they must succeed, and
1184always return their return type. In contrast, internal constructors
1185can be fallible because they are implemented by a list of rules whose
1186patterns may not cover the entire domain (in which case, the term
1187should be marked `partial`). If fallible behavior is needed when
1188invoking external Rust code, that behavior should occur in an extractor
1189(see below) instead: only pattern left-hand sides are meant to be
1190fallible.
1191
1192#### Extractors
1193
1194An *external extractor* is an implementation of matching behavior in
1195left-hand sides (patterns) that is fully programmable to interface
1196with the embedding application. When the generated pattern-matching
1197code is attempting to match a rule, and has a value to match against
1198an extractor pattern defined as an external extractor, it simply calls
1199a Rust function with the value of the term to be deconstructed, and
1200receives an `Option<(arg1, arg2, ...)>` in return. In other words, the
1201external extractor can choose to match or not, and if it does, it
1202provides the values that are in turn matched by sub-patterns.
1203
1204For any term declared like
1205
1206```lisp
1207    (decl T (A B C) U)`
1208```
1209
1210the programmer can declare
1211
1212```lisp
1213    (extern extractor T etor_func)
1214```
1215
1216which means that there is a Rust function `etor_func` on the context
1217trait (see below) that can be *invoked* with an argument of type `&U`,
1218and returns an `Option<(A, B, C)>`.
1219
1220If an extractor returns `None`, then the generated matching code
1221proceeds just as if an enum variant match had failed: it moves on to
1222try the next rule in turn.
1223
1224### Mapping Type Declarations to Rust
1225
1226When we declare a type like
1227
1228```lisp
1229    (decl MyEnum (enum
1230                   (A (x u32) (y u32))
1231                   (B)))
1232```
1233
1234ISLE will generate the Rust type definition
1235
1236```rust
1237#[derive(Clone, Debug)]
1238pub enum MyEnum {
1239    A { x: u32, y: u32, },
1240    B,
1241}
1242```
1243
1244Note that enum variants with no fields take on the brace-less form,
1245while those with fields use the named-struct-field `A { x: ... }`
1246form. If all variants are field-less, then the type will additionally
1247derive `Copy`, `PartialEq`, and `Eq`.
1248
1249If the type is declared as extern (`(decl MyEnum extern (enum ...))`)
1250then the same definition is assumed to exist. Primitives (`(decl u32
1251(primitive u32))`) are assumed to be defined already, and are required
1252to be `Copy`.
1253
1254All imported/extern types are pulled in via `use super::*` at the top
1255of the generated code; thus, these types should exist in (or be
1256re-exported from) the parent module.
1257
1258### Symbolic Constants
1259
1260ISLE allows the user to refer to external constants as follows:
1261
1262```lisp
1263    (extern const $I32 Type)
1264```
1265
1266This allows code to refer to `$I32` whenever a value of type `Type` is
1267needed, in either a pattern (LHS) or an expression (RHS). These
1268constants are pulled in via the same `use super::*` that imports all
1269external types.
1270
1271### Exported Interface: Functions and Context Trait
1272
1273The generated ISLE code provides an interface that is designed to be
1274agnostic to the embedding application. This means that ISLE knows
1275nothing about, e.g., Cranelift or compiler concepts in
1276general. Rather, the generated code provides function entry points
1277with well-defined signatures based on the terms, and imports the
1278extern constructors and extractors via a context trait that the
1279embedder must implement.
1280
1281When a term `T` is defined like
1282
1283```lisp
1284    (decl T (A B C) U)
1285```
1286
1287and has an internal constructor (provided by `rule` bodies), then a
1288function with the following signature will be exported from the
1289generated code:
1290
1291```rust
1292    pub fn constructor_T<C: Context>(ctx: &mut C, arg0: &A, arg1: &B, arg2: &C) -> Option<U>;
1293```
1294
1295In other words, `constructor_` is prepended, and the function takes
1296the expected arguments, along with a "context" (more on this
1297below). It returns an `Option<U>` because internal constructors can be
1298partial: if no rule's pattern matches, then the constructor
1299fails. Note that if a sub-constructor fails, no backtracking occurs;
1300rather, the failure propagates all the way up to the entry point.
1301
1302What is this "context" for? The context argument is used to allow
1303external extractors and constructors to access the necessary state of
1304the embedding application. (For example, in Cranelift, it might be the
1305`LowerCtx` that controls the code-lowering process.)
1306
1307The context is a trait because we want to decouple the generated code
1308from the application as much as possible. The trait will have a method
1309for each defined external extractor and constructor. For example, if
1310we have the following terms and declarations:
1311
1312```lisp
1313    (decl A (u32 u32) T)
1314    (extern constructor A build_a)
1315
1316    (decl B (T) U)
1317    (external extractor B disassemble_b)
1318```
1319
1320then the `Context` trait will include these methods:
1321
1322```rust
1323    trait Context {
1324        fn build_a(&mut self, arg0: u32, arg1: u32) -> T;
1325        fn disassemble_b(&mut self, arg0: &U) -> Option<T>;
1326    }
1327```
1328
1329These functions should be implemented as described above for external
1330constructors and extractors.
1331
1332Note that some external extractors are known to always succeed, for
1333example if they are just fetching some information that is always
1334present; in this case, the generated code can be made slightly more
1335efficient if we tell the ISLE compiler that this is so. By declaring
1336
1337```lisp
1338    (external extractor infallible B disassemble_b)
1339```
1340
1341we eliminate the `Option` on the return type, so the method is instead
1342
1343```rust
1344    trait Context {
1345        // ...
1346        fn disassemble_b(&mut self, arg0: &U) -> T;
1347    }
1348```
1349
1350## ISLE Internals
1351
1352### Compiler Stages
1353
1354Some detail and pointers to the compilation stages can be found in the
1355[README](../isle/README.md). The sequence starts as any ordinary
1356compiler: lexing, parsing, semantic analysis, and generation of an
1357IR. The most unique part is the "decision trie generation", which is
1358what converts the unordered-list-of-rule representation into something
1359that corresponds to the final Rust code's control flow and order of
1360matching operations.
1361
1362We describe this data structure below with the intent to provide an
1363understanding of how the DSL compiler weaves rules together into Rust
1364control flow. While this understanding shouldn't be strictly necessary
1365to use the DSL, it may be helpful. (The ultimate answer to "how does
1366the generated code work" is, of course, found by reading the generated
1367code; some care has been taken to ensure it is reasonably legible for
1368human consumption!)
1369
1370### Decision Trie
1371
1372The heart of the ISLE transformation lies in how the compiler converts
1373a list of rules into a scheme to attempt to match rules in some order,
1374possibly sharing match operations between similar rules to reduce
1375work.
1376
1377The core data structure we produce is a "decision trie" per internal
1378constructor body. This is an intermediate representation of sorts that
1379is built from individual-rule IR (LHS + RHS) sequences, and is then
1380used to generate Rust source.
1381
1382The decision trie is, as the name implies, a kind of decision tree, in
1383the sense that we start at the root and move down the tree based on
1384the result of match operations (each feeding one "decision").
1385
1386It is a "trie" (which is a kind of tree) because at each level, its
1387edges are labeled with match operations; a trie is a tree where one
1388input character from an alphabet is used to index children at each
1389level.
1390
1391Each node in the tree is either an internal decision node, or a leaf
1392"expression" node (which we reach once we have a successful rule
1393match). The "execution semantics" of the trie are
1394backtracking-based. We attempt to find some path down the tree through
1395edges whose match ops run successfully; when we do this to reach a
1396leaf, we have the values generated by all of the match ops, and we can
1397execute the sequence of "expression instructions" in the leaf. Each
1398rule's left-hand side becomes a series of edges (merged into the
1399existing tree as we process rules) and each rule's right-hand side
1400becomes one leaf node with expression instructions.
1401
1402At any point, if a match op does not succeed, we try the next out-edge
1403in sequence. If we have tried all out-edges from a decision node and
1404none were successful, then we backtrack one level further. Thus, we
1405simply perform an in-order tree traversal and find the first
1406successful match.
1407
1408Though this sounds possibly very inefficient if some decision node has
1409a high fan-out, in practice it is not because the edges are often
1410known to be *mutually exclusive*. The canonical example of this is
1411when an enum-typed value is destructured into different variants by
1412various edges; we can use a Rust `match` statement in the generated
1413source and have `O(1)` (or close to it) cost for the dispatch at this
1414level.[^8]
1415
1416Building the trie is a somewhat subtle procedure; see [this block
1417comment](https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/isle/isle/src/trie.rs#L15-L166)
1418for more information regarding the trie construction algorithm.
1419
1420[^8]: The worst-case complexity for a single term rewriting operation
1421      is still the cost of evaluating each rule's left-hand side
1422      sequentially, because in general there is no guarantee of
1423      overlap between the patterns. Ordering of the edges out of a
1424      decision node also affects complexity: if mutually-exclusive
1425      match operations are not adjacent, then they cannot be merged
1426      into a single `match` with `O(1)` dispatch. In general this
1427      ordering problem is quite difficult. We could do better with
1428      stronger heuristics; this is an open area for improvement in the
1429      DSL compiler!
1430
1431## Reference: ISLE Language Grammar
1432
1433Baseline: allow arbitrary whitespace, and wasm-style comments (`;` to
1434newline, or nested block-comments with `(;` and `;)`).
1435
1436The grammar accepted by the parser is as follows:
1437
1438```bnf
1439<skip> ::= <whitespace> | <comment>
1440
1441<whitespace> ::= " "
1442               | "\t"
1443               | "\n"
1444               | "\r"
1445
1446<comment> ::= <line-comment> | <block-comment>
1447
1448<line-comment> ::= ";" <line-char>* (<newline> | eof)
1449<newline> ::= "\n" | "\r"
1450<line-char> ::= <any character other than "\n" or "\r">
1451
1452<block-comment> ::= "(;" <block-char>* ";)"
1453<block-char> ::= <any character other than ";" or "(">
1454               | ";" if the next character is not ")"
1455               | "(" if the next character is not ";"
1456               | <block-comment>
1457
1458<ISLE> ::= <def>*
1459
1460<def> ::= "(" "pragma" <pragma> ")"
1461        | "(" "type" <typedecl> ")"
1462        | "(" "decl" <decl> ")"
1463        | "(" "rule" <rule> ")"
1464        | "(" "extractor" <etor> ")"
1465        | "(" "extern" <extern> ")"
1466        | "(" "convert" <converter> ")"
1467
1468// No pragmas are defined yet
1469<pragma> ::= <ident>
1470
1471<typedecl> ::= <ident> [ "extern" | "nodebug" ] <typevalue>
1472
1473<ident> ::= <ident-start> <ident-cont>*
1474<const-ident> ::= "$" <ident-cont>*
1475
1476<ident-start> ::= <any non-whitespace character other than "-", "0".."9", "(", ")" or ";">
1477<ident-cont>  ::= <any non-whitespace character other than "(", ")", ";" or "@">
1478
1479<int> ::= [ "-" ] ( "0".."9" | "_" )+
1480        | [ "-" ] "0x" ( "0".."9" | "A".."F" | "a".."f" | "_" )+
1481        | [ "-" ] "0o" ( "0".."7" | "_" )+
1482        | [ "-" ] "0b" ( "0".."1" | "_" )+
1483
1484<typevalue> ::= "(" "primitive" <ident> ")"
1485              | "(" "enum" <enumvariant>* ")"
1486
1487<enumvariant> ::= <ident>
1488                | "(" <ident> <enumfield>* ")"
1489
1490<enumfield> ::= "(" <ident> <ty> ")"
1491
1492<ty> ::= <ident>
1493
1494<decl> ::= [ "pure" ] [ "multi" ] [ "partial" ] <ident> "(" <ty>* ")" <ty>
1495
1496<rule> ::= [ <ident> ] [ <prio> ] <pattern> <stmt>* <expr>
1497<prio> ::= <int>
1498
1499<etor> ::= "(" <ident> <ident>* ")" <pattern>
1500
1501<pattern> ::= <int>
1502            | "true" | "false"
1503            | <const-ident>
1504            | "_"
1505            | <ident>
1506            | <ident> "@" <pattern>
1507            | "(" "and" <pattern>* ")"
1508            | "(" <ident> <pattern-arg>* ")"
1509
1510<pattern-arg> ::= <pattern>
1511                | "<" <expr>  ;; in-argument to an extractor
1512
1513<stmt> ::= "(" "if-let" <pattern> <expr> ")"
1514         | "(" "if" <expr> ")"
1515
1516<expr> ::= <int>
1517         | "true" | "false"
1518         | <const-ident>
1519         | <ident>
1520         | "(" "let" "(" <let-binding>* ")" <expr> ")"
1521         | "(" <ident> <expr>* ")"
1522
1523<let-binding> ::= "(" <ident> <ty> <expr> ")"
1524
1525<extern> ::= "constructor" <ident> <ident>
1526           | "extractor" [ "infallible" ] <ident> <ident>
1527           | "const" <const-ident> <ident> <ty>
1528
1529<converter> ::= <ty> <ty> <ident>
1530```
1531
1532## Reference: ISLE Language Grammar verification extensions
1533```bnf
1534<def> += "(" "spec" <spec> ")"
1535       | "(" "model" <model> ")"
1536       | "(" "form" <form> ")"
1537       | "(" "instantiate" <instantiation> ")"
1538
1539<spec> ::= "(" <ident> <ident>* <provide> [ <require> ] ")"
1540<provide> ::= "(" "provide" <spec-expr>* ")"
1541<require> ::= "(" "require" <spec-expr>* ")"
1542
1543<model> ::= <ty> "(" "type" <model-ty> ")"
1544          | <ty> "(" "enum" <model-variant>* ")"
1545
1546<model-ty> ::= "Bool"
1547             | "Int"
1548             | "Unit"
1549             | "(" "bv" <int> ")"
1550
1551<model-variant> ::= "(" <ident> [ <spec-expr> ]  ")"
1552
1553<form> ::= <ident> <signature>*
1554
1555<instantiation> ::= <ident> "(" <signature>* ")"
1556                  | <ident> <ident>
1557
1558<spec-expr> ::= <int>
1559              | <spec-bv>
1560              | "true" | "false"
1561              | <ident>
1562              | "(" "switch" <spec-expr> <spec-pair>* ")"
1563              | "(" <spec-op> <spec-expr>* ")"
1564              | "(" <ident> ")"
1565              | "(" ")"
1566
1567<spec-pair> ::= "(" <spec-expr> <spec-expr> ")"
1568
1569<spec-op> ::= "and" | "not" | "or" | "=>"
1570            | "=" | "<=" | "<" | ">=" | ">"
1571            | "bvnot" | "bvand" | "bvor" | "bvxor"
1572            | "bvneg" | "bvadd" | "bvsub" | "bvmul"
1573            | "bvudiv" | "bvurem" | "bvsdiv" | "bvsrem"
1574            | "bvshl" | "bvlshr| | "bvashr"
1575            | "bvsaddo" | "subs"
1576            | "bvule" | "bvult" | "bvugt" | "bvuge"
1577            | "bvsle" | "bvslt" | "bvsgt" | "bvsge"
1578            | "rotr" | "rotl"
1579            | "extract" | "concat" | "conv_to"
1580            | "zero_ext" | "sign_ext"
1581            | "int2bv" | "bv2int"
1582            | "widthof"
1583            | "if" | "switch"
1584            | "popcnt" | "rev" | "cls" | "clz"
1585            | "load_effect" | "store_effect"
1586
1587<signature>  ::= "(" <sig-args> <sig-ret> <sig-canon> ")"
1588<sig-args>   ::= "(" "args" <model-ty>* ")"
1589<sig-ret>    ::= "(" "ret" <model-ty>* ")"
1590<sig-canon>  ::= "(" "canon" <model-ty>* ")"
1591```
1592