Skip to content

[OptimizeInstructions] Check sign-ext child type - #9321

Merged
tlively merged 3 commits into
mainfrom
optimize-instructions-unreachable-sign-ext
Oct 29, 2026
Merged

tlively merged 3 commits into
mainfrom
optimize-instructions-unreachable-sign-ext

Conversation

@tlively

@tlively tlively commented Oct 28, 2026

Copy link
Copy Markdown
Member

In OptimizeInstructions, refinalization is deferred until the end of the function walk, so replacing a child expression (such as a trapping array.len on a null reference) with unreachable leaves an enclosing unary sign-extension instruction (i32.extend8_s, etc.) with its original integer type while its operand has Type::unreachable. While the shift-pair branch of Properties::getSignExtValue already rejects Type::unreachable operands via Abstract::Shl, the unary branch returned unary->value without checking that its type is still an integer, causing OptimizeInstructions::makeZeroExt to construct an integer mask literal with Type::unreachable and abort. Check unary->value->type.isInteger() in Properties::getSignExtValue.

In `OptimizeInstructions`, refinalization is deferred until the end of the function walk, so replacing a child expression (such as a trapping `array.len` on a null reference) with `unreachable` leaves an enclosing unary sign-extension instruction (`i32.extend8_s`, etc.) with its original integer type while its operand has `Type::unreachable`. While the shift-pair branch of `Properties::getSignExtValue` already rejects `Type::unreachable` operands via `Abstract::Shl`, the unary branch returned `unary->value` without checking that its type is still an integer, causing `OptimizeInstructions::makeZeroExt` to construct an integer mask literal with `Type::unreachable` and abort. Check `unary->value->type.isInteger()` in `Properties::getSignExtValue`.
@tlively
tlively requested a review from a team as a code owner October 28, 2026 23:00
@tlively
tlively requested review from kripken and removed request for a team October 28, 2026 23:00

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates getSignExtValue in src/ir/properties.h to ensure that the operand of a sign-extend operation is an integer by checking unary->value->type.isInteger() before returning it. This prevents treating newly-unreachable operands, which may have an unreachable type, as valid integer sign-extended values. A corresponding test case has been added to verify this behavior. There are no review comments, and I have no additional feedback to provide.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 Google Antigravity Code Review

I have reviewed the changes in this Pull Request and left inline feedback.

Comment thread src/ir/properties.h
case ExtendS16Int64:
case ExtendS32Int64:
return unary->value;
return unary->value->type.isInteger() ? unary->value : nullptr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[HIGH | Confidence: 5/5 | BUG]

While checking unary->value->type.isInteger() correctly addresses unary sign-extension operations whose operand became newly unreachable before refinalization, the shift-and-mask sign-extension pattern in the same function (lines 174–183) has the exact same vulnerability.

In getSignExtValue:

Suggested change
return unary->value->type.isInteger() ? unary->value : nullptr;
if (matches(curr,
binary(ShrS,
binary(Shl, any(&extended), ival(&leftShift)),
ival(&rightShift)))) {
auto leftShifts = Bits::getEffectiveShifts(leftShift, curr->type);
auto rightShifts = Bits::getEffectiveShifts(rightShift, curr->type);
if (leftShifts != 0 && leftShifts == rightShifts) {
return extended;
}
}

If extended becomes unreachable prior to refinalization propagating unreachable types to the outer Shl and ShrS nodes (for instance, (i32.shr_s (i32.shl (array.len (ref.null array)) 24) 24)), curr->type.isInteger() is initially true, extended is bound to the unreachable expression, and extended is returned.

Callers such as makeZeroExt (which calls Abstract::getBinary(curr->type, Abstract::And)) or Properties::getSignExtBits (which asserts curr->type.isInteger()) will crash when passed this unreachable node.

Consider verifying extended->type.isInteger() for the shift pattern as well:

Suggested change
return unary->value->type.isInteger() ? unary->value : nullptr;
if (leftShifts != 0 && leftShifts == rightShifts &&
extended->type.isInteger()) {
return extended;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a false positive. If the left child of the inner shift is unreachable, then binary(Shl, any(&extended), ival(&leftShift) will not match.

Comment thread src/ir/properties.h
case ExtendS16Int64:
case ExtendS32Int64:
return unary->value;
return unary->value->type.isInteger() ? unary->value : nullptr;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a comment here, as otherwise this looks like dead code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

@tlively
tlively merged commit 38b20e4 into main Oct 29, 2026
17 checks passed
@tlively
tlively deleted the optimize-instructions-unreachable-sign-ext branch October 29, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants