|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "jit/ParallelSafetyAnalysis.h" |
|
8 |
|
9 #include "jit/Ion.h" |
|
10 #include "jit/IonAnalysis.h" |
|
11 #include "jit/IonSpewer.h" |
|
12 #include "jit/MIR.h" |
|
13 #include "jit/MIRGenerator.h" |
|
14 #include "jit/MIRGraph.h" |
|
15 #include "jit/UnreachableCodeElimination.h" |
|
16 |
|
17 #include "jsinferinlines.h" |
|
18 #include "jsobjinlines.h" |
|
19 |
|
20 using namespace js; |
|
21 using namespace jit; |
|
22 |
|
23 using parallel::Spew; |
|
24 using parallel::SpewMIR; |
|
25 using parallel::SpewCompile; |
|
26 |
|
27 #define SAFE_OP(op) \ |
|
28 virtual bool visit##op(M##op *prop) { return true; } |
|
29 |
|
30 #define CUSTOM_OP(op) \ |
|
31 virtual bool visit##op(M##op *prop); |
|
32 |
|
33 #define DROP_OP(op) \ |
|
34 virtual bool visit##op(M##op *ins) { \ |
|
35 MBasicBlock *block = ins->block(); \ |
|
36 block->discard(ins); \ |
|
37 return true; \ |
|
38 } |
|
39 |
|
40 #define PERMIT(T) (1 << T) |
|
41 |
|
42 #define PERMIT_INT32 (PERMIT(MIRType_Int32)) |
|
43 #define PERMIT_NUMERIC (PERMIT(MIRType_Int32) | PERMIT(MIRType_Double)) |
|
44 |
|
45 #define SPECIALIZED_OP(op, flags) \ |
|
46 virtual bool visit##op(M##op *ins) { \ |
|
47 return visitSpecializedInstruction(ins, ins->specialization(), flags); \ |
|
48 } |
|
49 |
|
50 #define UNSAFE_OP(op) \ |
|
51 virtual bool visit##op(M##op *ins) { \ |
|
52 SpewMIR(ins, "Unsafe"); \ |
|
53 return markUnsafe(); \ |
|
54 } |
|
55 |
|
56 #define WRITE_GUARDED_OP(op, obj) \ |
|
57 virtual bool visit##op(M##op *prop) { \ |
|
58 return insertWriteGuard(prop, prop->obj()); \ |
|
59 } |
|
60 |
|
61 #define MAYBE_WRITE_GUARDED_OP(op, obj) \ |
|
62 virtual bool visit##op(M##op *prop) { \ |
|
63 if (prop->racy()) \ |
|
64 return true; \ |
|
65 return insertWriteGuard(prop, prop->obj()); \ |
|
66 } |
|
67 |
|
68 class ParallelSafetyVisitor : public MInstructionVisitor |
|
69 { |
|
70 MIRGraph &graph_; |
|
71 bool unsafe_; |
|
72 MDefinition *cx_; |
|
73 |
|
74 bool insertWriteGuard(MInstruction *writeInstruction, MDefinition *valueBeingWritten); |
|
75 |
|
76 bool replaceWithNewPar(MInstruction *newInstruction, JSObject *templateObject); |
|
77 bool replace(MInstruction *oldInstruction, MInstruction *replacementInstruction); |
|
78 |
|
79 bool visitSpecializedInstruction(MInstruction *ins, MIRType spec, uint32_t flags); |
|
80 |
|
81 // Intended for use in a visitXyz() instruction like "return |
|
82 // markUnsafe()". Sets the unsafe flag and returns true (since |
|
83 // this does not indicate an unrecoverable compilation failure). |
|
84 bool markUnsafe() { |
|
85 JS_ASSERT(!unsafe_); |
|
86 unsafe_ = true; |
|
87 return true; |
|
88 } |
|
89 |
|
90 TempAllocator &alloc() const { |
|
91 return graph_.alloc(); |
|
92 } |
|
93 |
|
94 public: |
|
95 ParallelSafetyVisitor(MIRGraph &graph) |
|
96 : graph_(graph), |
|
97 unsafe_(false), |
|
98 cx_(nullptr) |
|
99 { } |
|
100 |
|
101 void clearUnsafe() { unsafe_ = false; } |
|
102 bool unsafe() { return unsafe_; } |
|
103 MDefinition *ForkJoinContext() { |
|
104 if (!cx_) |
|
105 cx_ = graph_.forkJoinContext(); |
|
106 return cx_; |
|
107 } |
|
108 |
|
109 bool convertToBailout(MBasicBlock *block, MInstruction *ins); |
|
110 |
|
111 // I am taking the policy of blacklisting everything that's not |
|
112 // obviously safe for now. We can loosen as we need. |
|
113 |
|
114 SAFE_OP(Constant) |
|
115 UNSAFE_OP(CloneLiteral) |
|
116 SAFE_OP(Parameter) |
|
117 SAFE_OP(Callee) |
|
118 SAFE_OP(TableSwitch) |
|
119 SAFE_OP(Goto) |
|
120 SAFE_OP(Test) |
|
121 SAFE_OP(Compare) |
|
122 SAFE_OP(Phi) |
|
123 SAFE_OP(Beta) |
|
124 UNSAFE_OP(OsrValue) |
|
125 UNSAFE_OP(OsrScopeChain) |
|
126 UNSAFE_OP(OsrReturnValue) |
|
127 UNSAFE_OP(OsrArgumentsObject) |
|
128 UNSAFE_OP(ReturnFromCtor) |
|
129 CUSTOM_OP(CheckOverRecursed) |
|
130 UNSAFE_OP(DefVar) |
|
131 UNSAFE_OP(DefFun) |
|
132 UNSAFE_OP(CreateThis) |
|
133 CUSTOM_OP(CreateThisWithTemplate) |
|
134 UNSAFE_OP(CreateThisWithProto) |
|
135 UNSAFE_OP(CreateArgumentsObject) |
|
136 UNSAFE_OP(GetArgumentsObjectArg) |
|
137 UNSAFE_OP(SetArgumentsObjectArg) |
|
138 UNSAFE_OP(ComputeThis) |
|
139 UNSAFE_OP(LoadArrowThis) |
|
140 CUSTOM_OP(Call) |
|
141 UNSAFE_OP(ApplyArgs) |
|
142 UNSAFE_OP(ArraySplice) |
|
143 UNSAFE_OP(Bail) |
|
144 UNSAFE_OP(AssertFloat32) |
|
145 UNSAFE_OP(GetDynamicName) |
|
146 UNSAFE_OP(FilterArgumentsOrEval) |
|
147 UNSAFE_OP(CallDirectEval) |
|
148 SAFE_OP(BitNot) |
|
149 SAFE_OP(TypeOf) |
|
150 UNSAFE_OP(ToId) |
|
151 SAFE_OP(BitAnd) |
|
152 SAFE_OP(BitOr) |
|
153 SAFE_OP(BitXor) |
|
154 SAFE_OP(Lsh) |
|
155 SAFE_OP(Rsh) |
|
156 SAFE_OP(Ursh) |
|
157 SPECIALIZED_OP(MinMax, PERMIT_NUMERIC) |
|
158 SAFE_OP(Abs) |
|
159 SAFE_OP(Sqrt) |
|
160 UNSAFE_OP(Atan2) |
|
161 UNSAFE_OP(Hypot) |
|
162 CUSTOM_OP(MathFunction) |
|
163 SPECIALIZED_OP(Add, PERMIT_NUMERIC) |
|
164 SPECIALIZED_OP(Sub, PERMIT_NUMERIC) |
|
165 SPECIALIZED_OP(Mul, PERMIT_NUMERIC) |
|
166 SPECIALIZED_OP(Div, PERMIT_NUMERIC) |
|
167 SPECIALIZED_OP(Mod, PERMIT_NUMERIC) |
|
168 CUSTOM_OP(Concat) |
|
169 SAFE_OP(ConcatPar) |
|
170 UNSAFE_OP(CharCodeAt) |
|
171 UNSAFE_OP(FromCharCode) |
|
172 UNSAFE_OP(StringSplit) |
|
173 SAFE_OP(Return) |
|
174 CUSTOM_OP(Throw) |
|
175 SAFE_OP(Box) // Boxing just creates a JSVal, doesn't alloc. |
|
176 SAFE_OP(Unbox) |
|
177 SAFE_OP(GuardObject) |
|
178 SAFE_OP(ToDouble) |
|
179 SAFE_OP(ToFloat32) |
|
180 SAFE_OP(ToInt32) |
|
181 SAFE_OP(TruncateToInt32) |
|
182 SAFE_OP(MaybeToDoubleElement) |
|
183 CUSTOM_OP(ToString) |
|
184 SAFE_OP(NewSlots) |
|
185 CUSTOM_OP(NewArray) |
|
186 CUSTOM_OP(NewObject) |
|
187 CUSTOM_OP(NewCallObject) |
|
188 CUSTOM_OP(NewRunOnceCallObject) |
|
189 CUSTOM_OP(NewDerivedTypedObject) |
|
190 UNSAFE_OP(InitElem) |
|
191 UNSAFE_OP(InitElemGetterSetter) |
|
192 UNSAFE_OP(MutateProto) |
|
193 UNSAFE_OP(InitProp) |
|
194 UNSAFE_OP(InitPropGetterSetter) |
|
195 SAFE_OP(Start) |
|
196 UNSAFE_OP(OsrEntry) |
|
197 SAFE_OP(Nop) |
|
198 UNSAFE_OP(RegExp) |
|
199 CUSTOM_OP(Lambda) |
|
200 UNSAFE_OP(LambdaArrow) |
|
201 UNSAFE_OP(ImplicitThis) |
|
202 SAFE_OP(Slots) |
|
203 SAFE_OP(Elements) |
|
204 SAFE_OP(ConstantElements) |
|
205 SAFE_OP(LoadSlot) |
|
206 WRITE_GUARDED_OP(StoreSlot, slots) |
|
207 SAFE_OP(FunctionEnvironment) // just a load of func env ptr |
|
208 SAFE_OP(FilterTypeSet) |
|
209 SAFE_OP(TypeBarrier) // causes a bailout if the type is not found: a-ok with us |
|
210 SAFE_OP(MonitorTypes) // causes a bailout if the type is not found: a-ok with us |
|
211 UNSAFE_OP(PostWriteBarrier) |
|
212 SAFE_OP(GetPropertyCache) |
|
213 SAFE_OP(GetPropertyPolymorphic) |
|
214 UNSAFE_OP(SetPropertyPolymorphic) |
|
215 SAFE_OP(GetElementCache) |
|
216 WRITE_GUARDED_OP(SetElementCache, object) |
|
217 UNSAFE_OP(BindNameCache) |
|
218 SAFE_OP(GuardShape) |
|
219 SAFE_OP(GuardObjectType) |
|
220 SAFE_OP(GuardObjectIdentity) |
|
221 SAFE_OP(GuardClass) |
|
222 SAFE_OP(AssertRange) |
|
223 SAFE_OP(ArrayLength) |
|
224 WRITE_GUARDED_OP(SetArrayLength, elements) |
|
225 SAFE_OP(TypedArrayLength) |
|
226 SAFE_OP(TypedArrayElements) |
|
227 SAFE_OP(TypedObjectElements) |
|
228 SAFE_OP(SetTypedObjectOffset) |
|
229 SAFE_OP(InitializedLength) |
|
230 WRITE_GUARDED_OP(SetInitializedLength, elements) |
|
231 SAFE_OP(Not) |
|
232 SAFE_OP(NeuterCheck) |
|
233 SAFE_OP(BoundsCheck) |
|
234 SAFE_OP(BoundsCheckLower) |
|
235 SAFE_OP(LoadElement) |
|
236 SAFE_OP(LoadElementHole) |
|
237 MAYBE_WRITE_GUARDED_OP(StoreElement, elements) |
|
238 WRITE_GUARDED_OP(StoreElementHole, elements) |
|
239 UNSAFE_OP(ArrayPopShift) |
|
240 UNSAFE_OP(ArrayPush) |
|
241 SAFE_OP(LoadTypedArrayElement) |
|
242 SAFE_OP(LoadTypedArrayElementHole) |
|
243 SAFE_OP(LoadTypedArrayElementStatic) |
|
244 MAYBE_WRITE_GUARDED_OP(StoreTypedArrayElement, elements) |
|
245 WRITE_GUARDED_OP(StoreTypedArrayElementHole, elements) |
|
246 UNSAFE_OP(StoreTypedArrayElementStatic) |
|
247 UNSAFE_OP(ClampToUint8) |
|
248 SAFE_OP(LoadFixedSlot) |
|
249 WRITE_GUARDED_OP(StoreFixedSlot, object) |
|
250 UNSAFE_OP(CallGetProperty) |
|
251 UNSAFE_OP(GetNameCache) |
|
252 UNSAFE_OP(CallGetIntrinsicValue) |
|
253 UNSAFE_OP(CallsiteCloneCache) |
|
254 UNSAFE_OP(CallGetElement) |
|
255 WRITE_GUARDED_OP(CallSetElement, object) |
|
256 UNSAFE_OP(CallInitElementArray) |
|
257 WRITE_GUARDED_OP(CallSetProperty, object) |
|
258 UNSAFE_OP(DeleteProperty) |
|
259 UNSAFE_OP(DeleteElement) |
|
260 WRITE_GUARDED_OP(SetPropertyCache, object) |
|
261 UNSAFE_OP(IteratorStart) |
|
262 UNSAFE_OP(IteratorNext) |
|
263 UNSAFE_OP(IteratorMore) |
|
264 UNSAFE_OP(IteratorEnd) |
|
265 SAFE_OP(StringLength) |
|
266 SAFE_OP(ArgumentsLength) |
|
267 SAFE_OP(GetFrameArgument) |
|
268 UNSAFE_OP(SetFrameArgument) |
|
269 UNSAFE_OP(RunOncePrologue) |
|
270 CUSTOM_OP(Rest) |
|
271 SAFE_OP(RestPar) |
|
272 SAFE_OP(Floor) |
|
273 SAFE_OP(Round) |
|
274 UNSAFE_OP(InstanceOf) |
|
275 CUSTOM_OP(InterruptCheck) |
|
276 SAFE_OP(ForkJoinContext) |
|
277 SAFE_OP(ForkJoinGetSlice) |
|
278 SAFE_OP(NewPar) |
|
279 SAFE_OP(NewDenseArrayPar) |
|
280 SAFE_OP(NewCallObjectPar) |
|
281 SAFE_OP(LambdaPar) |
|
282 SAFE_OP(AbortPar) |
|
283 UNSAFE_OP(ArrayConcat) |
|
284 UNSAFE_OP(GetDOMProperty) |
|
285 UNSAFE_OP(GetDOMMember) |
|
286 UNSAFE_OP(SetDOMProperty) |
|
287 UNSAFE_OP(NewStringObject) |
|
288 UNSAFE_OP(Random) |
|
289 SAFE_OP(Pow) |
|
290 SAFE_OP(PowHalf) |
|
291 UNSAFE_OP(RegExpTest) |
|
292 UNSAFE_OP(RegExpExec) |
|
293 UNSAFE_OP(RegExpReplace) |
|
294 UNSAFE_OP(StringReplace) |
|
295 UNSAFE_OP(CallInstanceOf) |
|
296 UNSAFE_OP(ProfilerStackOp) |
|
297 UNSAFE_OP(GuardString) |
|
298 UNSAFE_OP(NewDeclEnvObject) |
|
299 UNSAFE_OP(In) |
|
300 UNSAFE_OP(InArray) |
|
301 SAFE_OP(GuardThreadExclusive) |
|
302 SAFE_OP(InterruptCheckPar) |
|
303 SAFE_OP(CheckOverRecursedPar) |
|
304 SAFE_OP(FunctionDispatch) |
|
305 SAFE_OP(TypeObjectDispatch) |
|
306 SAFE_OP(IsCallable) |
|
307 SAFE_OP(HaveSameClass) |
|
308 SAFE_OP(HasClass) |
|
309 UNSAFE_OP(EffectiveAddress) |
|
310 UNSAFE_OP(AsmJSUnsignedToDouble) |
|
311 UNSAFE_OP(AsmJSUnsignedToFloat32) |
|
312 UNSAFE_OP(AsmJSNeg) |
|
313 UNSAFE_OP(AsmJSLoadHeap) |
|
314 UNSAFE_OP(AsmJSStoreHeap) |
|
315 UNSAFE_OP(AsmJSLoadGlobalVar) |
|
316 UNSAFE_OP(AsmJSStoreGlobalVar) |
|
317 UNSAFE_OP(AsmJSLoadFuncPtr) |
|
318 UNSAFE_OP(AsmJSLoadFFIFunc) |
|
319 UNSAFE_OP(AsmJSReturn) |
|
320 UNSAFE_OP(AsmJSVoidReturn) |
|
321 UNSAFE_OP(AsmJSPassStackArg) |
|
322 UNSAFE_OP(AsmJSParameter) |
|
323 UNSAFE_OP(AsmJSCall) |
|
324 DROP_OP(RecompileCheck) |
|
325 |
|
326 // It looks like this could easily be made safe: |
|
327 UNSAFE_OP(ConvertElementsToDoubles) |
|
328 }; |
|
329 |
|
330 bool |
|
331 ParallelSafetyAnalysis::analyze() |
|
332 { |
|
333 // Walk the basic blocks in a DFS. When we encounter a block with an |
|
334 // unsafe instruction, then we know that this block will bailout when |
|
335 // executed. Therefore, we replace the block. |
|
336 // |
|
337 // We don't need a worklist, though, because the graph is sorted |
|
338 // in RPO. Therefore, we just use the marked flags to tell us |
|
339 // when we visited some predecessor of the current block. |
|
340 ParallelSafetyVisitor visitor(graph_); |
|
341 graph_.entryBlock()->mark(); // Note: in par. exec., we never enter from OSR. |
|
342 uint32_t marked = 0; |
|
343 for (ReversePostorderIterator block(graph_.rpoBegin()); block != graph_.rpoEnd(); block++) { |
|
344 if (mir_->shouldCancel("ParallelSafetyAnalysis")) |
|
345 return false; |
|
346 |
|
347 if (block->isMarked()) { |
|
348 // Iterate through and transform the instructions. Stop |
|
349 // if we encounter an inherently unsafe operation, in |
|
350 // which case we will transform this block into a bailout |
|
351 // block. |
|
352 MInstruction *instr = nullptr; |
|
353 for (MInstructionIterator ins(block->begin()); |
|
354 ins != block->end() && !visitor.unsafe();) |
|
355 { |
|
356 if (mir_->shouldCancel("ParallelSafetyAnalysis")) |
|
357 return false; |
|
358 |
|
359 // We may be removing or replacing the current |
|
360 // instruction, so advance `ins` now. Remember the |
|
361 // last instr. we looked at for use later if it should |
|
362 // prove unsafe. |
|
363 instr = *ins++; |
|
364 |
|
365 if (!instr->accept(&visitor)) { |
|
366 SpewMIR(instr, "Unaccepted"); |
|
367 return false; |
|
368 } |
|
369 } |
|
370 |
|
371 if (!visitor.unsafe()) { |
|
372 // Count the number of reachable blocks. |
|
373 marked++; |
|
374 |
|
375 // Block consists of only safe instructions. Visit its successors. |
|
376 for (uint32_t i = 0; i < block->numSuccessors(); i++) |
|
377 block->getSuccessor(i)->mark(); |
|
378 } else { |
|
379 // Block contains an unsafe instruction. That means that once |
|
380 // we enter this block, we are guaranteed to bailout. |
|
381 |
|
382 // If this is the entry block, then there is no point |
|
383 // in even trying to execute this function as it will |
|
384 // always bailout. |
|
385 if (*block == graph_.entryBlock()) { |
|
386 Spew(SpewCompile, "Entry block contains unsafe MIR"); |
|
387 return false; |
|
388 } |
|
389 |
|
390 // Otherwise, create a replacement that will. |
|
391 if (!visitor.convertToBailout(*block, instr)) |
|
392 return false; |
|
393 |
|
394 JS_ASSERT(!block->isMarked()); |
|
395 } |
|
396 } |
|
397 } |
|
398 |
|
399 Spew(SpewCompile, "Safe"); |
|
400 IonSpewPass("ParallelSafetyAnalysis"); |
|
401 |
|
402 UnreachableCodeElimination uce(mir_, graph_); |
|
403 if (!uce.removeUnmarkedBlocks(marked)) |
|
404 return false; |
|
405 IonSpewPass("UCEAfterParallelSafetyAnalysis"); |
|
406 AssertExtendedGraphCoherency(graph_); |
|
407 |
|
408 if (!removeResumePointOperands()) |
|
409 return false; |
|
410 IonSpewPass("RemoveResumePointOperands"); |
|
411 AssertExtendedGraphCoherency(graph_); |
|
412 |
|
413 return true; |
|
414 } |
|
415 |
|
416 bool |
|
417 ParallelSafetyAnalysis::removeResumePointOperands() |
|
418 { |
|
419 // In parallel exec mode, nothing is effectful, therefore we do |
|
420 // not need to reconstruct interpreter state and can simply |
|
421 // bailout by returning a special code. Ideally we'd either |
|
422 // remove the unused resume points or else never generate them in |
|
423 // the first place, but I encountered various assertions and |
|
424 // crashes attempting to do that, so for the time being I simply |
|
425 // replace their operands with undefined. This prevents them from |
|
426 // interfering with DCE and other optimizations. It is also *necessary* |
|
427 // to handle cases like this: |
|
428 // |
|
429 // foo(a, b, c.bar()) |
|
430 // |
|
431 // where `foo` was deemed to be an unsafe function to call. This |
|
432 // is because without neutering the ResumePoints, they would still |
|
433 // refer to the MPassArg nodes generated for the call to foo(). |
|
434 // But the call to foo() is dead and has been removed, leading to |
|
435 // an inconsistent IR and assertions at codegen time. |
|
436 |
|
437 MConstant *udef = nullptr; |
|
438 for (ReversePostorderIterator block(graph_.rpoBegin()); block != graph_.rpoEnd(); block++) { |
|
439 if (udef) |
|
440 replaceOperandsOnResumePoint(block->entryResumePoint(), udef); |
|
441 |
|
442 for (MInstructionIterator ins(block->begin()); ins != block->end(); ins++) { |
|
443 if (ins->isStart()) { |
|
444 JS_ASSERT(udef == nullptr); |
|
445 udef = MConstant::New(graph_.alloc(), UndefinedValue()); |
|
446 block->insertAfter(*ins, udef); |
|
447 } else if (udef) { |
|
448 if (MResumePoint *resumePoint = ins->resumePoint()) |
|
449 replaceOperandsOnResumePoint(resumePoint, udef); |
|
450 } |
|
451 } |
|
452 } |
|
453 return true; |
|
454 } |
|
455 |
|
456 void |
|
457 ParallelSafetyAnalysis::replaceOperandsOnResumePoint(MResumePoint *resumePoint, |
|
458 MDefinition *withDef) |
|
459 { |
|
460 for (size_t i = 0, e = resumePoint->numOperands(); i < e; i++) |
|
461 resumePoint->replaceOperand(i, withDef); |
|
462 } |
|
463 |
|
464 bool |
|
465 ParallelSafetyVisitor::convertToBailout(MBasicBlock *block, MInstruction *ins) |
|
466 { |
|
467 JS_ASSERT(unsafe()); // `block` must have contained unsafe items |
|
468 JS_ASSERT(block->isMarked()); // `block` must have been reachable to get here |
|
469 |
|
470 // Clear the unsafe flag for subsequent blocks. |
|
471 clearUnsafe(); |
|
472 |
|
473 // This block is no longer reachable. |
|
474 block->unmark(); |
|
475 |
|
476 // Create a bailout block for each predecessor. In principle, we |
|
477 // only need one bailout block--in fact, only one per graph! But I |
|
478 // found this approach easier to implement given the design of the |
|
479 // MIR Graph construction routines. Besides, most often `block` |
|
480 // has only one predecessor. Also, using multiple blocks helps to |
|
481 // keep the PC information more accurate (though replacing `block` |
|
482 // with exactly one bailout would be just as good). |
|
483 for (size_t i = 0; i < block->numPredecessors(); i++) { |
|
484 MBasicBlock *pred = block->getPredecessor(i); |
|
485 |
|
486 // We only care about incoming edges from reachable predecessors. |
|
487 if (!pred->isMarked()) |
|
488 continue; |
|
489 |
|
490 // create bailout block to insert on this edge |
|
491 MBasicBlock *bailBlock = MBasicBlock::NewAbortPar(graph_, block->info(), pred, |
|
492 block->pc(), |
|
493 block->entryResumePoint()); |
|
494 if (!bailBlock) |
|
495 return false; |
|
496 |
|
497 // if `block` had phis, we are replacing it with `bailBlock` which does not |
|
498 if (pred->successorWithPhis() == block) |
|
499 pred->setSuccessorWithPhis(nullptr, 0); |
|
500 |
|
501 // redirect the predecessor to the bailout block |
|
502 uint32_t succIdx = pred->getSuccessorIndex(block); |
|
503 pred->replaceSuccessor(succIdx, bailBlock); |
|
504 |
|
505 // Insert the bailout block after `block` in the execution |
|
506 // order. This should satisfy the RPO requirements and |
|
507 // moreover ensures that we will visit this block in our outer |
|
508 // walk, thus allowing us to keep the count of marked blocks |
|
509 // accurate. |
|
510 graph_.insertBlockAfter(block, bailBlock); |
|
511 bailBlock->mark(); |
|
512 } |
|
513 |
|
514 return true; |
|
515 } |
|
516 |
|
517 ///////////////////////////////////////////////////////////////////////////// |
|
518 // Memory allocation |
|
519 // |
|
520 // Simple memory allocation opcodes---those which ultimately compile |
|
521 // down to a (possibly inlined) invocation of NewGCThing()---are |
|
522 // replaced with MNewPar, which is supplied with the thread context. |
|
523 // These allocations will take place using per-helper-thread arenas. |
|
524 |
|
525 bool |
|
526 ParallelSafetyVisitor::visitCreateThisWithTemplate(MCreateThisWithTemplate *ins) |
|
527 { |
|
528 return replaceWithNewPar(ins, ins->templateObject()); |
|
529 } |
|
530 |
|
531 bool |
|
532 ParallelSafetyVisitor::visitNewCallObject(MNewCallObject *ins) |
|
533 { |
|
534 replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins)); |
|
535 return true; |
|
536 } |
|
537 |
|
538 bool |
|
539 ParallelSafetyVisitor::visitNewRunOnceCallObject(MNewRunOnceCallObject *ins) |
|
540 { |
|
541 replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins)); |
|
542 return true; |
|
543 } |
|
544 |
|
545 bool |
|
546 ParallelSafetyVisitor::visitLambda(MLambda *ins) |
|
547 { |
|
548 if (ins->info().singletonType || ins->info().useNewTypeForClone) { |
|
549 // slow path: bail on parallel execution. |
|
550 return markUnsafe(); |
|
551 } |
|
552 |
|
553 // fast path: replace with LambdaPar op |
|
554 replace(ins, MLambdaPar::New(alloc(), ForkJoinContext(), ins)); |
|
555 return true; |
|
556 } |
|
557 |
|
558 bool |
|
559 ParallelSafetyVisitor::visitNewObject(MNewObject *newInstruction) |
|
560 { |
|
561 if (newInstruction->shouldUseVM()) { |
|
562 SpewMIR(newInstruction, "should use VM"); |
|
563 return markUnsafe(); |
|
564 } |
|
565 |
|
566 return replaceWithNewPar(newInstruction, newInstruction->templateObject()); |
|
567 } |
|
568 |
|
569 bool |
|
570 ParallelSafetyVisitor::visitNewArray(MNewArray *newInstruction) |
|
571 { |
|
572 if (newInstruction->shouldUseVM()) { |
|
573 SpewMIR(newInstruction, "should use VM"); |
|
574 return markUnsafe(); |
|
575 } |
|
576 |
|
577 return replaceWithNewPar(newInstruction, newInstruction->templateObject()); |
|
578 } |
|
579 |
|
580 bool |
|
581 ParallelSafetyVisitor::visitNewDerivedTypedObject(MNewDerivedTypedObject *ins) |
|
582 { |
|
583 // FIXME(Bug 984090) -- There should really be a parallel-safe |
|
584 // version of NewDerivedTypedObject. However, until that is |
|
585 // implemented, let's just ignore those with 0 uses, since they |
|
586 // will be stripped out by DCE later. |
|
587 if (ins->useCount() == 0) |
|
588 return true; |
|
589 |
|
590 SpewMIR(ins, "visitNewDerivedTypedObject"); |
|
591 return markUnsafe(); |
|
592 } |
|
593 |
|
594 bool |
|
595 ParallelSafetyVisitor::visitRest(MRest *ins) |
|
596 { |
|
597 return replace(ins, MRestPar::New(alloc(), ForkJoinContext(), ins)); |
|
598 } |
|
599 |
|
600 bool |
|
601 ParallelSafetyVisitor::visitMathFunction(MMathFunction *ins) |
|
602 { |
|
603 return replace(ins, MMathFunction::New(alloc(), ins->input(), ins->function(), nullptr)); |
|
604 } |
|
605 |
|
606 bool |
|
607 ParallelSafetyVisitor::visitConcat(MConcat *ins) |
|
608 { |
|
609 return replace(ins, MConcatPar::New(alloc(), ForkJoinContext(), ins)); |
|
610 } |
|
611 |
|
612 bool |
|
613 ParallelSafetyVisitor::visitToString(MToString *ins) |
|
614 { |
|
615 MIRType inputType = ins->input()->type(); |
|
616 if (inputType != MIRType_Int32 && inputType != MIRType_Double) |
|
617 return markUnsafe(); |
|
618 return true; |
|
619 } |
|
620 |
|
621 bool |
|
622 ParallelSafetyVisitor::replaceWithNewPar(MInstruction *newInstruction, |
|
623 JSObject *templateObject) |
|
624 { |
|
625 replace(newInstruction, MNewPar::New(alloc(), ForkJoinContext(), templateObject)); |
|
626 return true; |
|
627 } |
|
628 |
|
629 bool |
|
630 ParallelSafetyVisitor::replace(MInstruction *oldInstruction, |
|
631 MInstruction *replacementInstruction) |
|
632 { |
|
633 MBasicBlock *block = oldInstruction->block(); |
|
634 block->insertBefore(oldInstruction, replacementInstruction); |
|
635 oldInstruction->replaceAllUsesWith(replacementInstruction); |
|
636 block->discard(oldInstruction); |
|
637 return true; |
|
638 } |
|
639 |
|
640 ///////////////////////////////////////////////////////////////////////////// |
|
641 // Write Guards |
|
642 // |
|
643 // We only want to permit writes to locally guarded objects. |
|
644 // Furthermore, we want to avoid PICs and other non-thread-safe things |
|
645 // (though perhaps we should support PICs at some point). If we |
|
646 // cannot determine the origin of an object, we can insert a write |
|
647 // guard which will check whether the object was allocated from the |
|
648 // per-thread-arena or not. |
|
649 |
|
650 bool |
|
651 ParallelSafetyVisitor::insertWriteGuard(MInstruction *writeInstruction, |
|
652 MDefinition *valueBeingWritten) |
|
653 { |
|
654 // Many of the write operations do not take the JS object |
|
655 // but rather something derived from it, such as the elements. |
|
656 // So we need to identify the JS object: |
|
657 MDefinition *object; |
|
658 switch (valueBeingWritten->type()) { |
|
659 case MIRType_Object: |
|
660 object = valueBeingWritten; |
|
661 break; |
|
662 |
|
663 case MIRType_Slots: |
|
664 switch (valueBeingWritten->op()) { |
|
665 case MDefinition::Op_Slots: |
|
666 object = valueBeingWritten->toSlots()->object(); |
|
667 break; |
|
668 |
|
669 case MDefinition::Op_NewSlots: |
|
670 // Values produced by new slots will ALWAYS be |
|
671 // thread-local. |
|
672 return true; |
|
673 |
|
674 default: |
|
675 SpewMIR(writeInstruction, "cannot insert write guard for %s", |
|
676 valueBeingWritten->opName()); |
|
677 return markUnsafe(); |
|
678 } |
|
679 break; |
|
680 |
|
681 case MIRType_Elements: |
|
682 switch (valueBeingWritten->op()) { |
|
683 case MDefinition::Op_Elements: |
|
684 object = valueBeingWritten->toElements()->object(); |
|
685 break; |
|
686 |
|
687 case MDefinition::Op_TypedArrayElements: |
|
688 object = valueBeingWritten->toTypedArrayElements()->object(); |
|
689 break; |
|
690 |
|
691 case MDefinition::Op_TypedObjectElements: |
|
692 object = valueBeingWritten->toTypedObjectElements()->object(); |
|
693 break; |
|
694 |
|
695 default: |
|
696 SpewMIR(writeInstruction, "cannot insert write guard for %s", |
|
697 valueBeingWritten->opName()); |
|
698 return markUnsafe(); |
|
699 } |
|
700 break; |
|
701 |
|
702 default: |
|
703 SpewMIR(writeInstruction, "cannot insert write guard for MIR Type %d", |
|
704 valueBeingWritten->type()); |
|
705 return markUnsafe(); |
|
706 } |
|
707 |
|
708 if (object->isUnbox()) |
|
709 object = object->toUnbox()->input(); |
|
710 |
|
711 switch (object->op()) { |
|
712 case MDefinition::Op_NewPar: |
|
713 // MNewPar will always be creating something thread-local, omit the guard |
|
714 SpewMIR(writeInstruction, "write to NewPar prop does not require guard"); |
|
715 return true; |
|
716 default: |
|
717 break; |
|
718 } |
|
719 |
|
720 MBasicBlock *block = writeInstruction->block(); |
|
721 MGuardThreadExclusive *writeGuard = |
|
722 MGuardThreadExclusive::New(alloc(), ForkJoinContext(), object); |
|
723 block->insertBefore(writeInstruction, writeGuard); |
|
724 writeGuard->adjustInputs(alloc(), writeGuard); |
|
725 return true; |
|
726 } |
|
727 |
|
728 ///////////////////////////////////////////////////////////////////////////// |
|
729 // Calls |
|
730 // |
|
731 // We only support calls to interpreted functions that that have already been |
|
732 // Ion compiled. If a function has no IonScript, we bail out. |
|
733 |
|
734 bool |
|
735 ParallelSafetyVisitor::visitCall(MCall *ins) |
|
736 { |
|
737 // DOM? Scary. |
|
738 if (ins->isCallDOMNative()) { |
|
739 SpewMIR(ins, "call to dom function"); |
|
740 return markUnsafe(); |
|
741 } |
|
742 |
|
743 JSFunction *target = ins->getSingleTarget(); |
|
744 if (target) { |
|
745 // Non-parallel native? Scary |
|
746 if (target->isNative() && !target->hasParallelNative()) { |
|
747 SpewMIR(ins, "call to non-parallel native function"); |
|
748 return markUnsafe(); |
|
749 } |
|
750 return true; |
|
751 } |
|
752 |
|
753 if (ins->isConstructing()) { |
|
754 SpewMIR(ins, "call to unknown constructor"); |
|
755 return markUnsafe(); |
|
756 } |
|
757 |
|
758 return true; |
|
759 } |
|
760 |
|
761 ///////////////////////////////////////////////////////////////////////////// |
|
762 // Stack limit, interrupts |
|
763 // |
|
764 // In sequential Ion code, the stack limit is stored in the JSRuntime. |
|
765 // We store it in the thread context. We therefore need a separate |
|
766 // instruction to access it, one parameterized by the thread context. |
|
767 // Similar considerations apply to checking for interrupts. |
|
768 |
|
769 bool |
|
770 ParallelSafetyVisitor::visitCheckOverRecursed(MCheckOverRecursed *ins) |
|
771 { |
|
772 return replace(ins, MCheckOverRecursedPar::New(alloc(), ForkJoinContext())); |
|
773 } |
|
774 |
|
775 bool |
|
776 ParallelSafetyVisitor::visitInterruptCheck(MInterruptCheck *ins) |
|
777 { |
|
778 return replace(ins, MInterruptCheckPar::New(alloc(), ForkJoinContext())); |
|
779 } |
|
780 |
|
781 ///////////////////////////////////////////////////////////////////////////// |
|
782 // Specialized ops |
|
783 // |
|
784 // Some ops, like +, can be specialized to ints/doubles. Anything |
|
785 // else is terrifying. |
|
786 // |
|
787 // TODO---Eventually, we should probably permit arbitrary + but bail |
|
788 // if the operands are not both integers/floats. |
|
789 |
|
790 bool |
|
791 ParallelSafetyVisitor::visitSpecializedInstruction(MInstruction *ins, MIRType spec, |
|
792 uint32_t flags) |
|
793 { |
|
794 uint32_t flag = 1 << spec; |
|
795 if (flags & flag) |
|
796 return true; |
|
797 |
|
798 SpewMIR(ins, "specialized to unacceptable type %d", spec); |
|
799 return markUnsafe(); |
|
800 } |
|
801 |
|
802 ///////////////////////////////////////////////////////////////////////////// |
|
803 // Throw |
|
804 |
|
805 bool |
|
806 ParallelSafetyVisitor::visitThrow(MThrow *thr) |
|
807 { |
|
808 MBasicBlock *block = thr->block(); |
|
809 JS_ASSERT(block->lastIns() == thr); |
|
810 block->discardLastIns(); |
|
811 MAbortPar *bailout = MAbortPar::New(alloc()); |
|
812 if (!bailout) |
|
813 return false; |
|
814 block->end(bailout); |
|
815 return true; |
|
816 } |
|
817 |
|
818 /////////////////////////////////////////////////////////////////////////// |
|
819 // Callee extraction |
|
820 // |
|
821 // See comments in header file. |
|
822 |
|
823 static bool |
|
824 GetPossibleCallees(JSContext *cx, HandleScript script, jsbytecode *pc, |
|
825 types::TemporaryTypeSet *calleeTypes, CallTargetVector &targets); |
|
826 |
|
827 static bool |
|
828 AddCallTarget(HandleScript script, CallTargetVector &targets); |
|
829 |
|
830 bool |
|
831 jit::AddPossibleCallees(JSContext *cx, MIRGraph &graph, CallTargetVector &targets) |
|
832 { |
|
833 for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { |
|
834 for (MInstructionIterator ins(block->begin()); ins != block->end(); ins++) |
|
835 { |
|
836 if (!ins->isCall()) |
|
837 continue; |
|
838 |
|
839 MCall *callIns = ins->toCall(); |
|
840 |
|
841 RootedFunction target(cx, callIns->getSingleTarget()); |
|
842 if (target) { |
|
843 JS_ASSERT_IF(!target->isInterpreted(), target->hasParallelNative()); |
|
844 |
|
845 if (target->isInterpreted()) { |
|
846 RootedScript script(cx, target->getOrCreateScript(cx)); |
|
847 if (!script || !AddCallTarget(script, targets)) |
|
848 return false; |
|
849 } |
|
850 |
|
851 continue; |
|
852 } |
|
853 |
|
854 types::TemporaryTypeSet *calleeTypes = callIns->getFunction()->resultTypeSet(); |
|
855 RootedScript script(cx, callIns->block()->info().script()); |
|
856 if (!GetPossibleCallees(cx, |
|
857 script, |
|
858 callIns->resumePoint()->pc(), |
|
859 calleeTypes, |
|
860 targets)) |
|
861 return false; |
|
862 } |
|
863 } |
|
864 |
|
865 return true; |
|
866 } |
|
867 |
|
868 static bool |
|
869 GetPossibleCallees(JSContext *cx, |
|
870 HandleScript script, |
|
871 jsbytecode *pc, |
|
872 types::TemporaryTypeSet *calleeTypes, |
|
873 CallTargetVector &targets) |
|
874 { |
|
875 if (!calleeTypes || calleeTypes->baseFlags() != 0) |
|
876 return true; |
|
877 |
|
878 unsigned objCount = calleeTypes->getObjectCount(); |
|
879 |
|
880 if (objCount == 0) |
|
881 return true; |
|
882 |
|
883 RootedFunction rootedFun(cx); |
|
884 RootedScript rootedScript(cx); |
|
885 for (unsigned i = 0; i < objCount; i++) { |
|
886 JSObject *obj = calleeTypes->getSingleObject(i); |
|
887 if (obj && obj->is<JSFunction>()) { |
|
888 rootedFun = &obj->as<JSFunction>(); |
|
889 } else { |
|
890 types::TypeObject *typeObj = calleeTypes->getTypeObject(i); |
|
891 if (!typeObj) |
|
892 continue; |
|
893 rootedFun = typeObj->interpretedFunction; |
|
894 if (!rootedFun) |
|
895 continue; |
|
896 } |
|
897 |
|
898 if (!rootedFun->isInterpreted()) |
|
899 continue; |
|
900 |
|
901 rootedScript = rootedFun->getOrCreateScript(cx); |
|
902 if (!rootedScript) |
|
903 return false; |
|
904 |
|
905 if (rootedScript->shouldCloneAtCallsite()) { |
|
906 rootedFun = CloneFunctionAtCallsite(cx, rootedFun, script, pc); |
|
907 if (!rootedFun) |
|
908 return false; |
|
909 rootedScript = rootedFun->nonLazyScript(); |
|
910 } |
|
911 |
|
912 // check if this call target is already known |
|
913 if (!AddCallTarget(rootedScript, targets)) |
|
914 return false; |
|
915 } |
|
916 |
|
917 return true; |
|
918 } |
|
919 |
|
920 static bool |
|
921 AddCallTarget(HandleScript script, CallTargetVector &targets) |
|
922 { |
|
923 for (size_t i = 0; i < targets.length(); i++) { |
|
924 if (targets[i] == script) |
|
925 return true; |
|
926 } |
|
927 |
|
928 if (!targets.append(script)) |
|
929 return false; |
|
930 |
|
931 return true; |
|
932 } |