source: webkit/trunk/Source/JavaScriptCore/dfg/DFGLICMPhase.cpp

Last change on this file was 279126, checked in by [email protected], 4 years ago

jitCompileAndSetHeuristics shouldn't return true when we fail to compile
https://bugs.webkit.org/show_bug.cgi?id=227155

Reviewed by Tadeu Zagallo.

JSTests:

  • microbenchmarks/interpreter-wasm.js:
  • microbenchmarks/memcpy-wasm-large.js:
  • microbenchmarks/memcpy-wasm-medium.js:
  • microbenchmarks/memcpy-wasm-small.js:
  • microbenchmarks/memcpy-wasm.js:
  • stress/wasm-error-message-cross-threads.js:

Source/JavaScriptCore:

jitCompileAndSetHeuristics should only return true when we've successfully
compiled a baseline JIT CodeBlock. However, with the rewrite to using a
unified JIT worklist, the code was changed to returning true when a
compilation finished, regardless of it being successful or not. This patch
fixes that error.

This bug was found by our existing executable allocation fuzzer, but at a low
hit rate. That fuzzer only ran a single test case. This patch also introduces
a new form of the executable fuzzer where we fail to allocate JIT code
randomly, and the crash manifests more reliably. And this patch also hooks
the new fuzzer into more JSC stress tests.

  • dfg/DFGLICMPhase.cpp:

(JSC::DFG::LICMPhase::run):

  • jit/ExecutableAllocationFuzz.cpp:

(JSC::doExecutableAllocationFuzzing):

  • jsc.cpp:

(runJSC):

  • llint/LLIntSlowPaths.cpp:

(JSC::LLInt::jitCompileAndSetHeuristics):
(JSC::LLInt::LLINT_SLOW_PATH_DECL):

  • runtime/OptionsList.h:

Source/WTF:

  • wtf/WeakRandom.h:

Tools:

  • Scripts/run-jsc-stress-tests:
File size: 20.2 KB
Line 
1/*
2 * Copyright (C) 2013-2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DFGLICMPhase.h"
28
29#if ENABLE(DFG_JIT)
30
31#include "DFGAbstractInterpreterInlines.h"
32#include "DFGAtTailAbstractState.h"
33#include "DFGClobberSet.h"
34#include "DFGClobberize.h"
35#include "DFGControlEquivalenceAnalysis.h"
36#include "DFGEdgeDominates.h"
37#include "DFGGraph.h"
38#include "DFGMayExit.h"
39#include "DFGNaturalLoops.h"
40#include "DFGPhase.h"
41#include "DFGSafeToExecute.h"
42#include "JSCInlines.h"
43
44namespace JSC { namespace DFG {
45
46class LICMPhase : public Phase {
47 static constexpr bool verbose = false;
48
49 using NaturalLoop = SSANaturalLoop;
50
51 struct LoopData {
52 ClobberSet writes;
53 BasicBlock* preHeader { nullptr };
54 };
55
56public:
57 LICMPhase(Graph& graph)
58 : Phase(graph, "LICM")
59 , m_state(graph)
60 , m_interpreter(graph, m_state)
61 {
62 }
63
64 bool run()
65 {
66 DFG_ASSERT(m_graph, nullptr, m_graph.m_form == SSA);
67
68 m_graph.ensureSSADominators();
69 m_graph.ensureSSANaturalLoops();
70 m_graph.ensureControlEquivalenceAnalysis();
71
72 if (verbose) {
73 dataLog("Graph before LICM:\n");
74 m_graph.dump();
75 }
76
77 m_data.resize(m_graph.m_ssaNaturalLoops->numLoops());
78
79 // Figure out the set of things each loop writes to, not including blocks that
80 // belong to inner loops. We fix this later.
81 for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
82 BasicBlock* block = m_graph.block(blockIndex);
83 if (!block)
84 continue;
85
86 // Skip blocks that are proved to not execute.
87 // FIXME: This shouldn't be needed.
88 // https://bugs.webkit.org/show_bug.cgi?id=128584
89 if (!block->cfaHasVisited)
90 continue;
91
92 const NaturalLoop* loop = m_graph.m_ssaNaturalLoops->innerMostLoopOf(block);
93 if (!loop)
94 continue;
95 LoopData& data = m_data[loop->index()];
96 for (auto* node : *block) {
97 // Don't look beyond parts of the code that definitely always exit.
98 // FIXME: This shouldn't be needed.
99 // https://bugs.webkit.org/show_bug.cgi?id=128584
100 if (node->op() == ForceOSRExit)
101 break;
102
103 addWrites(m_graph, node, data.writes);
104 }
105 }
106
107 // For each loop:
108 // - Identify its pre-header.
109 // - Make sure its outer loops know what it clobbers.
110 for (unsigned loopIndex = m_graph.m_ssaNaturalLoops->numLoops(); loopIndex--;) {
111 const NaturalLoop& loop = m_graph.m_ssaNaturalLoops->loop(loopIndex);
112 LoopData& data = m_data[loop.index()];
113
114 for (
115 const NaturalLoop* outerLoop = m_graph.m_ssaNaturalLoops->innerMostOuterLoop(loop);
116 outerLoop;
117 outerLoop = m_graph.m_ssaNaturalLoops->innerMostOuterLoop(*outerLoop))
118 m_data[outerLoop->index()].writes.addAll(data.writes);
119
120 BasicBlock* header = loop.header();
121 BasicBlock* preHeader = nullptr;
122 unsigned numberOfPreHeaders = 0; // We're cool if this is 1.
123
124 // This is guaranteed because we expect the CFG not to have unreachable code. Therefore, a
125 // loop header must have a predecessor. (Also, we don't allow the root block to be a loop,
126 // which cuts out the one other way of having a loop header with only one predecessor.)
127 DFG_ASSERT(m_graph, header->at(0), header->predecessors.size() > 1, header->predecessors.size());
128
129 for (unsigned i = header->predecessors.size(); i--;) {
130 BasicBlock* predecessor = header->predecessors[i];
131 if (m_graph.m_ssaDominators->dominates(header, predecessor))
132 continue;
133
134 preHeader = predecessor;
135 ++numberOfPreHeaders;
136 }
137
138 // We need to validate the pre-header. There are a bunch of things that could be wrong
139 // about it:
140 //
141 // - There might be more than one. This means that pre-header creation either did not run,
142 // or some CFG transformation destroyed the pre-headers.
143 //
144 // - It may not be legal to exit at the pre-header. That would be a real bummer. Currently,
145 // LICM assumes that it can always hoist checks. See
146 // https://bugs.webkit.org/show_bug.cgi?id=148545. Though even with that fixed, we anyway
147 // would need to check if it's OK to exit at the pre-header since if we can't then we
148 // would have to restrict hoisting to non-exiting nodes.
149
150 if (numberOfPreHeaders != 1)
151 continue;
152
153 // This is guaranteed because the header has multiple predecessors and critical edges are
154 // broken. Therefore the predecessors must all have one successor, which implies that they
155 // must end in a Jump.
156 DFG_ASSERT(m_graph, preHeader->terminal(), preHeader->terminal()->op() == Jump, preHeader->terminal()->op());
157
158 if (!preHeader->terminal()->origin.exitOK)
159 continue;
160
161 data.preHeader = preHeader;
162 }
163
164 m_graph.initializeNodeOwners();
165
166 // Walk all basic blocks that belong to loops, looking for hoisting opportunities.
167 // We try to hoist to the outer-most loop that permits it. Hoisting is valid if:
168 // - The node doesn't write anything.
169 // - The node doesn't read anything that the loop writes.
170 // - The preHeader is valid (i.e. it passed the validation above).
171 // - The preHeader's state at tail makes the node safe to execute.
172 // - The loop's children all belong to nodes that strictly dominate the loop header.
173 // - The preHeader's state at tail is still valid. This is mostly to save compile
174 // time and preserve some kind of sanity, if we hoist something that must exit.
175 //
176 // Also, we need to remember to:
177 // - Update the state-at-tail with the node we hoisted, so future hoist candidates
178 // know about any type checks we hoisted.
179 //
180 // For maximum profit, we walk blocks in DFS order to ensure that we generally
181 // tend to hoist dominators before dominatees.
182 Vector<const NaturalLoop*> loopStack;
183 bool changed = false;
184
185 WeakRandom random { Options::seedForLICMFuzzer() };
186
187 for (BasicBlock* block : m_graph.blocksInPreOrder()) {
188 if (!block->cfaHasVisited)
189 continue;
190
191 const NaturalLoop* loop = m_graph.m_ssaNaturalLoops->innerMostLoopOf(block);
192 if (!loop)
193 continue;
194
195 loopStack.shrink(0);
196 for (
197 const NaturalLoop* current = loop;
198 current;
199 current = m_graph.m_ssaNaturalLoops->innerMostOuterLoop(*current))
200 loopStack.append(current);
201
202 // Remember: the loop stack has the inner-most loop at index 0, so if we want
203 // to bias hoisting to outer loops then we need to use a reverse loop.
204
205 if (verbose) {
206 dataLog(
207 "Attempting to hoist out of block ", *block, " in loops:\n");
208 for (unsigned stackIndex = loopStack.size(); stackIndex--;) {
209 dataLog(
210 " ", *loopStack[stackIndex], ", which writes ",
211 m_data[loopStack[stackIndex]->index()].writes, "\n");
212 }
213 }
214
215 for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
216 Node*& nodeRef = block->at(nodeIndex);
217 if (nodeRef->op() == ForceOSRExit)
218 break;
219 for (unsigned stackIndex = loopStack.size(); stackIndex--;) {
220 if (UNLIKELY(Options::useLICMFuzzing())) {
221 bool shouldAttemptHoist = random.returnTrueWithProbability(Options::allowHoistingLICMProbability());
222 if (!shouldAttemptHoist)
223 continue;
224 }
225
226 changed |= attemptHoist(block, nodeRef, loopStack[stackIndex]);
227 }
228 }
229 }
230
231 return changed;
232 }
233
234private:
235 bool attemptHoist(BasicBlock* fromBlock, Node*& nodeRef, const NaturalLoop* loop)
236 {
237 Node* node = nodeRef;
238 LoopData& data = m_data[loop->index()];
239
240 if (!data.preHeader) {
241 if (verbose)
242 dataLog(" Not hoisting ", node, " because the pre-header is invalid.\n");
243 return false;
244 }
245
246 if (!data.preHeader->cfaDidFinish) {
247 if (verbose)
248 dataLog(" Not hoisting ", node, " because CFA is invalid.\n");
249 return false;
250 }
251
252 m_state.initializeTo(data.preHeader);
253 ASSERT(m_state.isValid());
254 NodeOrigin originalOrigin = node->origin;
255 bool canSpeculateBlindly = !m_graph.hasGlobalExitSite(originalOrigin.semantic, HoistingFailed);
256
257 // NOTE: We could just use BackwardsDominators here directly, since we already know that the
258 // preHeader dominates fromBlock. But we wouldn't get anything from being so clever, since
259 // dominance checks are O(1) and only a few integer compares.
260 bool isControlEquivalent = m_graph.m_controlEquivalenceAnalysis->dominatesEquivalently(data.preHeader, fromBlock);
261
262 bool addsBlindSpeculation = !isControlEquivalent;
263 NodeOrigin terminalOrigin = data.preHeader->terminal()->origin;
264 Vector<Node*, 2> hoistedNodes; // This is sorted in the program order they will appear in the basic block we're hoisting to.
265
266 auto insertHoistedNode = [&] (Node* node) {
267 data.preHeader->insertBeforeTerminal(node);
268 node->owner = data.preHeader;
269 node->origin = terminalOrigin.withSemantic(node->origin.semantic);
270 node->origin.wasHoisted |= addsBlindSpeculation;
271 hoistedNodes.append(node);
272 };
273
274 auto updateAbstractState = [&] {
275 auto invalidate = [&] (const NaturalLoop* loop) {
276 LoopData& data = m_data[loop->index()];
277 data.preHeader->cfaDidFinish = false;
278
279 for (unsigned bodyIndex = loop->size(); bodyIndex--;) {
280 BasicBlock* block = loop->at(bodyIndex);
281 if (block != data.preHeader)
282 block->cfaHasVisited = false;
283 block->cfaDidFinish = false;
284 }
285 };
286
287 // We can trust what AI proves about edge proof statuses when hoisting to the preheader.
288 m_state.trustEdgeProofs();
289 for (unsigned i = 0; i < hoistedNodes.size(); ++i) {
290 if (!m_interpreter.execute(hoistedNodes[i])) {
291 invalidate(loop);
292 return;
293 }
294 }
295
296 // However, when walking various inner loops below, the proof status of
297 // an edge may be trivially true, even if it's not true in the preheader
298 // we hoist to. We don't allow the below node executions to change the
299 // state of edge proofs. An example of where a proof is trivially true
300 // is if we have two loops, L1 and L2, where L2 is nested inside L1. The
301 // header for L1 dominates L2. We hoist a Check from L1's header into L1's
302 // preheader. However, inside L2's preheader, we can't trust that AI will
303 // tell us this edge is proven. It's proven in L2's preheader because L2
304 // is dominated by L1's header. However, the edge is not guaranteed to be
305 // proven inside L1's preheader.
306 m_state.dontTrustEdgeProofs();
307
308 // Modify the states at the end of the preHeader of the loop we hoisted to,
309 // and all pre-headers inside the loop. This isn't a stability bottleneck right now
310 // because most loops are small and most blocks belong to few loops.
311 for (unsigned bodyIndex = loop->size(); bodyIndex--;) {
312 BasicBlock* subBlock = loop->at(bodyIndex);
313 const NaturalLoop* subLoop = m_graph.m_ssaNaturalLoops->headerOf(subBlock);
314 if (!subLoop)
315 continue;
316 BasicBlock* subPreHeader = m_data[subLoop->index()].preHeader;
317 // We may not have given this loop a pre-header because either it didn't have exitOK
318 // or the header had multiple predecessors that it did not dominate. In that case the
319 // loop wouldn't be a hoisting candidate anyway, so we don't have to do anything.
320 if (!subPreHeader)
321 continue;
322 // The pre-header's tail may be unreachable, in which case we have nothing to do.
323 if (!subPreHeader->cfaDidFinish)
324 continue;
325 // We handled this above.
326 if (subPreHeader == data.preHeader)
327 continue;
328 m_state.initializeTo(subPreHeader);
329 for (unsigned i = 0; i < hoistedNodes.size(); ++i) {
330 if (!m_interpreter.execute(hoistedNodes[i])) {
331 invalidate(subLoop);
332 break;
333 }
334 }
335 }
336 };
337
338 auto tryHoistChecks = [&] {
339 if (addsBlindSpeculation && !canSpeculateBlindly)
340 return false;
341
342 ASSERT(hoistedNodes.isEmpty());
343
344 Vector<Edge, 3> checks;
345 m_graph.doToChildren(node, [&] (Edge edge) {
346 if (!m_graph.m_ssaDominators->dominates(edge.node()->owner, data.preHeader))
347 return;
348
349 if (!edge.willHaveCheck())
350 return;
351
352 if ((m_state.forNode(edge).m_type & SpecEmpty) && checkMayCrashIfInputIsEmpty(edge.useKind())) {
353 if (!canSpeculateBlindly)
354 return;
355 Node* checkNotEmpty = m_graph.addNode(CheckNotEmpty, originalOrigin, Edge(edge.node(), UntypedUse));
356 insertHoistedNode(checkNotEmpty);
357 }
358
359 checks.append(edge);
360 });
361
362 if (checks.isEmpty())
363 return false;
364
365 AdjacencyList children;
366 NodeType checkOp = Check;
367 if (checks.size() <= AdjacencyList::Size) {
368 children = AdjacencyList(AdjacencyList::Fixed);
369 for (unsigned i = 0; i < checks.size(); ++i)
370 children.setChild(i, checks[i]);
371 } else {
372 checkOp = CheckVarargs;
373 unsigned firstChild = m_graph.m_varArgChildren.size();
374 for (Edge edge : checks)
375 m_graph.m_varArgChildren.append(edge);
376 children = AdjacencyList(AdjacencyList::Variable, firstChild, checks.size());
377 }
378
379 Node* check = m_graph.addNode(checkOp, originalOrigin, children);
380 insertHoistedNode(check);
381 updateAbstractState();
382
383 if (verbose)
384 dataLogLn(" Hoisted some checks from ", node, " and created a new Check ", check, ". Hoisted from ", *fromBlock, " to ", *data.preHeader);
385
386 return true;
387 };
388
389 if (!edgesDominate(m_graph, node, data.preHeader)) {
390 if (verbose) {
391 dataLog(
392 " Not hoisting ", node, " because it isn't loop invariant.\n");
393 }
394 return tryHoistChecks();
395 }
396
397 if (doesWrites(m_graph, node)) {
398 if (verbose)
399 dataLog(" Not hoisting ", node, " because it writes things.\n");
400 return tryHoistChecks();
401 }
402
403 // It's not safe to consult the AbstractState inside mayExit until we prove all edges
404 // dominate the pre-header we're hoisting to. We are more conservative above when assigning
405 // to this variable since we hadn't yet proven all edges dominate the pre-header. Above, we
406 // just assume mayExit is true. We refine that here since we can now consult the AbstractState.
407 addsBlindSpeculation = mayExit(m_graph, node, m_state) && !isControlEquivalent;
408
409 if (readsOverlap(m_graph, node, data.writes)) {
410 if (verbose) {
411 dataLog(
412 " Not hoisting ", node,
413 " because it reads things that the loop writes.\n");
414 }
415 return tryHoistChecks();
416 }
417
418 if (addsBlindSpeculation && !canSpeculateBlindly) {
419 if (verbose) {
420 dataLog(
421 " Not hoisting ", node, " because it may exit and the pre-header (",
422 *data.preHeader, ") is not control equivalent to the node's original block (",
423 *fromBlock, ") and hoisting had previously failed.\n");
424 }
425 return tryHoistChecks();
426 }
427
428 if (!safeToExecute(m_state, m_graph, node)) {
429 // See if we can rescue the situation by inserting blind speculations.
430 bool ignoreEmptyChildren = true;
431 if (canSpeculateBlindly
432 && safeToExecute(m_state, m_graph, node, ignoreEmptyChildren)) {
433 if (verbose) {
434 dataLog(
435 " Rescuing hoisting by inserting empty checks.\n");
436 }
437 m_graph.doToChildren(
438 node,
439 [&] (Edge& edge) {
440 if (!(m_state.forNode(edge).m_type & SpecEmpty))
441 return;
442
443 Node* check = m_graph.addNode(CheckNotEmpty, originalOrigin, Edge(edge.node(), UntypedUse));
444 insertHoistedNode(check);
445 });
446 } else {
447 if (verbose) {
448 dataLog(
449 " Not hoisting ", node, " because it isn't safe to execute.\n");
450 }
451 return tryHoistChecks();
452 }
453 }
454
455 if (verbose) {
456 dataLog(
457 " Hoisting ", node, " from ", *fromBlock, " to ", *data.preHeader,
458 "\n");
459 }
460
461 insertHoistedNode(node);
462 updateAbstractState();
463
464 if (node->flags() & NodeHasVarArgs)
465 nodeRef = m_graph.addNode(CheckVarargs, originalOrigin, m_graph.copyVarargChildren(node));
466 else
467 nodeRef = m_graph.addNode(Check, originalOrigin, node->children);
468
469 return true;
470 }
471
472 AtTailAbstractState m_state;
473 AbstractInterpreter<AtTailAbstractState> m_interpreter;
474 Vector<LoopData> m_data;
475};
476
477bool performLICM(Graph& graph)
478{
479 return runPhase<LICMPhase>(graph);
480}
481
482} } // namespace JSC::DFG
483
484#endif // ENABLE(DFG_JIT)
485
Note: See TracBrowser for help on using the repository browser.