Bug 245468 - A live node becomes undefined in OSR Exit.
Summary: A live node becomes undefined in OSR Exit.
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Local Build
Hardware: PC Linux
: P2 Normal
Assignee: David Degazio
URL:
Keywords: InRadar
: 246307 (view as bug list)
Depends on:
Blocks:
 
Reported: 2022-09-21 01:48 PDT by EntryHi
Modified: 2022-11-12 14:55 PST (History)
6 users (show)

See Also:


Attachments
Patch (6.16 KB, patch)
2022-10-10 15:34 PDT, David Degazio
no flags Details | Formatted Diff | Diff
Patch (6.12 KB, patch)
2022-10-10 16:01 PDT, David Degazio
d_degazio: commit-queue-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description EntryHi 2022-09-21 01:48:32 PDT
function main() {
  const v75 = {};
  v75.__proto__ = Object;
  Object.defineProperty(Object, 117982949, {});
  let v119 = 0;
  while (v119 < 10) {
    function v121() {
      for (let v130 = 0; v130 < 100; v130++) {}
      const v142 = [2.1,  NaN];
      print(v142[0]) // wrongly print undefined 
    }
    v121()
    v119++;
  }
}
noDFG(main);
noFTL(main);
main();

With the above script as input to JSC, run JSC with the following parameters:
./jsc test.js --useConcurrentJIT=0 --jitPolicyScale=0.001

v142[0] should be 2.1. But after OSRExit, v142[0] becomes undefined. The problem may be in CompileNewArray in DFGSpeculativeJIT. 2.1 is used (operand.use()) and NaN causes bailout. The use count of 2.1 is 0. So OSRExit wrongly thinks 2.1 is dead and put an undefined into the call frame.
Comment 1 Radar WebKit Bug Importer 2022-09-28 01:49:18 PDT
<rdar://problem/100497797>
Comment 2 David Degazio 2022-10-10 15:34:33 PDT
Created attachment 462916 [details]
Patch
Comment 3 David Degazio 2022-10-10 15:48:34 PDT
Elaborating on the explanation: in SpeculativeJIT::compileNewArray, in specifically the slow path (reached when we have invalidated the isHavingABadTime watchpoint), we explicitly use() each operand after we write it to the buffer:

>   case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
>       JSValueOperand operand(this, use, ManualOperandSpeculation);
>       JSValueRegs operandRegs = operand.jsValueRegs();
>       if (hasInt32(node->indexingType())) {
>           DFG_TYPE_CHECK(
>               operandRegs, use, SpecInt32Only,
>               m_jit.branchIfNotInt32(operandRegs));
>       }
>       m_jit.storeValue(operandRegs, buffer + operandIdx);
>       operand.use();
>       break;
>   }

This is an optimization to avoid spilling operands unnecessarily when we call the new-array operation further down, by manually managing the node lifetimes. If a later operand fails the DFG_TYPE_CHECK, however, we may OSRExit. At that point, the exit information will be collected after earlier operands were used, potentially leaving it dead, resulting in the undefined value we see in this bug.

This patch resolves the issue by doing two separate passes - first, we traverse the operands and emit the necessary type checks; second, we traverse the operands again and write them to the buffer. In this scheme, we generate the exit information prior to using any of the operands, so they should all remain live during this period.
Comment 4 Yusuke Suzuki 2022-10-10 15:54:06 PDT
Comment on attachment 462916 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=462916&action=review

r=me

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:9694
> +            for (unsigned operandIdx = 0; operandIdx < node->numChildren(); ++operandIdx) {
> +                Edge use = m_graph.m_varArgChildren[node->firstChild() + operandIdx];
> +                JSValueOperand operand(this, use, ManualOperandSpeculation);
> +                JSValueRegs operandRegs = operand.jsValueRegs();
> +                if (hasInt32(node->indexingType())) {
> +                    DFG_TYPE_CHECK(
> +                        operandRegs, use, SpecInt32Only,
> +                        m_jit.branchIfNotInt32(operandRegs));
> +                }
> +            }

Let's put it under `if (hasInt32(node->indexingType()))` And remove inner `if (hasInt32(node->indexingType()))`.
Comment 5 David Degazio 2022-10-10 16:01:16 PDT
Created attachment 462917 [details]
Patch
Comment 6 Yusuke Suzuki 2022-10-10 18:30:42 PDT
Comment on attachment 462917 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=462917&action=review

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:9657
>          case ALL_DOUBLE_INDEXING_TYPES: {

In WebKit coding style, `case` is aligned to `switch`.

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:9684
>          case ALL_INT32_INDEXING_TYPES:
>          case ALL_CONTIGUOUS_INDEXING_TYPES:
>          case ALL_ARRAY_STORAGE_INDEXING_TYPES: {

Ditto.
Comment 7 Yusuke Suzuki 2022-10-10 18:31:16 PDT
Comment on attachment 462917 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=462917&action=review

> Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:9704
>          default:

Ditto.
Comment 8 Mark Lam 2022-11-11 15:53:04 PST
d_degazio@apple.com:
Pull request: https://github.com/WebKit/WebKit/pull/5211
Comment 9 Mark Lam 2022-11-11 16:14:37 PST
*** Bug 246307 has been marked as a duplicate of this bug. ***
Comment 10 Mark Lam 2022-11-12 09:37:50 PST
Comment on attachment 462917 [details]
Patch

Let's land this via the github PR.
Comment 11 EWS 2022-11-12 14:55:14 PST
Committed 256611@main (104b2a1546ed): <https://commits.webkit.org/256611@main>

Reviewed commits have been landed. Closing PR #5211 and removing active labels.