Skip to content

fix(codegen): a typed-array element store used as an expression evaluates to 0 — discard_expr_value leaks into operand position #7590

Description

@proggeramlug

Summary

A typed-array element store used as an expression evaluates to 0 instead of the assigned value, whenever it appears anywhere inside an expression statement. The store itself is correct — only the expression's value is wrong. Silent: no crash, no diagnostic, just a wrong number.

const buf = new Uint8Array(4);
function sink(label: string, got: number, want: number): void {
  if (got !== want) console.log("WRONG", label, "got", got, "want", want);
}

sink("store-arg", (buf[0] = 5), 5);            // consumed as a call argument
let n = 0;
n = buf[1] = 7;                                 // consumed by an assignment
sink("store-assign", n, 7);
sink("store-binary", (buf[2] = 3) + 100, 103);  // consumed by arithmetic

console.log("buf", buf[0], buf[1], buf[2]);
output
node buf 5 7 3
perry (d2dca5823) WRONG store-arg got 0 want 5
WRONG store-assign got 0 want 7
WRONG store-binary got 100 want 103
buf 5 7 3

buf 5 7 3 on both, so the stores land correctly. An assignment expression must evaluate to the assigned value (ES2024 §13.15.2); here it evaluates to 0.

Cause

ctx.discard_expr_value means "this STATEMENT's value is discarded", and lower_expr does not clear it when recursing into subexpressions — the only reset in the tree is lower_call/new_ctor_args.rs:252, for constructor arguments. So while lowering the operands of sink(buf[0] = 5); the flag is still set from the enclosing Stmt::Expr.

Four sites read it as though it meant "this EXPRESSION's value is discarded" and return double_literal(0.0):

  • expr/index_set.rs:754lower_typed_array_store
  • expr/index_set.rs:769 — proven-view checked store
  • expr/index_set.rs:848
  • expr/arrays_finds.rs:788, :824, :895, :912Uint8ArraySet / buffer stores

expr/dispatch.rs:25 also reads the flag, but only to pick a materialization path, so it is not affected.

Scope

Needs a typed array or buffer view — plain arr[i] = v on an ordinary array does not take these paths, so this is narrower than "all index assignment". Every affected site is a store whose value would normally be materialized for the caller.

Suggested fix

The flag cannot answer the question these sites are asking. Either clear discard_expr_value in lower_expr when recursing into operand position — which makes it mean what the sites assume, and is the smaller change but touches every reader — or give the sites a non-leaking signal set only for an expression that is the direct child of Stmt::Expr.

I hit this from the other direction: I tried gating arr.push(x)'s length computation on the same flag as a perf change and it produced exactly this bug for sink(a.push(10)), n = a.push(20), a.push(1) + 100 and a.push(1) > 0 ? 7 : 9. So whichever fix is chosen should land with the array-push case in the same test, since that path is about to want the same optimization (js_array_length on a discarded push is 8–13% of push_cls — see #7511).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions