Skip statically dead typeof branches at MLIR generation - #324
Merged
Merged
Conversation
`typeof x === "name"` on a value whose type is known at compile time emits a
TypeDescriptor, whose run-time name (typeOfAsString of its type) is compared
with the literal by content, so the result is known while generating MLIR. It
was not folded, so a branch that can never run was generated anyway. In a
generic specialised for an array, such a branch can hold casts an array cannot
take: `print("s: ", x)` under `typeof x === "string"` emitted an array-to-string
cast that reached llvm_unreachable at CastLogicHelper.h:815 in debug builds
(01symbol, 00funcs_generic_with_typeof) and undefined behaviour in release.
Two changes:
- The ==, ===, != and !== comparison between a TypeDescriptor and a string
literal (either order) now produces a boolean literal, which `if` already
uses to skip a branch. `any`, union tags and stored typeof values keep their
run-time check; names compare exactly as at run time, so no result changes.
- `if` added its narrowing (safe-cast) before deciding whether to generate a
branch, so a skipped branch still got a cast of the tested value. Unused, it
was removed as dead code, but under --di the narrowed variable's debug
record kept it alive into LLVM lowering. Narrowing is now added only to a
branch that is generated, for both `then` and `else`.
Adds 00typeof_static_fold.ts (compile, jit, rc/none corpus).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 14, 2026
#324 stopped `if` from generating the branch of a `typeof x === "name"` test that folds to a constant. `?:`, `&&` and `while` still generated it, with its narrowing: in a generic specialised for an array, `typeof x === "string" ? x.length : -1` cast the array to string, and under --di the narrowed variable's debug record kept that cast alive until LLVM lowering, which hit llvm_unreachable in CastLogicHelper. - getStaticBoolean() recognises a constant condition, also through casts to boolean (a folded typeof, or an `&&` whose left side folded to false); `if` uses it too. - ?: and `false && right` evaluate the dead branch, narrowing included, in the temporary module to keep its type, and yield an undefined value of that type; the expression's type is unchanged. A boolean `false && right` is just the constant condition, so an enclosing if/while sees it too. - `while` with a false constant condition does not generate its body. `||` does not narrow and was not affected. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In a generic specialised for a concrete type, a
typeofcheck whose answer is known still generated its branch. When that branch uses the value as the narrowed type, it emits a cast the value cannot take:In debug builds the cast reached
llvm_unreachable("review usage")atCastLogicHelper.h:815; in release builds that is undefined behaviour and the test only passes because the branch never runs. This is why01symboland00funcs_generic_with_typeoffail on a local debug build but never on CI (all workflows build Release).Two separate causes:
typeof x === "name"was not folded. For a statically typedx,typeofemits aTypeDescriptor, whose run-time name (typeOfAsStringof its type) is compared with the literal by content, so the result is known during MLIR generation.ifadded narrowing before deciding to skip a branch. Even with a constant condition,checkSafeCastemitted the narrowing cast into the skipped branch. Unused, it was removed as dead code, but under--di(which the test runner always passes) the narrowed variable'sts.DebugVariablekept it alive into LLVM lowering.Fix
foldStaticTypeOfCompare(MLIRGenImpl.h):==,===,!=and!==between aTypeDescriptorand a string literal, in either order, produce a boolean literal, whichifalready uses to skip a branch.any, union tags and storedtypeofvalues keep their run-time check. Names are compared exactly as at run time (e.g. an integer literal is still"s32"), so no existing result changes.mlirGen(IfStatement): narrowing is added only to a branch that is generated, for boththenandelse.Not covered:
?:,&&/||andwhilealso narrow but always generate both branches, so atypeof-guarded?:in an array-specialised generic can still hit the same cast. That was already the case before this change.Tests
00typeof_static_fold.ts(compile, jit, rc/none corpus): a genericdescribe<T>called with a string, an empty array, a string array, a number and a boolean, plustypeofon anany. It crashes the compiler without the fix.01symboland00funcs_generic_with_typeofproduce the same output as the release build.00owned_array_opsunder rc,gc-shared-auto,gc-defaultlib-collector); 12 tests that failed before now pass.🤖 Generated with Claude Code