Skip to content

Cranelift: ISLE recursion check#12474

Merged
cfallin merged 11 commits intobytecodealliance:mainfrom
mmcloughlin:isle-recursion-check
Feb 12, 2026
Merged

Cranelift: ISLE recursion check#12474
cfallin merged 11 commits intobytecodealliance:mainfrom
mmcloughlin:isle-recursion-check

Conversation

@mmcloughlin
Copy link
Contributor

Extends the ISLE compiler with a recursive terms check. Terms with rules that include a reference cycle are only allowed if the term explicitly opts in with the rec attribute.

Recently, we have seen problems where recursive rules enable user-controlled recursion and therefore potential stack overflow enabling a compilation DoS. For example, see #12368, #12333, and #12369. With this additional compilation check, recursion is not disallowed but is more explicit, making audits easier and offering a path to eventually disallowing recursive rules altogether.

@mmcloughlin mmcloughlin requested a review from a team as a code owner January 29, 2026 19:55
@mmcloughlin mmcloughlin requested review from alexcrichton and removed request for a team January 29, 2026 19:55
@mmcloughlin mmcloughlin changed the title Isle recursion check Cranelift: ISLE recursion check Jan 29, 2026
Copy link
Member

@cfallin cfallin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! I think this will be a great improvement in our ability to reason about lowering complexity.

My main question/concern is about the semantics. It appears (after reading through it all -- some comments below are written as I worked this out) that the semantics are: any term that participates in a rec cycle must have rec, and any term with rec must participate in a recursive cycle, is that right?

Naively, at least coming from experience with other "permissive superset" features of languages, I would expect rec to permit recursion but not require it (e.g., unsafe in Rust, or letrec chains of bindings in a Lisp/Scheme, or ...). But maybe this is actually good if it means it forces us to rethink carefully (and keep justifications up-to-date) if we remove the recursion, too. Regardless, I'd like to see a description of the precise semantics and a distillation of the thinking behind it in the langref, so we can preserve that intent going forward.

A few other minor nits but otherwise seems fine -- thanks!

pub multi: bool,
/// Whether this term's constructor can fail to match.
pub partial: bool,
/// Whether this term is recursive.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/is recursive/is permitted to be recursive/, right?

If you wouldn't mind, it'd be great to update the langref at the same time (cranelift/isle/docs/language-reference.md). In particular the semantics could use a little bit of preciseness: is it that in any cycle in the callgraph, at least one term must have rec?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, after reading further, I see that actually all terms in the cycle must have rec, and all terms with rec must participate in a cycle; is that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to the "permit recursion" semantics. Added a section to the language reference.

rules: Vec<Span>,
},

/// Recurive rules error. Term is recursive without explicit opt-in, or vice versa.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"or vice versa" implies that if a term is marked rec then it must participate in a recursive cycle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer has the "must participate" semantics. Updated comment accordingly.

let mut errors = Vec::new();
for (term_id, _) in terms {
// Check if this term is involved in a reference cycle.
let reachable = terms_reachable_from(*term_id, &term_rule_sets);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat concerned about runtime here: for every term we're computing all reachable terms, which in the limit in an arbitrary graph has O(V^2) complexity, right?

Could we instead do a DFS over the forest (i.e., DFS with a toplevel loop over all nodes that starts a new DFS from a not-yet-visited node) to detect cycles? That should have O(V + E) complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use a DFS algorithm over the term-reference graph which should have O(V+E) complexity.

;; Helper for emitting `MInst.FpuCSel16` / `MInst.FpuCSel32` / `MInst.FpuCSel64`
;; instructions.
(decl fpu_csel (Type Cond Reg Reg) ConsumesFlags)
(decl rec fpu_csel (Type Cond Reg Reg) ConsumesFlags)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In each case where we have a rec, could we have a comment describing why it's bounded?

In particular I'm thinking these should be modeled after something like Rust safety comments -- so e.g. // Recursion: may recurse once to reuse implementation for F32 in the case of F16 or // Recursion: bounded by explicit depth parameter 'depth' or // Recursion: each iteration of count_the_bits_in_isle removes one bit from value and completes when zero or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ; Recursion: ... comments for all (decl rec ...) terms.

@github-actions github-actions bot added cranelift Issues related to the Cranelift code generator cranelift:area:aarch64 Issues related to AArch64 backend. cranelift:area:x64 Issues related to x64 codegen isle Related to the ISLE domain-specific language labels Jan 29, 2026
@github-actions
Copy link

Subscribe to Label Action

cc @cfallin, @fitzgen

Details This issue or pull request has been labeled: "cranelift", "cranelift:area:aarch64", "cranelift:area:x64", "isle"

Thus the following users have been cc'd because of the following labels:

  • cfallin: isle
  • fitzgen: isle

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

@mmcloughlin
Copy link
Contributor Author

Updated in response to review comments. Main changes:

  • Cycle finding via plain DFS with back-edge detection. Ideally we might use something like Johnson's Algorithm or a variant of Tarjan's, but DFS will find a cycle if there is one, and
    should be good enough in practice for the term-reference graphs we have.
  • Change rec attribute semantics to "permit recursion" rather than "permit and require recursion".
  • Add pass and fail test cases under isle_examples.
  • Document the rec attribute in the language reference.
  • Provide safety comments for all existing uses of rec in ISLE backends.

Copy link
Member

@cfallin cfallin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; thanks!

@cfallin cfallin added this pull request to the merge queue Feb 12, 2026
Merged via the queue into bytecodealliance:main with commit 8ec3ff3 Feb 12, 2026
76 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cranelift:area:aarch64 Issues related to AArch64 backend. cranelift:area:x64 Issues related to x64 codegen cranelift Issues related to the Cranelift code generator isle Related to the ISLE domain-specific language

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants