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