Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * vim: set ts=8 sts=4 et sw=4 tw=99: |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #if defined(XP_WIN) |
michael@0 | 8 | # include <io.h> // for isatty() |
michael@0 | 9 | #else |
michael@0 | 10 | # include <unistd.h> // for isatty() |
michael@0 | 11 | #endif |
michael@0 | 12 | |
michael@0 | 13 | #include "vm/ForkJoin.h" |
michael@0 | 14 | |
michael@0 | 15 | #include "mozilla/ThreadLocal.h" |
michael@0 | 16 | |
michael@0 | 17 | #include "jscntxt.h" |
michael@0 | 18 | #include "jslock.h" |
michael@0 | 19 | #include "jsprf.h" |
michael@0 | 20 | |
michael@0 | 21 | #include "builtin/TypedObject.h" |
michael@0 | 22 | |
michael@0 | 23 | #ifdef JS_THREADSAFE |
michael@0 | 24 | # include "jit/BaselineJIT.h" |
michael@0 | 25 | # include "vm/Monitor.h" |
michael@0 | 26 | #endif |
michael@0 | 27 | |
michael@0 | 28 | #if defined(JS_THREADSAFE) && defined(JS_ION) |
michael@0 | 29 | # include "jit/JitCommon.h" |
michael@0 | 30 | # ifdef DEBUG |
michael@0 | 31 | # include "jit/Ion.h" |
michael@0 | 32 | # include "jit/JitCompartment.h" |
michael@0 | 33 | # include "jit/MIR.h" |
michael@0 | 34 | # include "jit/MIRGraph.h" |
michael@0 | 35 | # endif |
michael@0 | 36 | #endif // THREADSAFE && ION |
michael@0 | 37 | |
michael@0 | 38 | #include "vm/Interpreter-inl.h" |
michael@0 | 39 | |
michael@0 | 40 | using namespace js; |
michael@0 | 41 | using namespace js::parallel; |
michael@0 | 42 | using namespace js::jit; |
michael@0 | 43 | |
michael@0 | 44 | using mozilla::ThreadLocal; |
michael@0 | 45 | |
michael@0 | 46 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 47 | // Degenerate configurations |
michael@0 | 48 | // |
michael@0 | 49 | // When JS_THREADSAFE or JS_ION is not defined, we simply run the |
michael@0 | 50 | // |func| callback sequentially. We also forego the feedback |
michael@0 | 51 | // altogether. |
michael@0 | 52 | |
michael@0 | 53 | static bool |
michael@0 | 54 | ExecuteSequentially(JSContext *cx_, HandleValue funVal, uint16_t *sliceStart, |
michael@0 | 55 | uint16_t sliceEnd); |
michael@0 | 56 | |
michael@0 | 57 | #if !defined(JS_THREADSAFE) || !defined(JS_ION) |
michael@0 | 58 | bool |
michael@0 | 59 | js::ForkJoin(JSContext *cx, CallArgs &args) |
michael@0 | 60 | { |
michael@0 | 61 | RootedValue argZero(cx, args[0]); |
michael@0 | 62 | uint16_t sliceStart = uint16_t(args[1].toInt32()); |
michael@0 | 63 | uint16_t sliceEnd = uint16_t(args[2].toInt32()); |
michael@0 | 64 | if (!ExecuteSequentially(cx, argZero, &sliceStart, sliceEnd)) |
michael@0 | 65 | return false; |
michael@0 | 66 | MOZ_ASSERT(sliceStart == sliceEnd); |
michael@0 | 67 | return true; |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | JSContext * |
michael@0 | 71 | ForkJoinContext::acquireJSContext() |
michael@0 | 72 | { |
michael@0 | 73 | return nullptr; |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | void |
michael@0 | 77 | ForkJoinContext::releaseJSContext() |
michael@0 | 78 | { |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | bool |
michael@0 | 82 | ForkJoinContext::isMainThread() const |
michael@0 | 83 | { |
michael@0 | 84 | return true; |
michael@0 | 85 | } |
michael@0 | 86 | |
michael@0 | 87 | JSRuntime * |
michael@0 | 88 | ForkJoinContext::runtime() |
michael@0 | 89 | { |
michael@0 | 90 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | bool |
michael@0 | 94 | ForkJoinContext::check() |
michael@0 | 95 | { |
michael@0 | 96 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | void |
michael@0 | 100 | ForkJoinContext::requestGC(JS::gcreason::Reason reason) |
michael@0 | 101 | { |
michael@0 | 102 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | void |
michael@0 | 106 | ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) |
michael@0 | 107 | { |
michael@0 | 108 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | bool |
michael@0 | 112 | ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause) |
michael@0 | 113 | { |
michael@0 | 114 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 115 | return false; |
michael@0 | 116 | } |
michael@0 | 117 | |
michael@0 | 118 | void |
michael@0 | 119 | ParallelBailoutRecord::setCause(ParallelBailoutCause cause, |
michael@0 | 120 | JSScript *outermostScript, |
michael@0 | 121 | JSScript *currentScript, |
michael@0 | 122 | jsbytecode *currentPc) |
michael@0 | 123 | { |
michael@0 | 124 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | void |
michael@0 | 128 | js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause, |
michael@0 | 129 | JSScript *outermostScript, |
michael@0 | 130 | JSScript *currentScript, |
michael@0 | 131 | jsbytecode *currentPc) |
michael@0 | 132 | { |
michael@0 | 133 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | void |
michael@0 | 137 | ParallelBailoutRecord::addTrace(JSScript *script, |
michael@0 | 138 | jsbytecode *pc) |
michael@0 | 139 | { |
michael@0 | 140 | MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | bool |
michael@0 | 144 | js::InExclusiveParallelSection() |
michael@0 | 145 | { |
michael@0 | 146 | return false; |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | bool |
michael@0 | 150 | js::ParallelTestsShouldPass(JSContext *cx) |
michael@0 | 151 | { |
michael@0 | 152 | return false; |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | bool |
michael@0 | 156 | js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 157 | { |
michael@0 | 158 | return true; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | static bool |
michael@0 | 162 | intrinsic_SetForkJoinTargetRegionPar(ForkJoinContext *cx, unsigned argc, Value *vp) |
michael@0 | 163 | { |
michael@0 | 164 | return true; |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_SetForkJoinTargetRegionInfo, |
michael@0 | 168 | intrinsic_SetForkJoinTargetRegionPar); |
michael@0 | 169 | |
michael@0 | 170 | bool |
michael@0 | 171 | js::intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 172 | { |
michael@0 | 173 | return true; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | static bool |
michael@0 | 177 | intrinsic_ClearThreadLocalArenasPar(ForkJoinContext *cx, unsigned argc, Value *vp) |
michael@0 | 178 | { |
michael@0 | 179 | return true; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_ClearThreadLocalArenasInfo, |
michael@0 | 183 | intrinsic_ClearThreadLocalArenasPar); |
michael@0 | 184 | |
michael@0 | 185 | #endif // !JS_THREADSAFE || !JS_ION |
michael@0 | 186 | |
michael@0 | 187 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 188 | // All configurations |
michael@0 | 189 | // |
michael@0 | 190 | // Some code that is shared between degenerate and parallel configurations. |
michael@0 | 191 | |
michael@0 | 192 | static bool |
michael@0 | 193 | ExecuteSequentially(JSContext *cx, HandleValue funVal, uint16_t *sliceStart, |
michael@0 | 194 | uint16_t sliceEnd) |
michael@0 | 195 | { |
michael@0 | 196 | FastInvokeGuard fig(cx, funVal); |
michael@0 | 197 | InvokeArgs &args = fig.args(); |
michael@0 | 198 | if (!args.init(3)) |
michael@0 | 199 | return false; |
michael@0 | 200 | args.setCallee(funVal); |
michael@0 | 201 | args.setThis(UndefinedValue()); |
michael@0 | 202 | args[0].setInt32(0); |
michael@0 | 203 | args[1].setInt32(*sliceStart); |
michael@0 | 204 | args[2].setInt32(sliceEnd); |
michael@0 | 205 | if (!fig.invoke(cx)) |
michael@0 | 206 | return false; |
michael@0 | 207 | *sliceStart = (uint16_t)(args.rval().toInt32()); |
michael@0 | 208 | return true; |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | ThreadLocal<ForkJoinContext*> ForkJoinContext::tlsForkJoinContext; |
michael@0 | 212 | |
michael@0 | 213 | /* static */ bool |
michael@0 | 214 | ForkJoinContext::initialize() |
michael@0 | 215 | { |
michael@0 | 216 | if (!tlsForkJoinContext.initialized()) { |
michael@0 | 217 | if (!tlsForkJoinContext.init()) |
michael@0 | 218 | return false; |
michael@0 | 219 | } |
michael@0 | 220 | return true; |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 224 | // Parallel configurations |
michael@0 | 225 | // |
michael@0 | 226 | // The remainder of this file is specific to cases where both |
michael@0 | 227 | // JS_THREADSAFE and JS_ION are enabled. |
michael@0 | 228 | |
michael@0 | 229 | #if defined(JS_THREADSAFE) && defined(JS_ION) |
michael@0 | 230 | |
michael@0 | 231 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 232 | // Class Declarations and Function Prototypes |
michael@0 | 233 | |
michael@0 | 234 | namespace js { |
michael@0 | 235 | |
michael@0 | 236 | // When writing tests, it is often useful to specify different modes |
michael@0 | 237 | // of operation. |
michael@0 | 238 | enum ForkJoinMode { |
michael@0 | 239 | // WARNING: If you change this enum, you MUST update |
michael@0 | 240 | // ForkJoinMode() in Utilities.js |
michael@0 | 241 | |
michael@0 | 242 | // The "normal" behavior: attempt parallel, fallback to |
michael@0 | 243 | // sequential. If compilation is ongoing in a helper thread, then |
michael@0 | 244 | // run sequential warmup iterations in the meantime. If those |
michael@0 | 245 | // iterations wind up completing all the work, just abort. |
michael@0 | 246 | ForkJoinModeNormal, |
michael@0 | 247 | |
michael@0 | 248 | // Like normal, except that we will keep running warmup iterations |
michael@0 | 249 | // until compilations are complete, even if there is no more work |
michael@0 | 250 | // to do. This is useful in tests as a "setup" run. |
michael@0 | 251 | ForkJoinModeCompile, |
michael@0 | 252 | |
michael@0 | 253 | // Requires that compilation has already completed. Expects parallel |
michael@0 | 254 | // execution to proceed without a hitch. (Reports an error otherwise) |
michael@0 | 255 | ForkJoinModeParallel, |
michael@0 | 256 | |
michael@0 | 257 | // Requires that compilation has already completed. Expects |
michael@0 | 258 | // parallel execution to bailout once but continue after that without |
michael@0 | 259 | // further bailouts. (Reports an error otherwise) |
michael@0 | 260 | ForkJoinModeRecover, |
michael@0 | 261 | |
michael@0 | 262 | // Expects all parallel executions to yield a bailout. If this is not |
michael@0 | 263 | // the case, reports an error. |
michael@0 | 264 | ForkJoinModeBailout, |
michael@0 | 265 | |
michael@0 | 266 | NumForkJoinModes |
michael@0 | 267 | }; |
michael@0 | 268 | |
michael@0 | 269 | class ForkJoinOperation |
michael@0 | 270 | { |
michael@0 | 271 | public: |
michael@0 | 272 | // For tests, make sure to keep this in sync with minItemsTestingThreshold. |
michael@0 | 273 | static const uint32_t MAX_BAILOUTS = 3; |
michael@0 | 274 | uint32_t bailouts; |
michael@0 | 275 | |
michael@0 | 276 | // Information about the bailout: |
michael@0 | 277 | ParallelBailoutCause bailoutCause; |
michael@0 | 278 | RootedScript bailoutScript; |
michael@0 | 279 | jsbytecode *bailoutBytecode; |
michael@0 | 280 | |
michael@0 | 281 | ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, |
michael@0 | 282 | uint16_t sliceEnd, ForkJoinMode mode); |
michael@0 | 283 | ExecutionStatus apply(); |
michael@0 | 284 | |
michael@0 | 285 | private: |
michael@0 | 286 | // Most of the functions involved in managing the parallel |
michael@0 | 287 | // compilation follow a similar control-flow. They return RedLight |
michael@0 | 288 | // if they have either encountered a fatal error or completed the |
michael@0 | 289 | // execution, such that no further work is needed. In that event, |
michael@0 | 290 | // they take an `ExecutionStatus*` which they use to report |
michael@0 | 291 | // whether execution was successful or not. If the function |
michael@0 | 292 | // returns `GreenLight`, then the parallel operation is not yet |
michael@0 | 293 | // fully completed, so the state machine should carry on. |
michael@0 | 294 | enum TrafficLight { |
michael@0 | 295 | RedLight, |
michael@0 | 296 | GreenLight |
michael@0 | 297 | }; |
michael@0 | 298 | |
michael@0 | 299 | struct WorklistData { |
michael@0 | 300 | // True if we enqueued the callees from the ion-compiled |
michael@0 | 301 | // version of this entry |
michael@0 | 302 | bool calleesEnqueued; |
michael@0 | 303 | |
michael@0 | 304 | // Last record useCount; updated after warmup |
michael@0 | 305 | // iterations; |
michael@0 | 306 | uint32_t useCount; |
michael@0 | 307 | |
michael@0 | 308 | // Number of continuous "stalls" --- meaning warmups |
michael@0 | 309 | // where useCount did not increase. |
michael@0 | 310 | uint32_t stallCount; |
michael@0 | 311 | |
michael@0 | 312 | void reset() { |
michael@0 | 313 | calleesEnqueued = false; |
michael@0 | 314 | useCount = 0; |
michael@0 | 315 | stallCount = 0; |
michael@0 | 316 | } |
michael@0 | 317 | }; |
michael@0 | 318 | |
michael@0 | 319 | JSContext *cx_; |
michael@0 | 320 | HandleFunction fun_; |
michael@0 | 321 | uint16_t sliceStart_; |
michael@0 | 322 | uint16_t sliceEnd_; |
michael@0 | 323 | Vector<ParallelBailoutRecord, 16> bailoutRecords_; |
michael@0 | 324 | AutoScriptVector worklist_; |
michael@0 | 325 | Vector<WorklistData, 16> worklistData_; |
michael@0 | 326 | ForkJoinMode mode_; |
michael@0 | 327 | |
michael@0 | 328 | TrafficLight enqueueInitialScript(ExecutionStatus *status); |
michael@0 | 329 | TrafficLight compileForParallelExecution(ExecutionStatus *status); |
michael@0 | 330 | TrafficLight warmupExecution(bool stopIfComplete, ExecutionStatus *status); |
michael@0 | 331 | TrafficLight parallelExecution(ExecutionStatus *status); |
michael@0 | 332 | TrafficLight sequentialExecution(bool disqualified, ExecutionStatus *status); |
michael@0 | 333 | TrafficLight recoverFromBailout(ExecutionStatus *status); |
michael@0 | 334 | TrafficLight fatalError(ExecutionStatus *status); |
michael@0 | 335 | bool isInitialScript(HandleScript script); |
michael@0 | 336 | void determineBailoutCause(); |
michael@0 | 337 | bool invalidateBailedOutScripts(); |
michael@0 | 338 | ExecutionStatus sequentialExecution(bool disqualified); |
michael@0 | 339 | |
michael@0 | 340 | TrafficLight appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status); |
michael@0 | 341 | TrafficLight appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status); |
michael@0 | 342 | bool addToWorklist(HandleScript script); |
michael@0 | 343 | inline bool hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script); |
michael@0 | 344 | }; // class ForkJoinOperation |
michael@0 | 345 | |
michael@0 | 346 | class ForkJoinShared : public ParallelJob, public Monitor |
michael@0 | 347 | { |
michael@0 | 348 | ///////////////////////////////////////////////////////////////////////// |
michael@0 | 349 | // Constant fields |
michael@0 | 350 | |
michael@0 | 351 | JSContext *const cx_; // Current context |
michael@0 | 352 | ThreadPool *const threadPool_; // The thread pool |
michael@0 | 353 | HandleFunction fun_; // The JavaScript function to execute |
michael@0 | 354 | uint16_t sliceStart_; // The starting slice id. |
michael@0 | 355 | uint16_t sliceEnd_; // The ending slice id + 1. |
michael@0 | 356 | PRLock *cxLock_; // Locks cx_ for parallel VM calls |
michael@0 | 357 | ParallelBailoutRecord *const records_; // Bailout records for each worker |
michael@0 | 358 | |
michael@0 | 359 | ///////////////////////////////////////////////////////////////////////// |
michael@0 | 360 | // Per-thread arenas |
michael@0 | 361 | // |
michael@0 | 362 | // Each worker thread gets an arena to use when allocating. |
michael@0 | 363 | |
michael@0 | 364 | Vector<Allocator *, 16> allocators_; |
michael@0 | 365 | |
michael@0 | 366 | ///////////////////////////////////////////////////////////////////////// |
michael@0 | 367 | // Locked Fields |
michael@0 | 368 | // |
michael@0 | 369 | // Only to be accessed while holding the lock. |
michael@0 | 370 | |
michael@0 | 371 | bool gcRequested_; // True if a worker requested a GC |
michael@0 | 372 | JS::gcreason::Reason gcReason_; // Reason given to request GC |
michael@0 | 373 | Zone *gcZone_; // Zone for GC, or nullptr for full |
michael@0 | 374 | |
michael@0 | 375 | ///////////////////////////////////////////////////////////////////////// |
michael@0 | 376 | // Asynchronous Flags |
michael@0 | 377 | // |
michael@0 | 378 | // These can be accessed without the lock and are thus atomic. |
michael@0 | 379 | |
michael@0 | 380 | // Set to true when parallel execution should abort. |
michael@0 | 381 | mozilla::Atomic<bool, mozilla::ReleaseAcquire> abort_; |
michael@0 | 382 | |
michael@0 | 383 | // Set to true when a worker bails for a fatal reason. |
michael@0 | 384 | mozilla::Atomic<bool, mozilla::ReleaseAcquire> fatal_; |
michael@0 | 385 | |
michael@0 | 386 | public: |
michael@0 | 387 | ForkJoinShared(JSContext *cx, |
michael@0 | 388 | ThreadPool *threadPool, |
michael@0 | 389 | HandleFunction fun, |
michael@0 | 390 | uint16_t sliceStart, |
michael@0 | 391 | uint16_t sliceEnd, |
michael@0 | 392 | ParallelBailoutRecord *records); |
michael@0 | 393 | ~ForkJoinShared(); |
michael@0 | 394 | |
michael@0 | 395 | bool init(); |
michael@0 | 396 | |
michael@0 | 397 | ParallelResult execute(); |
michael@0 | 398 | |
michael@0 | 399 | // Invoked from parallel worker threads: |
michael@0 | 400 | virtual bool executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit) MOZ_OVERRIDE; |
michael@0 | 401 | |
michael@0 | 402 | // Invoked only from the main thread: |
michael@0 | 403 | virtual bool executeFromMainThread(ThreadPoolWorker *worker) MOZ_OVERRIDE; |
michael@0 | 404 | |
michael@0 | 405 | // Executes the user-supplied function a worker or the main thread. |
michael@0 | 406 | void executePortion(PerThreadData *perThread, ThreadPoolWorker *worker); |
michael@0 | 407 | |
michael@0 | 408 | // Moves all the per-thread arenas into the main compartment and processes |
michael@0 | 409 | // any pending requests for a GC. This can only safely be invoked on the |
michael@0 | 410 | // main thread after the workers have completed. |
michael@0 | 411 | void transferArenasToCompartmentAndProcessGCRequests(); |
michael@0 | 412 | |
michael@0 | 413 | |
michael@0 | 414 | // Requests a GC, either full or specific to a zone. |
michael@0 | 415 | void requestGC(JS::gcreason::Reason reason); |
michael@0 | 416 | void requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason); |
michael@0 | 417 | |
michael@0 | 418 | // Requests that computation abort. |
michael@0 | 419 | void setAbortFlagDueToInterrupt(ForkJoinContext &cx); |
michael@0 | 420 | void setAbortFlagAndRequestInterrupt(bool fatal); |
michael@0 | 421 | |
michael@0 | 422 | // Set the fatal flag for the next abort. |
michael@0 | 423 | void setPendingAbortFatal() { fatal_ = true; } |
michael@0 | 424 | |
michael@0 | 425 | JSRuntime *runtime() { return cx_->runtime(); } |
michael@0 | 426 | JS::Zone *zone() { return cx_->zone(); } |
michael@0 | 427 | JSCompartment *compartment() { return cx_->compartment(); } |
michael@0 | 428 | |
michael@0 | 429 | JSContext *acquireJSContext() { PR_Lock(cxLock_); return cx_; } |
michael@0 | 430 | void releaseJSContext() { PR_Unlock(cxLock_); } |
michael@0 | 431 | }; |
michael@0 | 432 | |
michael@0 | 433 | class AutoEnterWarmup |
michael@0 | 434 | { |
michael@0 | 435 | JSRuntime *runtime_; |
michael@0 | 436 | |
michael@0 | 437 | public: |
michael@0 | 438 | AutoEnterWarmup(JSRuntime *runtime) : runtime_(runtime) { runtime_->forkJoinWarmup++; } |
michael@0 | 439 | ~AutoEnterWarmup() { runtime_->forkJoinWarmup--; } |
michael@0 | 440 | }; |
michael@0 | 441 | |
michael@0 | 442 | class AutoSetForkJoinContext |
michael@0 | 443 | { |
michael@0 | 444 | public: |
michael@0 | 445 | AutoSetForkJoinContext(ForkJoinContext *threadCx) { |
michael@0 | 446 | ForkJoinContext::tlsForkJoinContext.set(threadCx); |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | ~AutoSetForkJoinContext() { |
michael@0 | 450 | ForkJoinContext::tlsForkJoinContext.set(nullptr); |
michael@0 | 451 | } |
michael@0 | 452 | }; |
michael@0 | 453 | |
michael@0 | 454 | } // namespace js |
michael@0 | 455 | |
michael@0 | 456 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 457 | // ForkJoinActivation |
michael@0 | 458 | // |
michael@0 | 459 | // Takes care of tidying up GC before we enter a fork join section. Also |
michael@0 | 460 | // pauses the barrier verifier, as we cannot enter fork join with the runtime |
michael@0 | 461 | // or the zone needing barriers. |
michael@0 | 462 | |
michael@0 | 463 | ForkJoinActivation::ForkJoinActivation(JSContext *cx) |
michael@0 | 464 | : Activation(cx, ForkJoin), |
michael@0 | 465 | prevIonTop_(cx->mainThread().ionTop), |
michael@0 | 466 | av_(cx->runtime(), false) |
michael@0 | 467 | { |
michael@0 | 468 | // Note: we do not allow GC during parallel sections. |
michael@0 | 469 | // Moreover, we do not wish to worry about making |
michael@0 | 470 | // write barriers thread-safe. Therefore, we guarantee |
michael@0 | 471 | // that there is no incremental GC in progress and force |
michael@0 | 472 | // a minor GC to ensure no cross-generation pointers get |
michael@0 | 473 | // created: |
michael@0 | 474 | |
michael@0 | 475 | if (JS::IsIncrementalGCInProgress(cx->runtime())) { |
michael@0 | 476 | JS::PrepareForIncrementalGC(cx->runtime()); |
michael@0 | 477 | JS::FinishIncrementalGC(cx->runtime(), JS::gcreason::API); |
michael@0 | 478 | } |
michael@0 | 479 | |
michael@0 | 480 | MinorGC(cx->runtime(), JS::gcreason::API); |
michael@0 | 481 | |
michael@0 | 482 | cx->runtime()->gcHelperThread.waitBackgroundSweepEnd(); |
michael@0 | 483 | |
michael@0 | 484 | JS_ASSERT(!cx->runtime()->needsBarrier()); |
michael@0 | 485 | JS_ASSERT(!cx->zone()->needsBarrier()); |
michael@0 | 486 | } |
michael@0 | 487 | |
michael@0 | 488 | ForkJoinActivation::~ForkJoinActivation() |
michael@0 | 489 | { |
michael@0 | 490 | cx_->mainThread().ionTop = prevIonTop_; |
michael@0 | 491 | } |
michael@0 | 492 | |
michael@0 | 493 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 494 | // js::ForkJoin() and ForkJoinOperation class |
michael@0 | 495 | // |
michael@0 | 496 | // These are the top-level objects that manage the parallel execution. |
michael@0 | 497 | // They handle parallel compilation (if necessary), triggering |
michael@0 | 498 | // parallel execution, and recovering from bailouts. |
michael@0 | 499 | |
michael@0 | 500 | static const char *ForkJoinModeString(ForkJoinMode mode); |
michael@0 | 501 | |
michael@0 | 502 | bool |
michael@0 | 503 | js::ForkJoin(JSContext *cx, CallArgs &args) |
michael@0 | 504 | { |
michael@0 | 505 | JS_ASSERT(args.length() == 4); // else the self-hosted code is wrong |
michael@0 | 506 | JS_ASSERT(args[0].isObject()); |
michael@0 | 507 | JS_ASSERT(args[0].toObject().is<JSFunction>()); |
michael@0 | 508 | JS_ASSERT(args[1].isInt32()); |
michael@0 | 509 | JS_ASSERT(args[2].isInt32()); |
michael@0 | 510 | JS_ASSERT(args[3].isInt32()); |
michael@0 | 511 | JS_ASSERT(args[3].toInt32() < NumForkJoinModes); |
michael@0 | 512 | |
michael@0 | 513 | RootedFunction fun(cx, &args[0].toObject().as<JSFunction>()); |
michael@0 | 514 | uint16_t sliceStart = (uint16_t)(args[1].toInt32()); |
michael@0 | 515 | uint16_t sliceEnd = (uint16_t)(args[2].toInt32()); |
michael@0 | 516 | ForkJoinMode mode = (ForkJoinMode)(args[3].toInt32()); |
michael@0 | 517 | |
michael@0 | 518 | MOZ_ASSERT(sliceStart == args[1].toInt32()); |
michael@0 | 519 | MOZ_ASSERT(sliceEnd == args[2].toInt32()); |
michael@0 | 520 | MOZ_ASSERT(sliceStart <= sliceEnd); |
michael@0 | 521 | |
michael@0 | 522 | ForkJoinOperation op(cx, fun, sliceStart, sliceEnd, mode); |
michael@0 | 523 | ExecutionStatus status = op.apply(); |
michael@0 | 524 | if (status == ExecutionFatal) |
michael@0 | 525 | return false; |
michael@0 | 526 | |
michael@0 | 527 | switch (mode) { |
michael@0 | 528 | case ForkJoinModeNormal: |
michael@0 | 529 | case ForkJoinModeCompile: |
michael@0 | 530 | return true; |
michael@0 | 531 | |
michael@0 | 532 | case ForkJoinModeParallel: |
michael@0 | 533 | if (status == ExecutionParallel && op.bailouts == 0) |
michael@0 | 534 | return true; |
michael@0 | 535 | break; |
michael@0 | 536 | |
michael@0 | 537 | case ForkJoinModeRecover: |
michael@0 | 538 | if (status != ExecutionSequential && op.bailouts > 0) |
michael@0 | 539 | return true; |
michael@0 | 540 | break; |
michael@0 | 541 | |
michael@0 | 542 | case ForkJoinModeBailout: |
michael@0 | 543 | if (status != ExecutionParallel) |
michael@0 | 544 | return true; |
michael@0 | 545 | break; |
michael@0 | 546 | |
michael@0 | 547 | case NumForkJoinModes: |
michael@0 | 548 | break; |
michael@0 | 549 | } |
michael@0 | 550 | |
michael@0 | 551 | const char *statusString = "?"; |
michael@0 | 552 | switch (status) { |
michael@0 | 553 | case ExecutionSequential: statusString = "seq"; break; |
michael@0 | 554 | case ExecutionParallel: statusString = "par"; break; |
michael@0 | 555 | case ExecutionWarmup: statusString = "warmup"; break; |
michael@0 | 556 | case ExecutionFatal: statusString = "fatal"; break; |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | if (ParallelTestsShouldPass(cx)) { |
michael@0 | 560 | JS_ReportError(cx, "ForkJoin: mode=%s status=%s bailouts=%d", |
michael@0 | 561 | ForkJoinModeString(mode), statusString, op.bailouts); |
michael@0 | 562 | return false; |
michael@0 | 563 | } |
michael@0 | 564 | return true; |
michael@0 | 565 | } |
michael@0 | 566 | |
michael@0 | 567 | static const char * |
michael@0 | 568 | ForkJoinModeString(ForkJoinMode mode) { |
michael@0 | 569 | switch (mode) { |
michael@0 | 570 | case ForkJoinModeNormal: return "normal"; |
michael@0 | 571 | case ForkJoinModeCompile: return "compile"; |
michael@0 | 572 | case ForkJoinModeParallel: return "parallel"; |
michael@0 | 573 | case ForkJoinModeRecover: return "recover"; |
michael@0 | 574 | case ForkJoinModeBailout: return "bailout"; |
michael@0 | 575 | case NumForkJoinModes: return "max"; |
michael@0 | 576 | } |
michael@0 | 577 | return "???"; |
michael@0 | 578 | } |
michael@0 | 579 | |
michael@0 | 580 | ForkJoinOperation::ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, |
michael@0 | 581 | uint16_t sliceEnd, ForkJoinMode mode) |
michael@0 | 582 | : bailouts(0), |
michael@0 | 583 | bailoutCause(ParallelBailoutNone), |
michael@0 | 584 | bailoutScript(cx), |
michael@0 | 585 | bailoutBytecode(nullptr), |
michael@0 | 586 | cx_(cx), |
michael@0 | 587 | fun_(fun), |
michael@0 | 588 | sliceStart_(sliceStart), |
michael@0 | 589 | sliceEnd_(sliceEnd), |
michael@0 | 590 | bailoutRecords_(cx), |
michael@0 | 591 | worklist_(cx), |
michael@0 | 592 | worklistData_(cx), |
michael@0 | 593 | mode_(mode) |
michael@0 | 594 | { } |
michael@0 | 595 | |
michael@0 | 596 | ExecutionStatus |
michael@0 | 597 | ForkJoinOperation::apply() |
michael@0 | 598 | { |
michael@0 | 599 | ExecutionStatus status; |
michael@0 | 600 | |
michael@0 | 601 | // High level outline of the procedure: |
michael@0 | 602 | // |
michael@0 | 603 | // - As we enter, we check for parallel script without "uncompiled" flag. |
michael@0 | 604 | // - If present, skip initial enqueue. |
michael@0 | 605 | // - While not too many bailouts: |
michael@0 | 606 | // - While all scripts in worklist are not compiled: |
michael@0 | 607 | // - For each script S in worklist: |
michael@0 | 608 | // - Compile S if not compiled |
michael@0 | 609 | // -> Error: fallback |
michael@0 | 610 | // - If compiled, add call targets to worklist w/o checking uncompiled |
michael@0 | 611 | // flag |
michael@0 | 612 | // - If some compilations pending, run warmup iteration |
michael@0 | 613 | // - Otherwise, clear "uncompiled targets" flag on main script and |
michael@0 | 614 | // break from loop |
michael@0 | 615 | // - Attempt parallel execution |
michael@0 | 616 | // - If successful: return happily |
michael@0 | 617 | // - If error: abort sadly |
michael@0 | 618 | // - If bailout: |
michael@0 | 619 | // - Invalidate any scripts that may need to be invalidated |
michael@0 | 620 | // - Re-enqueue main script and any uncompiled scripts that were called |
michael@0 | 621 | // - Too many bailouts: Fallback to sequential |
michael@0 | 622 | |
michael@0 | 623 | JS_ASSERT_IF(!jit::IsBaselineEnabled(cx_), !jit::IsIonEnabled(cx_)); |
michael@0 | 624 | if (!jit::IsBaselineEnabled(cx_) || !jit::IsIonEnabled(cx_)) |
michael@0 | 625 | return sequentialExecution(true); |
michael@0 | 626 | |
michael@0 | 627 | SpewBeginOp(cx_, "ForkJoinOperation"); |
michael@0 | 628 | |
michael@0 | 629 | // How many workers do we have, counting the main thread. |
michael@0 | 630 | unsigned numWorkers = cx_->runtime()->threadPool.numWorkers(); |
michael@0 | 631 | |
michael@0 | 632 | if (!bailoutRecords_.resize(numWorkers)) |
michael@0 | 633 | return SpewEndOp(ExecutionFatal); |
michael@0 | 634 | |
michael@0 | 635 | for (uint32_t i = 0; i < numWorkers; i++) |
michael@0 | 636 | bailoutRecords_[i].init(cx_); |
michael@0 | 637 | |
michael@0 | 638 | if (enqueueInitialScript(&status) == RedLight) |
michael@0 | 639 | return SpewEndOp(status); |
michael@0 | 640 | |
michael@0 | 641 | Spew(SpewOps, "Execution mode: %s", ForkJoinModeString(mode_)); |
michael@0 | 642 | switch (mode_) { |
michael@0 | 643 | case ForkJoinModeNormal: |
michael@0 | 644 | case ForkJoinModeCompile: |
michael@0 | 645 | case ForkJoinModeBailout: |
michael@0 | 646 | break; |
michael@0 | 647 | |
michael@0 | 648 | case ForkJoinModeParallel: |
michael@0 | 649 | case ForkJoinModeRecover: |
michael@0 | 650 | // These two modes are used to check that every iteration can |
michael@0 | 651 | // be executed in parallel. They expect compilation to have |
michael@0 | 652 | // been done. But, when using gc zeal, it's possible that |
michael@0 | 653 | // compiled scripts were collected. |
michael@0 | 654 | if (ParallelTestsShouldPass(cx_) && worklist_.length() != 0) { |
michael@0 | 655 | JS_ReportError(cx_, "ForkJoin: compilation required in par or bailout mode"); |
michael@0 | 656 | return SpewEndOp(ExecutionFatal); |
michael@0 | 657 | } |
michael@0 | 658 | break; |
michael@0 | 659 | |
michael@0 | 660 | case NumForkJoinModes: |
michael@0 | 661 | MOZ_ASSUME_UNREACHABLE("Invalid mode"); |
michael@0 | 662 | } |
michael@0 | 663 | |
michael@0 | 664 | while (bailouts < MAX_BAILOUTS) { |
michael@0 | 665 | for (uint32_t i = 0; i < numWorkers; i++) |
michael@0 | 666 | bailoutRecords_[i].reset(cx_); |
michael@0 | 667 | |
michael@0 | 668 | if (compileForParallelExecution(&status) == RedLight) |
michael@0 | 669 | return SpewEndOp(status); |
michael@0 | 670 | |
michael@0 | 671 | JS_ASSERT(worklist_.length() == 0); |
michael@0 | 672 | if (parallelExecution(&status) == RedLight) |
michael@0 | 673 | return SpewEndOp(status); |
michael@0 | 674 | |
michael@0 | 675 | if (recoverFromBailout(&status) == RedLight) |
michael@0 | 676 | return SpewEndOp(status); |
michael@0 | 677 | } |
michael@0 | 678 | |
michael@0 | 679 | // After enough tries, just execute sequentially. |
michael@0 | 680 | return SpewEndOp(sequentialExecution(true)); |
michael@0 | 681 | } |
michael@0 | 682 | |
michael@0 | 683 | ForkJoinOperation::TrafficLight |
michael@0 | 684 | ForkJoinOperation::enqueueInitialScript(ExecutionStatus *status) |
michael@0 | 685 | { |
michael@0 | 686 | // GreenLight: script successfully enqueued if necessary |
michael@0 | 687 | // RedLight: fatal error or fell back to sequential |
michael@0 | 688 | |
michael@0 | 689 | // The kernel should be a self-hosted function. |
michael@0 | 690 | if (!fun_->is<JSFunction>()) |
michael@0 | 691 | return sequentialExecution(true, status); |
michael@0 | 692 | |
michael@0 | 693 | RootedFunction callee(cx_, &fun_->as<JSFunction>()); |
michael@0 | 694 | |
michael@0 | 695 | if (!callee->isInterpreted() || !callee->isSelfHostedBuiltin()) |
michael@0 | 696 | return sequentialExecution(true, status); |
michael@0 | 697 | |
michael@0 | 698 | // If the main script is already compiled, and we have no reason |
michael@0 | 699 | // to suspect any of its callees are not compiled, then we can |
michael@0 | 700 | // just skip the compilation step. |
michael@0 | 701 | RootedScript script(cx_, callee->getOrCreateScript(cx_)); |
michael@0 | 702 | if (!script) |
michael@0 | 703 | return RedLight; |
michael@0 | 704 | |
michael@0 | 705 | if (script->hasParallelIonScript()) { |
michael@0 | 706 | // Notify that there's been activity on the entry script. |
michael@0 | 707 | JitCompartment *jitComp = cx_->compartment()->jitCompartment(); |
michael@0 | 708 | if (!jitComp->notifyOfActiveParallelEntryScript(cx_, script)) { |
michael@0 | 709 | *status = ExecutionFatal; |
michael@0 | 710 | return RedLight; |
michael@0 | 711 | } |
michael@0 | 712 | |
michael@0 | 713 | if (!script->parallelIonScript()->hasUncompiledCallTarget()) { |
michael@0 | 714 | Spew(SpewOps, "Script %p:%s:%d already compiled, no uncompiled callees", |
michael@0 | 715 | script.get(), script->filename(), script->lineno()); |
michael@0 | 716 | return GreenLight; |
michael@0 | 717 | } |
michael@0 | 718 | |
michael@0 | 719 | Spew(SpewOps, "Script %p:%s:%d already compiled, may have uncompiled callees", |
michael@0 | 720 | script.get(), script->filename(), script->lineno()); |
michael@0 | 721 | } |
michael@0 | 722 | |
michael@0 | 723 | // Otherwise, add to the worklist of scripts to process. |
michael@0 | 724 | if (addToWorklist(script) == RedLight) |
michael@0 | 725 | return fatalError(status); |
michael@0 | 726 | return GreenLight; |
michael@0 | 727 | } |
michael@0 | 728 | |
michael@0 | 729 | ForkJoinOperation::TrafficLight |
michael@0 | 730 | ForkJoinOperation::compileForParallelExecution(ExecutionStatus *status) |
michael@0 | 731 | { |
michael@0 | 732 | // GreenLight: all scripts compiled |
michael@0 | 733 | // RedLight: fatal error or completed work via warmups or fallback |
michael@0 | 734 | |
michael@0 | 735 | // This routine attempts to do whatever compilation is necessary |
michael@0 | 736 | // to execute a single parallel attempt. When it returns, either |
michael@0 | 737 | // (1) we have fallen back to sequential; (2) we have run enough |
michael@0 | 738 | // warmup runs to complete all the work; or (3) we have compiled |
michael@0 | 739 | // all scripts we think likely to be executed during a parallel |
michael@0 | 740 | // execution. |
michael@0 | 741 | |
michael@0 | 742 | RootedFunction fun(cx_); |
michael@0 | 743 | RootedScript script(cx_); |
michael@0 | 744 | |
michael@0 | 745 | // After 3 stalls, we stop waiting for a script to gather type |
michael@0 | 746 | // info and move on with execution. |
michael@0 | 747 | const uint32_t stallThreshold = 3; |
michael@0 | 748 | |
michael@0 | 749 | // This loop continues to iterate until the full contents of |
michael@0 | 750 | // `worklist` have been successfully compiled for parallel |
michael@0 | 751 | // execution. The compilations themselves typically occur on |
michael@0 | 752 | // helper threads. While we wait for the compilations to complete, |
michael@0 | 753 | // or for sufficient type information to be gathered, we execute |
michael@0 | 754 | // warmup iterations. |
michael@0 | 755 | while (true) { |
michael@0 | 756 | bool offMainThreadCompilationsInProgress = false; |
michael@0 | 757 | bool gatheringTypeInformation = false; |
michael@0 | 758 | |
michael@0 | 759 | // Walk over the worklist to check on the status of each entry. |
michael@0 | 760 | for (uint32_t i = 0; i < worklist_.length(); i++) { |
michael@0 | 761 | script = worklist_[i]; |
michael@0 | 762 | script->ensureNonLazyCanonicalFunction(cx_); |
michael@0 | 763 | fun = script->functionNonDelazifying(); |
michael@0 | 764 | |
michael@0 | 765 | // No baseline script means no type information, hence we |
michael@0 | 766 | // will not be able to compile very well. In such cases, |
michael@0 | 767 | // we continue to run baseline iterations until either (1) |
michael@0 | 768 | // the potential callee *has* a baseline script or (2) the |
michael@0 | 769 | // potential callee's use count stops increasing, |
michael@0 | 770 | // indicating that they are not in fact a callee. |
michael@0 | 771 | if (!script->hasBaselineScript()) { |
michael@0 | 772 | uint32_t previousUseCount = worklistData_[i].useCount; |
michael@0 | 773 | uint32_t currentUseCount = script->getUseCount(); |
michael@0 | 774 | if (previousUseCount < currentUseCount) { |
michael@0 | 775 | worklistData_[i].useCount = currentUseCount; |
michael@0 | 776 | worklistData_[i].stallCount = 0; |
michael@0 | 777 | gatheringTypeInformation = true; |
michael@0 | 778 | |
michael@0 | 779 | Spew(SpewCompile, |
michael@0 | 780 | "Script %p:%s:%d has no baseline script, " |
michael@0 | 781 | "but use count grew from %d to %d", |
michael@0 | 782 | script.get(), script->filename(), script->lineno(), |
michael@0 | 783 | previousUseCount, currentUseCount); |
michael@0 | 784 | } else { |
michael@0 | 785 | uint32_t stallCount = ++worklistData_[i].stallCount; |
michael@0 | 786 | if (stallCount < stallThreshold) { |
michael@0 | 787 | gatheringTypeInformation = true; |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | Spew(SpewCompile, |
michael@0 | 791 | "Script %p:%s:%d has no baseline script, " |
michael@0 | 792 | "and use count has %u stalls at %d", |
michael@0 | 793 | script.get(), script->filename(), script->lineno(), |
michael@0 | 794 | stallCount, previousUseCount); |
michael@0 | 795 | } |
michael@0 | 796 | continue; |
michael@0 | 797 | } |
michael@0 | 798 | |
michael@0 | 799 | if (!script->hasParallelIonScript()) { |
michael@0 | 800 | // Script has not yet been compiled. Attempt to compile it. |
michael@0 | 801 | SpewBeginCompile(script); |
michael@0 | 802 | MethodStatus mstatus = jit::CanEnterInParallel(cx_, script); |
michael@0 | 803 | SpewEndCompile(mstatus); |
michael@0 | 804 | |
michael@0 | 805 | switch (mstatus) { |
michael@0 | 806 | case Method_Error: |
michael@0 | 807 | return fatalError(status); |
michael@0 | 808 | |
michael@0 | 809 | case Method_CantCompile: |
michael@0 | 810 | Spew(SpewCompile, |
michael@0 | 811 | "Script %p:%s:%d cannot be compiled, " |
michael@0 | 812 | "falling back to sequential execution", |
michael@0 | 813 | script.get(), script->filename(), script->lineno()); |
michael@0 | 814 | return sequentialExecution(true, status); |
michael@0 | 815 | |
michael@0 | 816 | case Method_Skipped: |
michael@0 | 817 | // A "skipped" result either means that we are compiling |
michael@0 | 818 | // in parallel OR some other transient error occurred. |
michael@0 | 819 | if (script->isParallelIonCompilingOffThread()) { |
michael@0 | 820 | Spew(SpewCompile, |
michael@0 | 821 | "Script %p:%s:%d compiling off-thread", |
michael@0 | 822 | script.get(), script->filename(), script->lineno()); |
michael@0 | 823 | offMainThreadCompilationsInProgress = true; |
michael@0 | 824 | continue; |
michael@0 | 825 | } |
michael@0 | 826 | return sequentialExecution(false, status); |
michael@0 | 827 | |
michael@0 | 828 | case Method_Compiled: |
michael@0 | 829 | Spew(SpewCompile, |
michael@0 | 830 | "Script %p:%s:%d compiled", |
michael@0 | 831 | script.get(), script->filename(), script->lineno()); |
michael@0 | 832 | JS_ASSERT(script->hasParallelIonScript()); |
michael@0 | 833 | |
michael@0 | 834 | if (isInitialScript(script)) { |
michael@0 | 835 | JitCompartment *jitComp = cx_->compartment()->jitCompartment(); |
michael@0 | 836 | if (!jitComp->notifyOfActiveParallelEntryScript(cx_, script)) { |
michael@0 | 837 | *status = ExecutionFatal; |
michael@0 | 838 | return RedLight; |
michael@0 | 839 | } |
michael@0 | 840 | } |
michael@0 | 841 | |
michael@0 | 842 | break; |
michael@0 | 843 | } |
michael@0 | 844 | } |
michael@0 | 845 | |
michael@0 | 846 | // At this point, either the script was already compiled |
michael@0 | 847 | // or we just compiled it. Check whether its "uncompiled |
michael@0 | 848 | // call target" flag is set and add the targets to our |
michael@0 | 849 | // worklist if so. Clear the flag after that, since we |
michael@0 | 850 | // will be compiling the call targets. |
michael@0 | 851 | JS_ASSERT(script->hasParallelIonScript()); |
michael@0 | 852 | if (appendCallTargetsToWorklist(i, status) == RedLight) |
michael@0 | 853 | return RedLight; |
michael@0 | 854 | } |
michael@0 | 855 | |
michael@0 | 856 | // If there is compilation occurring in a helper thread, then |
michael@0 | 857 | // run a warmup iterations in the main thread while we wait. |
michael@0 | 858 | // There is a chance that this warmup will finish all the work |
michael@0 | 859 | // we have to do, so we should stop then, unless we are in |
michael@0 | 860 | // compile mode, in which case we'll continue to block. |
michael@0 | 861 | // |
michael@0 | 862 | // Note that even in compile mode, we can't block *forever*: |
michael@0 | 863 | // - OMTC compiles will finish; |
michael@0 | 864 | // - no work is being done, so use counts on not-yet-baselined |
michael@0 | 865 | // scripts will not increase. |
michael@0 | 866 | if (offMainThreadCompilationsInProgress || gatheringTypeInformation) { |
michael@0 | 867 | bool stopIfComplete = (mode_ != ForkJoinModeCompile); |
michael@0 | 868 | if (warmupExecution(stopIfComplete, status) == RedLight) |
michael@0 | 869 | return RedLight; |
michael@0 | 870 | continue; |
michael@0 | 871 | } |
michael@0 | 872 | |
michael@0 | 873 | // All compilations are complete. However, be careful: it is |
michael@0 | 874 | // possible that a garbage collection occurred while we were |
michael@0 | 875 | // iterating and caused some of the scripts we thought we had |
michael@0 | 876 | // compiled to be collected. In that case, we will just have |
michael@0 | 877 | // to begin again. |
michael@0 | 878 | bool allScriptsPresent = true; |
michael@0 | 879 | for (uint32_t i = 0; i < worklist_.length(); i++) { |
michael@0 | 880 | if (!worklist_[i]->hasParallelIonScript()) { |
michael@0 | 881 | if (worklistData_[i].stallCount < stallThreshold) { |
michael@0 | 882 | worklistData_[i].reset(); |
michael@0 | 883 | allScriptsPresent = false; |
michael@0 | 884 | |
michael@0 | 885 | Spew(SpewCompile, |
michael@0 | 886 | "Script %p:%s:%d is not stalled, " |
michael@0 | 887 | "but no parallel ion script found, " |
michael@0 | 888 | "restarting loop", |
michael@0 | 889 | script.get(), script->filename(), script->lineno()); |
michael@0 | 890 | } |
michael@0 | 891 | } |
michael@0 | 892 | } |
michael@0 | 893 | |
michael@0 | 894 | if (allScriptsPresent) |
michael@0 | 895 | break; |
michael@0 | 896 | } |
michael@0 | 897 | |
michael@0 | 898 | Spew(SpewCompile, "Compilation complete (final worklist length %d)", |
michael@0 | 899 | worklist_.length()); |
michael@0 | 900 | |
michael@0 | 901 | // At this point, all scripts and their transitive callees are |
michael@0 | 902 | // either stalled (indicating they are unlikely to be called) or |
michael@0 | 903 | // in a compiled state. Therefore we can clear the |
michael@0 | 904 | // "hasUncompiledCallTarget" flag on them and then clear the |
michael@0 | 905 | // worklist. |
michael@0 | 906 | for (uint32_t i = 0; i < worklist_.length(); i++) { |
michael@0 | 907 | if (worklist_[i]->hasParallelIonScript()) { |
michael@0 | 908 | JS_ASSERT(worklistData_[i].calleesEnqueued); |
michael@0 | 909 | worklist_[i]->parallelIonScript()->clearHasUncompiledCallTarget(); |
michael@0 | 910 | } else { |
michael@0 | 911 | JS_ASSERT(worklistData_[i].stallCount >= stallThreshold); |
michael@0 | 912 | } |
michael@0 | 913 | } |
michael@0 | 914 | worklist_.clear(); |
michael@0 | 915 | worklistData_.clear(); |
michael@0 | 916 | return GreenLight; |
michael@0 | 917 | } |
michael@0 | 918 | |
michael@0 | 919 | ForkJoinOperation::TrafficLight |
michael@0 | 920 | ForkJoinOperation::appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status) |
michael@0 | 921 | { |
michael@0 | 922 | // GreenLight: call targets appended |
michael@0 | 923 | // RedLight: fatal error or completed work via warmups or fallback |
michael@0 | 924 | |
michael@0 | 925 | JS_ASSERT(worklist_[index]->hasParallelIonScript()); |
michael@0 | 926 | |
michael@0 | 927 | // Check whether we have already enqueued the targets for |
michael@0 | 928 | // this entry and avoid doing it again if so. |
michael@0 | 929 | if (worklistData_[index].calleesEnqueued) |
michael@0 | 930 | return GreenLight; |
michael@0 | 931 | worklistData_[index].calleesEnqueued = true; |
michael@0 | 932 | |
michael@0 | 933 | // Iterate through the callees and enqueue them. |
michael@0 | 934 | RootedScript target(cx_); |
michael@0 | 935 | IonScript *ion = worklist_[index]->parallelIonScript(); |
michael@0 | 936 | for (uint32_t i = 0; i < ion->callTargetEntries(); i++) { |
michael@0 | 937 | target = ion->callTargetList()[i]; |
michael@0 | 938 | parallel::Spew(parallel::SpewCompile, |
michael@0 | 939 | "Adding call target %s:%u", |
michael@0 | 940 | target->filename(), target->lineno()); |
michael@0 | 941 | if (appendCallTargetToWorklist(target, status) == RedLight) |
michael@0 | 942 | return RedLight; |
michael@0 | 943 | } |
michael@0 | 944 | |
michael@0 | 945 | return GreenLight; |
michael@0 | 946 | } |
michael@0 | 947 | |
michael@0 | 948 | ForkJoinOperation::TrafficLight |
michael@0 | 949 | ForkJoinOperation::appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status) |
michael@0 | 950 | { |
michael@0 | 951 | // GreenLight: call target appended if necessary |
michael@0 | 952 | // RedLight: fatal error or completed work via warmups or fallback |
michael@0 | 953 | |
michael@0 | 954 | JS_ASSERT(script); |
michael@0 | 955 | |
michael@0 | 956 | // Fallback to sequential if disabled. |
michael@0 | 957 | if (!script->canParallelIonCompile()) { |
michael@0 | 958 | Spew(SpewCompile, "Skipping %p:%s:%u, canParallelIonCompile() is false", |
michael@0 | 959 | script.get(), script->filename(), script->lineno()); |
michael@0 | 960 | return sequentialExecution(true, status); |
michael@0 | 961 | } |
michael@0 | 962 | |
michael@0 | 963 | if (script->hasParallelIonScript()) { |
michael@0 | 964 | // Skip if the code is expected to result in a bailout. |
michael@0 | 965 | if (script->parallelIonScript()->bailoutExpected()) { |
michael@0 | 966 | Spew(SpewCompile, "Skipping %p:%s:%u, bailout expected", |
michael@0 | 967 | script.get(), script->filename(), script->lineno()); |
michael@0 | 968 | return sequentialExecution(false, status); |
michael@0 | 969 | } |
michael@0 | 970 | } |
michael@0 | 971 | |
michael@0 | 972 | if (!addToWorklist(script)) |
michael@0 | 973 | return fatalError(status); |
michael@0 | 974 | |
michael@0 | 975 | return GreenLight; |
michael@0 | 976 | } |
michael@0 | 977 | |
michael@0 | 978 | bool |
michael@0 | 979 | ForkJoinOperation::addToWorklist(HandleScript script) |
michael@0 | 980 | { |
michael@0 | 981 | for (uint32_t i = 0; i < worklist_.length(); i++) { |
michael@0 | 982 | if (worklist_[i] == script) { |
michael@0 | 983 | Spew(SpewCompile, "Skipping %p:%s:%u, already in worklist", |
michael@0 | 984 | script.get(), script->filename(), script->lineno()); |
michael@0 | 985 | return true; |
michael@0 | 986 | } |
michael@0 | 987 | } |
michael@0 | 988 | |
michael@0 | 989 | Spew(SpewCompile, "Enqueued %p:%s:%u", |
michael@0 | 990 | script.get(), script->filename(), script->lineno()); |
michael@0 | 991 | |
michael@0 | 992 | // Note that we add all possibly compilable functions to the worklist, |
michael@0 | 993 | // even if they're already compiled. This is so that we can return |
michael@0 | 994 | // Method_Compiled and not Method_Skipped if we have a worklist full of |
michael@0 | 995 | // already-compiled functions. |
michael@0 | 996 | if (!worklist_.append(script)) |
michael@0 | 997 | return false; |
michael@0 | 998 | |
michael@0 | 999 | // we have not yet enqueued the callees of this script |
michael@0 | 1000 | if (!worklistData_.append(WorklistData())) |
michael@0 | 1001 | return false; |
michael@0 | 1002 | worklistData_[worklistData_.length() - 1].reset(); |
michael@0 | 1003 | |
michael@0 | 1004 | return true; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | ForkJoinOperation::TrafficLight |
michael@0 | 1008 | ForkJoinOperation::sequentialExecution(bool disqualified, ExecutionStatus *status) |
michael@0 | 1009 | { |
michael@0 | 1010 | // RedLight: fatal error or completed work |
michael@0 | 1011 | |
michael@0 | 1012 | *status = sequentialExecution(disqualified); |
michael@0 | 1013 | return RedLight; |
michael@0 | 1014 | } |
michael@0 | 1015 | |
michael@0 | 1016 | ExecutionStatus |
michael@0 | 1017 | ForkJoinOperation::sequentialExecution(bool disqualified) |
michael@0 | 1018 | { |
michael@0 | 1019 | // XXX use disqualified to set parallelIon to ION_DISABLED_SCRIPT? |
michael@0 | 1020 | |
michael@0 | 1021 | Spew(SpewOps, "Executing sequential execution (disqualified=%d).", |
michael@0 | 1022 | disqualified); |
michael@0 | 1023 | |
michael@0 | 1024 | if (sliceStart_ == sliceEnd_) |
michael@0 | 1025 | return ExecutionSequential; |
michael@0 | 1026 | |
michael@0 | 1027 | RootedValue funVal(cx_, ObjectValue(*fun_)); |
michael@0 | 1028 | if (!ExecuteSequentially(cx_, funVal, &sliceStart_, sliceEnd_)) |
michael@0 | 1029 | return ExecutionFatal; |
michael@0 | 1030 | MOZ_ASSERT(sliceStart_ == sliceEnd_); |
michael@0 | 1031 | return ExecutionSequential; |
michael@0 | 1032 | } |
michael@0 | 1033 | |
michael@0 | 1034 | ForkJoinOperation::TrafficLight |
michael@0 | 1035 | ForkJoinOperation::fatalError(ExecutionStatus *status) |
michael@0 | 1036 | { |
michael@0 | 1037 | // RedLight: fatal error |
michael@0 | 1038 | |
michael@0 | 1039 | *status = ExecutionFatal; |
michael@0 | 1040 | return RedLight; |
michael@0 | 1041 | } |
michael@0 | 1042 | |
michael@0 | 1043 | static const char * |
michael@0 | 1044 | BailoutExplanation(ParallelBailoutCause cause) |
michael@0 | 1045 | { |
michael@0 | 1046 | switch (cause) { |
michael@0 | 1047 | case ParallelBailoutNone: |
michael@0 | 1048 | return "no particular reason"; |
michael@0 | 1049 | case ParallelBailoutCompilationSkipped: |
michael@0 | 1050 | return "compilation failed (method skipped)"; |
michael@0 | 1051 | case ParallelBailoutCompilationFailure: |
michael@0 | 1052 | return "compilation failed"; |
michael@0 | 1053 | case ParallelBailoutInterrupt: |
michael@0 | 1054 | return "interrupted"; |
michael@0 | 1055 | case ParallelBailoutFailedIC: |
michael@0 | 1056 | return "failed to attach stub to IC"; |
michael@0 | 1057 | case ParallelBailoutHeapBusy: |
michael@0 | 1058 | return "heap busy flag set during interrupt"; |
michael@0 | 1059 | case ParallelBailoutMainScriptNotPresent: |
michael@0 | 1060 | return "main script not present"; |
michael@0 | 1061 | case ParallelBailoutCalledToUncompiledScript: |
michael@0 | 1062 | return "called to uncompiled script"; |
michael@0 | 1063 | case ParallelBailoutIllegalWrite: |
michael@0 | 1064 | return "illegal write"; |
michael@0 | 1065 | case ParallelBailoutAccessToIntrinsic: |
michael@0 | 1066 | return "access to intrinsic"; |
michael@0 | 1067 | case ParallelBailoutOverRecursed: |
michael@0 | 1068 | return "over recursed"; |
michael@0 | 1069 | case ParallelBailoutOutOfMemory: |
michael@0 | 1070 | return "out of memory"; |
michael@0 | 1071 | case ParallelBailoutUnsupported: |
michael@0 | 1072 | return "unsupported"; |
michael@0 | 1073 | case ParallelBailoutUnsupportedVM: |
michael@0 | 1074 | return "unsupported operation in VM call"; |
michael@0 | 1075 | case ParallelBailoutUnsupportedStringComparison: |
michael@0 | 1076 | return "unsupported string comparison"; |
michael@0 | 1077 | case ParallelBailoutRequestedGC: |
michael@0 | 1078 | return "requested GC"; |
michael@0 | 1079 | case ParallelBailoutRequestedZoneGC: |
michael@0 | 1080 | return "requested zone GC"; |
michael@0 | 1081 | default: |
michael@0 | 1082 | return "no known reason"; |
michael@0 | 1083 | } |
michael@0 | 1084 | } |
michael@0 | 1085 | |
michael@0 | 1086 | bool |
michael@0 | 1087 | ForkJoinOperation::isInitialScript(HandleScript script) |
michael@0 | 1088 | { |
michael@0 | 1089 | return fun_->is<JSFunction>() && (fun_->as<JSFunction>().nonLazyScript() == script); |
michael@0 | 1090 | } |
michael@0 | 1091 | |
michael@0 | 1092 | void |
michael@0 | 1093 | ForkJoinOperation::determineBailoutCause() |
michael@0 | 1094 | { |
michael@0 | 1095 | bailoutCause = ParallelBailoutNone; |
michael@0 | 1096 | for (uint32_t i = 0; i < bailoutRecords_.length(); i++) { |
michael@0 | 1097 | if (bailoutRecords_[i].cause == ParallelBailoutNone) |
michael@0 | 1098 | continue; |
michael@0 | 1099 | |
michael@0 | 1100 | if (bailoutRecords_[i].cause == ParallelBailoutInterrupt) |
michael@0 | 1101 | continue; |
michael@0 | 1102 | |
michael@0 | 1103 | bailoutCause = bailoutRecords_[i].cause; |
michael@0 | 1104 | const char *causeStr = BailoutExplanation(bailoutCause); |
michael@0 | 1105 | if (bailoutRecords_[i].depth) { |
michael@0 | 1106 | bailoutScript = bailoutRecords_[i].trace[0].script; |
michael@0 | 1107 | bailoutBytecode = bailoutRecords_[i].trace[0].bytecode; |
michael@0 | 1108 | |
michael@0 | 1109 | const char *filename = bailoutScript->filename(); |
michael@0 | 1110 | int line = JS_PCToLineNumber(cx_, bailoutScript, bailoutBytecode); |
michael@0 | 1111 | JS_ReportWarning(cx_, "Bailed out of parallel operation: %s at %s:%d", |
michael@0 | 1112 | causeStr, filename, line); |
michael@0 | 1113 | |
michael@0 | 1114 | Spew(SpewBailouts, "Bailout from thread %d: cause %d at loc %s:%d", |
michael@0 | 1115 | i, |
michael@0 | 1116 | bailoutCause, |
michael@0 | 1117 | bailoutScript->filename(), |
michael@0 | 1118 | PCToLineNumber(bailoutScript, bailoutBytecode)); |
michael@0 | 1119 | } else { |
michael@0 | 1120 | JS_ReportWarning(cx_, "Bailed out of parallel operation: %s", |
michael@0 | 1121 | causeStr); |
michael@0 | 1122 | |
michael@0 | 1123 | Spew(SpewBailouts, "Bailout from thread %d: cause %d, unknown loc", |
michael@0 | 1124 | i, |
michael@0 | 1125 | bailoutCause); |
michael@0 | 1126 | } |
michael@0 | 1127 | } |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | bool |
michael@0 | 1131 | ForkJoinOperation::invalidateBailedOutScripts() |
michael@0 | 1132 | { |
michael@0 | 1133 | Vector<types::RecompileInfo> invalid(cx_); |
michael@0 | 1134 | for (uint32_t i = 0; i < bailoutRecords_.length(); i++) { |
michael@0 | 1135 | RootedScript script(cx_, bailoutRecords_[i].topScript); |
michael@0 | 1136 | |
michael@0 | 1137 | // No script to invalidate. |
michael@0 | 1138 | if (!script || !script->hasParallelIonScript()) |
michael@0 | 1139 | continue; |
michael@0 | 1140 | |
michael@0 | 1141 | Spew(SpewBailouts, |
michael@0 | 1142 | "Bailout from thread %d: cause %d, topScript %p:%s:%d", |
michael@0 | 1143 | i, |
michael@0 | 1144 | bailoutRecords_[i].cause, |
michael@0 | 1145 | script.get(), script->filename(), script->lineno()); |
michael@0 | 1146 | |
michael@0 | 1147 | switch (bailoutRecords_[i].cause) { |
michael@0 | 1148 | // An interrupt is not the fault of the script, so don't |
michael@0 | 1149 | // invalidate it. |
michael@0 | 1150 | case ParallelBailoutInterrupt: continue; |
michael@0 | 1151 | |
michael@0 | 1152 | // An illegal write will not be made legal by invalidation. |
michael@0 | 1153 | case ParallelBailoutIllegalWrite: continue; |
michael@0 | 1154 | |
michael@0 | 1155 | // For other cases, consider invalidation. |
michael@0 | 1156 | default: break; |
michael@0 | 1157 | } |
michael@0 | 1158 | |
michael@0 | 1159 | // Already invalidated. |
michael@0 | 1160 | if (hasScript(invalid, script)) |
michael@0 | 1161 | continue; |
michael@0 | 1162 | |
michael@0 | 1163 | Spew(SpewBailouts, "Invalidating script %p:%s:%d due to cause %d", |
michael@0 | 1164 | script.get(), script->filename(), script->lineno(), |
michael@0 | 1165 | bailoutRecords_[i].cause); |
michael@0 | 1166 | |
michael@0 | 1167 | types::RecompileInfo co = script->parallelIonScript()->recompileInfo(); |
michael@0 | 1168 | |
michael@0 | 1169 | if (!invalid.append(co)) |
michael@0 | 1170 | return false; |
michael@0 | 1171 | |
michael@0 | 1172 | // any script that we have marked for invalidation will need |
michael@0 | 1173 | // to be recompiled |
michael@0 | 1174 | if (!addToWorklist(script)) |
michael@0 | 1175 | return false; |
michael@0 | 1176 | } |
michael@0 | 1177 | |
michael@0 | 1178 | Invalidate(cx_, invalid); |
michael@0 | 1179 | |
michael@0 | 1180 | return true; |
michael@0 | 1181 | } |
michael@0 | 1182 | |
michael@0 | 1183 | ForkJoinOperation::TrafficLight |
michael@0 | 1184 | ForkJoinOperation::warmupExecution(bool stopIfComplete, ExecutionStatus *status) |
michael@0 | 1185 | { |
michael@0 | 1186 | // GreenLight: warmup succeeded, still more work to do |
michael@0 | 1187 | // RedLight: fatal error or warmup completed all work (check status) |
michael@0 | 1188 | |
michael@0 | 1189 | if (sliceStart_ == sliceEnd_) { |
michael@0 | 1190 | Spew(SpewOps, "Warmup execution finished all the work."); |
michael@0 | 1191 | |
michael@0 | 1192 | if (stopIfComplete) { |
michael@0 | 1193 | *status = ExecutionWarmup; |
michael@0 | 1194 | return RedLight; |
michael@0 | 1195 | } |
michael@0 | 1196 | |
michael@0 | 1197 | // If we finished all slices in warmup, be sure check the |
michael@0 | 1198 | // interrupt flag. This is because we won't be running more JS |
michael@0 | 1199 | // code, and thus no more automatic checking of the interrupt |
michael@0 | 1200 | // flag. |
michael@0 | 1201 | if (!CheckForInterrupt(cx_)) { |
michael@0 | 1202 | *status = ExecutionFatal; |
michael@0 | 1203 | return RedLight; |
michael@0 | 1204 | } |
michael@0 | 1205 | |
michael@0 | 1206 | return GreenLight; |
michael@0 | 1207 | } |
michael@0 | 1208 | |
michael@0 | 1209 | Spew(SpewOps, "Executing warmup from slice %d.", sliceStart_); |
michael@0 | 1210 | |
michael@0 | 1211 | AutoEnterWarmup warmup(cx_->runtime()); |
michael@0 | 1212 | RootedValue funVal(cx_, ObjectValue(*fun_)); |
michael@0 | 1213 | if (!ExecuteSequentially(cx_, funVal, &sliceStart_, sliceStart_ + 1)) { |
michael@0 | 1214 | *status = ExecutionFatal; |
michael@0 | 1215 | return RedLight; |
michael@0 | 1216 | } |
michael@0 | 1217 | |
michael@0 | 1218 | return GreenLight; |
michael@0 | 1219 | } |
michael@0 | 1220 | |
michael@0 | 1221 | ForkJoinOperation::TrafficLight |
michael@0 | 1222 | ForkJoinOperation::parallelExecution(ExecutionStatus *status) |
michael@0 | 1223 | { |
michael@0 | 1224 | // GreenLight: bailout occurred, keep trying |
michael@0 | 1225 | // RedLight: fatal error or all work completed |
michael@0 | 1226 | |
michael@0 | 1227 | // Recursive use of the ThreadPool is not supported. Right now we |
michael@0 | 1228 | // cannot get here because parallel code cannot invoke native |
michael@0 | 1229 | // functions such as ForkJoin(). |
michael@0 | 1230 | JS_ASSERT(ForkJoinContext::current() == nullptr); |
michael@0 | 1231 | |
michael@0 | 1232 | if (sliceStart_ == sliceEnd_) { |
michael@0 | 1233 | Spew(SpewOps, "Warmup execution finished all the work."); |
michael@0 | 1234 | *status = ExecutionWarmup; |
michael@0 | 1235 | return RedLight; |
michael@0 | 1236 | } |
michael@0 | 1237 | |
michael@0 | 1238 | ForkJoinActivation activation(cx_); |
michael@0 | 1239 | ThreadPool *threadPool = &cx_->runtime()->threadPool; |
michael@0 | 1240 | ForkJoinShared shared(cx_, threadPool, fun_, sliceStart_, sliceEnd_, &bailoutRecords_[0]); |
michael@0 | 1241 | if (!shared.init()) { |
michael@0 | 1242 | *status = ExecutionFatal; |
michael@0 | 1243 | return RedLight; |
michael@0 | 1244 | } |
michael@0 | 1245 | |
michael@0 | 1246 | switch (shared.execute()) { |
michael@0 | 1247 | case TP_SUCCESS: |
michael@0 | 1248 | *status = ExecutionParallel; |
michael@0 | 1249 | return RedLight; |
michael@0 | 1250 | |
michael@0 | 1251 | case TP_FATAL: |
michael@0 | 1252 | *status = ExecutionFatal; |
michael@0 | 1253 | return RedLight; |
michael@0 | 1254 | |
michael@0 | 1255 | case TP_RETRY_SEQUENTIALLY: |
michael@0 | 1256 | case TP_RETRY_AFTER_GC: |
michael@0 | 1257 | break; // bailout |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | return GreenLight; |
michael@0 | 1261 | } |
michael@0 | 1262 | |
michael@0 | 1263 | ForkJoinOperation::TrafficLight |
michael@0 | 1264 | ForkJoinOperation::recoverFromBailout(ExecutionStatus *status) |
michael@0 | 1265 | { |
michael@0 | 1266 | // GreenLight: bailout recovered, try to compile-and-run again |
michael@0 | 1267 | // RedLight: fatal error |
michael@0 | 1268 | |
michael@0 | 1269 | bailouts += 1; |
michael@0 | 1270 | determineBailoutCause(); |
michael@0 | 1271 | |
michael@0 | 1272 | SpewBailout(bailouts, bailoutScript, bailoutBytecode, bailoutCause); |
michael@0 | 1273 | |
michael@0 | 1274 | // After any bailout, we always scan over callee list of main |
michael@0 | 1275 | // function, if nothing else |
michael@0 | 1276 | RootedScript mainScript(cx_, fun_->nonLazyScript()); |
michael@0 | 1277 | if (!addToWorklist(mainScript)) |
michael@0 | 1278 | return fatalError(status); |
michael@0 | 1279 | |
michael@0 | 1280 | // Also invalidate and recompile any callees that were implicated |
michael@0 | 1281 | // by the bailout |
michael@0 | 1282 | if (!invalidateBailedOutScripts()) |
michael@0 | 1283 | return fatalError(status); |
michael@0 | 1284 | |
michael@0 | 1285 | if (warmupExecution(/*stopIfComplete:*/true, status) == RedLight) |
michael@0 | 1286 | return RedLight; |
michael@0 | 1287 | |
michael@0 | 1288 | return GreenLight; |
michael@0 | 1289 | } |
michael@0 | 1290 | |
michael@0 | 1291 | bool |
michael@0 | 1292 | ForkJoinOperation::hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script) |
michael@0 | 1293 | { |
michael@0 | 1294 | for (uint32_t i = 0; i < scripts.length(); i++) { |
michael@0 | 1295 | if (scripts[i] == script->parallelIonScript()->recompileInfo()) |
michael@0 | 1296 | return true; |
michael@0 | 1297 | } |
michael@0 | 1298 | return false; |
michael@0 | 1299 | } |
michael@0 | 1300 | |
michael@0 | 1301 | // Can only enter callees with a valid IonScript. |
michael@0 | 1302 | template <uint32_t maxArgc> |
michael@0 | 1303 | class ParallelIonInvoke |
michael@0 | 1304 | { |
michael@0 | 1305 | EnterJitCode enter_; |
michael@0 | 1306 | void *jitcode_; |
michael@0 | 1307 | void *calleeToken_; |
michael@0 | 1308 | Value argv_[maxArgc + 2]; |
michael@0 | 1309 | uint32_t argc_; |
michael@0 | 1310 | |
michael@0 | 1311 | public: |
michael@0 | 1312 | Value *args; |
michael@0 | 1313 | |
michael@0 | 1314 | ParallelIonInvoke(JSRuntime *rt, |
michael@0 | 1315 | HandleFunction callee, |
michael@0 | 1316 | uint32_t argc) |
michael@0 | 1317 | : argc_(argc), |
michael@0 | 1318 | args(argv_ + 2) |
michael@0 | 1319 | { |
michael@0 | 1320 | JS_ASSERT(argc <= maxArgc + 2); |
michael@0 | 1321 | |
michael@0 | 1322 | // Set 'callee' and 'this'. |
michael@0 | 1323 | argv_[0] = ObjectValue(*callee); |
michael@0 | 1324 | argv_[1] = UndefinedValue(); |
michael@0 | 1325 | |
michael@0 | 1326 | // Find JIT code pointer. |
michael@0 | 1327 | IonScript *ion = callee->nonLazyScript()->parallelIonScript(); |
michael@0 | 1328 | JitCode *code = ion->method(); |
michael@0 | 1329 | jitcode_ = code->raw(); |
michael@0 | 1330 | enter_ = rt->jitRuntime()->enterIon(); |
michael@0 | 1331 | calleeToken_ = CalleeToToken(callee); |
michael@0 | 1332 | } |
michael@0 | 1333 | |
michael@0 | 1334 | bool invoke(PerThreadData *perThread) { |
michael@0 | 1335 | RootedValue result(perThread); |
michael@0 | 1336 | CALL_GENERATED_CODE(enter_, jitcode_, argc_ + 1, argv_ + 1, nullptr, calleeToken_, |
michael@0 | 1337 | nullptr, 0, result.address()); |
michael@0 | 1338 | return !result.isMagic(); |
michael@0 | 1339 | } |
michael@0 | 1340 | }; |
michael@0 | 1341 | |
michael@0 | 1342 | ///////////////////////////////////////////////////////////////////////////// |
michael@0 | 1343 | // ForkJoinShared |
michael@0 | 1344 | // |
michael@0 | 1345 | |
michael@0 | 1346 | ForkJoinShared::ForkJoinShared(JSContext *cx, |
michael@0 | 1347 | ThreadPool *threadPool, |
michael@0 | 1348 | HandleFunction fun, |
michael@0 | 1349 | uint16_t sliceStart, |
michael@0 | 1350 | uint16_t sliceEnd, |
michael@0 | 1351 | ParallelBailoutRecord *records) |
michael@0 | 1352 | : cx_(cx), |
michael@0 | 1353 | threadPool_(threadPool), |
michael@0 | 1354 | fun_(fun), |
michael@0 | 1355 | sliceStart_(sliceStart), |
michael@0 | 1356 | sliceEnd_(sliceEnd), |
michael@0 | 1357 | cxLock_(nullptr), |
michael@0 | 1358 | records_(records), |
michael@0 | 1359 | allocators_(cx), |
michael@0 | 1360 | gcRequested_(false), |
michael@0 | 1361 | gcReason_(JS::gcreason::NUM_REASONS), |
michael@0 | 1362 | gcZone_(nullptr), |
michael@0 | 1363 | abort_(false), |
michael@0 | 1364 | fatal_(false) |
michael@0 | 1365 | { |
michael@0 | 1366 | } |
michael@0 | 1367 | |
michael@0 | 1368 | bool |
michael@0 | 1369 | ForkJoinShared::init() |
michael@0 | 1370 | { |
michael@0 | 1371 | // Create temporary arenas to hold the data allocated during the |
michael@0 | 1372 | // parallel code. |
michael@0 | 1373 | // |
michael@0 | 1374 | // Note: you might think (as I did, initially) that we could use |
michael@0 | 1375 | // compartment |Allocator| for the main thread. This is not true, |
michael@0 | 1376 | // because when executing parallel code we sometimes check what |
michael@0 | 1377 | // arena list an object is in to decide if it is writable. If we |
michael@0 | 1378 | // used the compartment |Allocator| for the main thread, then the |
michael@0 | 1379 | // main thread would be permitted to write to any object it wants. |
michael@0 | 1380 | |
michael@0 | 1381 | if (!Monitor::init()) |
michael@0 | 1382 | return false; |
michael@0 | 1383 | |
michael@0 | 1384 | cxLock_ = PR_NewLock(); |
michael@0 | 1385 | if (!cxLock_) |
michael@0 | 1386 | return false; |
michael@0 | 1387 | |
michael@0 | 1388 | for (unsigned i = 0; i < threadPool_->numWorkers(); i++) { |
michael@0 | 1389 | Allocator *allocator = cx_->new_<Allocator>(cx_->zone()); |
michael@0 | 1390 | if (!allocator) |
michael@0 | 1391 | return false; |
michael@0 | 1392 | |
michael@0 | 1393 | if (!allocators_.append(allocator)) { |
michael@0 | 1394 | js_delete(allocator); |
michael@0 | 1395 | return false; |
michael@0 | 1396 | } |
michael@0 | 1397 | } |
michael@0 | 1398 | |
michael@0 | 1399 | return true; |
michael@0 | 1400 | } |
michael@0 | 1401 | |
michael@0 | 1402 | ForkJoinShared::~ForkJoinShared() |
michael@0 | 1403 | { |
michael@0 | 1404 | PR_DestroyLock(cxLock_); |
michael@0 | 1405 | |
michael@0 | 1406 | while (allocators_.length() > 0) |
michael@0 | 1407 | js_delete(allocators_.popCopy()); |
michael@0 | 1408 | } |
michael@0 | 1409 | |
michael@0 | 1410 | ParallelResult |
michael@0 | 1411 | ForkJoinShared::execute() |
michael@0 | 1412 | { |
michael@0 | 1413 | // Sometimes a GC request occurs *just before* we enter into the |
michael@0 | 1414 | // parallel section. Rather than enter into the parallel section |
michael@0 | 1415 | // and then abort, we just check here and abort early. |
michael@0 | 1416 | if (cx_->runtime()->interruptPar) |
michael@0 | 1417 | return TP_RETRY_SEQUENTIALLY; |
michael@0 | 1418 | |
michael@0 | 1419 | AutoLockMonitor lock(*this); |
michael@0 | 1420 | |
michael@0 | 1421 | ParallelResult jobResult = TP_SUCCESS; |
michael@0 | 1422 | { |
michael@0 | 1423 | AutoUnlockMonitor unlock(*this); |
michael@0 | 1424 | |
michael@0 | 1425 | // Push parallel tasks and wait until they're all done. |
michael@0 | 1426 | jobResult = threadPool_->executeJob(cx_, this, sliceStart_, sliceEnd_); |
michael@0 | 1427 | if (jobResult == TP_FATAL) |
michael@0 | 1428 | return TP_FATAL; |
michael@0 | 1429 | } |
michael@0 | 1430 | |
michael@0 | 1431 | transferArenasToCompartmentAndProcessGCRequests(); |
michael@0 | 1432 | |
michael@0 | 1433 | // Check if any of the workers failed. |
michael@0 | 1434 | if (abort_) { |
michael@0 | 1435 | if (fatal_) |
michael@0 | 1436 | return TP_FATAL; |
michael@0 | 1437 | return TP_RETRY_SEQUENTIALLY; |
michael@0 | 1438 | } |
michael@0 | 1439 | |
michael@0 | 1440 | #ifdef DEBUG |
michael@0 | 1441 | Spew(SpewOps, "Completed parallel job [slices: %d, threads: %d, stolen: %d (work stealing:%s)]", |
michael@0 | 1442 | sliceEnd_ - sliceStart_, |
michael@0 | 1443 | threadPool_->numWorkers(), |
michael@0 | 1444 | threadPool_->stolenSlices(), |
michael@0 | 1445 | threadPool_->workStealing() ? "ON" : "OFF"); |
michael@0 | 1446 | #endif |
michael@0 | 1447 | |
michael@0 | 1448 | // Everything went swimmingly. Give yourself a pat on the back. |
michael@0 | 1449 | return jobResult; |
michael@0 | 1450 | } |
michael@0 | 1451 | |
michael@0 | 1452 | void |
michael@0 | 1453 | ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests() |
michael@0 | 1454 | { |
michael@0 | 1455 | JSCompartment *comp = cx_->compartment(); |
michael@0 | 1456 | for (unsigned i = 0; i < threadPool_->numWorkers(); i++) |
michael@0 | 1457 | comp->adoptWorkerAllocator(allocators_[i]); |
michael@0 | 1458 | |
michael@0 | 1459 | if (gcRequested_) { |
michael@0 | 1460 | if (!gcZone_) |
michael@0 | 1461 | TriggerGC(cx_->runtime(), gcReason_); |
michael@0 | 1462 | else |
michael@0 | 1463 | TriggerZoneGC(gcZone_, gcReason_); |
michael@0 | 1464 | gcRequested_ = false; |
michael@0 | 1465 | gcZone_ = nullptr; |
michael@0 | 1466 | } |
michael@0 | 1467 | } |
michael@0 | 1468 | |
michael@0 | 1469 | bool |
michael@0 | 1470 | ForkJoinShared::executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit) |
michael@0 | 1471 | { |
michael@0 | 1472 | PerThreadData thisThread(cx_->runtime()); |
michael@0 | 1473 | if (!thisThread.init()) { |
michael@0 | 1474 | setAbortFlagAndRequestInterrupt(true); |
michael@0 | 1475 | return false; |
michael@0 | 1476 | } |
michael@0 | 1477 | TlsPerThreadData.set(&thisThread); |
michael@0 | 1478 | |
michael@0 | 1479 | #ifdef JS_ARM_SIMULATOR |
michael@0 | 1480 | stackLimit = Simulator::StackLimit(); |
michael@0 | 1481 | #endif |
michael@0 | 1482 | |
michael@0 | 1483 | // Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the |
michael@0 | 1484 | // lock has not been initialized in these cases. |
michael@0 | 1485 | thisThread.jitStackLimit = stackLimit; |
michael@0 | 1486 | executePortion(&thisThread, worker); |
michael@0 | 1487 | TlsPerThreadData.set(nullptr); |
michael@0 | 1488 | |
michael@0 | 1489 | return !abort_; |
michael@0 | 1490 | } |
michael@0 | 1491 | |
michael@0 | 1492 | bool |
michael@0 | 1493 | ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker) |
michael@0 | 1494 | { |
michael@0 | 1495 | executePortion(&cx_->mainThread(), worker); |
michael@0 | 1496 | return !abort_; |
michael@0 | 1497 | } |
michael@0 | 1498 | |
michael@0 | 1499 | void |
michael@0 | 1500 | ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worker) |
michael@0 | 1501 | { |
michael@0 | 1502 | // WARNING: This code runs ON THE PARALLEL WORKER THREAD. |
michael@0 | 1503 | // Be careful when accessing cx_. |
michael@0 | 1504 | |
michael@0 | 1505 | // ForkJoinContext already contains an AutoAssertNoGC; however, the analysis |
michael@0 | 1506 | // does not propagate this type information. We duplicate the assertion |
michael@0 | 1507 | // here for maximum clarity. |
michael@0 | 1508 | JS::AutoAssertNoGC nogc(runtime()); |
michael@0 | 1509 | |
michael@0 | 1510 | Allocator *allocator = allocators_[worker->id()]; |
michael@0 | 1511 | ForkJoinContext cx(perThread, worker, allocator, this, &records_[worker->id()]); |
michael@0 | 1512 | AutoSetForkJoinContext autoContext(&cx); |
michael@0 | 1513 | |
michael@0 | 1514 | #ifdef DEBUG |
michael@0 | 1515 | // Set the maximum worker and slice number for prettier spewing. |
michael@0 | 1516 | cx.maxWorkerId = threadPool_->numWorkers(); |
michael@0 | 1517 | #endif |
michael@0 | 1518 | |
michael@0 | 1519 | Spew(SpewOps, "Up"); |
michael@0 | 1520 | |
michael@0 | 1521 | // Make a new IonContext for the slice, which is needed if we need to |
michael@0 | 1522 | // re-enter the VM. |
michael@0 | 1523 | IonContext icx(CompileRuntime::get(cx_->runtime()), |
michael@0 | 1524 | CompileCompartment::get(cx_->compartment()), |
michael@0 | 1525 | nullptr); |
michael@0 | 1526 | |
michael@0 | 1527 | JS_ASSERT(cx.bailoutRecord->topScript == nullptr); |
michael@0 | 1528 | |
michael@0 | 1529 | if (!fun_->nonLazyScript()->hasParallelIonScript()) { |
michael@0 | 1530 | // Sometimes, particularly with GCZeal, the parallel ion |
michael@0 | 1531 | // script can be collected between starting the parallel |
michael@0 | 1532 | // op and reaching this point. In that case, we just fail |
michael@0 | 1533 | // and fallback. |
michael@0 | 1534 | Spew(SpewOps, "Down (Script no longer present)"); |
michael@0 | 1535 | cx.bailoutRecord->setCause(ParallelBailoutMainScriptNotPresent); |
michael@0 | 1536 | setAbortFlagAndRequestInterrupt(false); |
michael@0 | 1537 | } else { |
michael@0 | 1538 | ParallelIonInvoke<3> fii(cx_->runtime(), fun_, 3); |
michael@0 | 1539 | |
michael@0 | 1540 | fii.args[0] = Int32Value(worker->id()); |
michael@0 | 1541 | fii.args[1] = Int32Value(sliceStart_); |
michael@0 | 1542 | fii.args[2] = Int32Value(sliceEnd_); |
michael@0 | 1543 | |
michael@0 | 1544 | bool ok = fii.invoke(perThread); |
michael@0 | 1545 | JS_ASSERT(ok == !cx.bailoutRecord->topScript); |
michael@0 | 1546 | if (!ok) |
michael@0 | 1547 | setAbortFlagAndRequestInterrupt(false); |
michael@0 | 1548 | } |
michael@0 | 1549 | |
michael@0 | 1550 | Spew(SpewOps, "Down"); |
michael@0 | 1551 | } |
michael@0 | 1552 | |
michael@0 | 1553 | void |
michael@0 | 1554 | ForkJoinShared::setAbortFlagDueToInterrupt(ForkJoinContext &cx) |
michael@0 | 1555 | { |
michael@0 | 1556 | JS_ASSERT(cx_->runtime()->interruptPar); |
michael@0 | 1557 | // The GC Needed flag should not be set during parallel |
michael@0 | 1558 | // execution. Instead, one of the requestGC() or |
michael@0 | 1559 | // requestZoneGC() methods should be invoked. |
michael@0 | 1560 | JS_ASSERT(!cx_->runtime()->gcIsNeeded); |
michael@0 | 1561 | |
michael@0 | 1562 | if (!abort_) { |
michael@0 | 1563 | cx.bailoutRecord->setCause(ParallelBailoutInterrupt); |
michael@0 | 1564 | setAbortFlagAndRequestInterrupt(false); |
michael@0 | 1565 | } |
michael@0 | 1566 | } |
michael@0 | 1567 | |
michael@0 | 1568 | void |
michael@0 | 1569 | ForkJoinShared::setAbortFlagAndRequestInterrupt(bool fatal) |
michael@0 | 1570 | { |
michael@0 | 1571 | AutoLockMonitor lock(*this); |
michael@0 | 1572 | |
michael@0 | 1573 | abort_ = true; |
michael@0 | 1574 | fatal_ = fatal_ || fatal; |
michael@0 | 1575 | |
michael@0 | 1576 | // Note: The ForkJoin trigger here avoids the expensive memory protection needed to |
michael@0 | 1577 | // interrupt Ion code compiled for sequential execution. |
michael@0 | 1578 | cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptAnyThreadForkJoin); |
michael@0 | 1579 | } |
michael@0 | 1580 | |
michael@0 | 1581 | void |
michael@0 | 1582 | ForkJoinShared::requestGC(JS::gcreason::Reason reason) |
michael@0 | 1583 | { |
michael@0 | 1584 | AutoLockMonitor lock(*this); |
michael@0 | 1585 | |
michael@0 | 1586 | gcZone_ = nullptr; |
michael@0 | 1587 | gcReason_ = reason; |
michael@0 | 1588 | gcRequested_ = true; |
michael@0 | 1589 | } |
michael@0 | 1590 | |
michael@0 | 1591 | void |
michael@0 | 1592 | ForkJoinShared::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) |
michael@0 | 1593 | { |
michael@0 | 1594 | AutoLockMonitor lock(*this); |
michael@0 | 1595 | |
michael@0 | 1596 | if (gcRequested_ && gcZone_ != zone) { |
michael@0 | 1597 | // If a full GC has been requested, or a GC for another zone, |
michael@0 | 1598 | // issue a request for a full GC. |
michael@0 | 1599 | gcZone_ = nullptr; |
michael@0 | 1600 | gcReason_ = reason; |
michael@0 | 1601 | gcRequested_ = true; |
michael@0 | 1602 | } else { |
michael@0 | 1603 | // Otherwise, just GC this zone. |
michael@0 | 1604 | gcZone_ = zone; |
michael@0 | 1605 | gcReason_ = reason; |
michael@0 | 1606 | gcRequested_ = true; |
michael@0 | 1607 | } |
michael@0 | 1608 | } |
michael@0 | 1609 | |
michael@0 | 1610 | ///////////////////////////////////////////////////////////////////////////// |
michael@0 | 1611 | // ForkJoinContext |
michael@0 | 1612 | // |
michael@0 | 1613 | |
michael@0 | 1614 | ForkJoinContext::ForkJoinContext(PerThreadData *perThreadData, ThreadPoolWorker *worker, |
michael@0 | 1615 | Allocator *allocator, ForkJoinShared *shared, |
michael@0 | 1616 | ParallelBailoutRecord *bailoutRecord) |
michael@0 | 1617 | : ThreadSafeContext(shared->runtime(), perThreadData, Context_ForkJoin), |
michael@0 | 1618 | bailoutRecord(bailoutRecord), |
michael@0 | 1619 | targetRegionStart(nullptr), |
michael@0 | 1620 | targetRegionEnd(nullptr), |
michael@0 | 1621 | shared_(shared), |
michael@0 | 1622 | worker_(worker), |
michael@0 | 1623 | acquiredJSContext_(false), |
michael@0 | 1624 | nogc_(shared->runtime()) |
michael@0 | 1625 | { |
michael@0 | 1626 | /* |
michael@0 | 1627 | * Unsafely set the zone. This is used to track malloc counters and to |
michael@0 | 1628 | * trigger GCs and is otherwise not thread-safe to access. |
michael@0 | 1629 | */ |
michael@0 | 1630 | zone_ = shared->zone(); |
michael@0 | 1631 | |
michael@0 | 1632 | /* |
michael@0 | 1633 | * Unsafely set the compartment. This is used to get read-only access to |
michael@0 | 1634 | * shared tables. |
michael@0 | 1635 | */ |
michael@0 | 1636 | compartment_ = shared->compartment(); |
michael@0 | 1637 | |
michael@0 | 1638 | allocator_ = allocator; |
michael@0 | 1639 | } |
michael@0 | 1640 | |
michael@0 | 1641 | bool |
michael@0 | 1642 | ForkJoinContext::isMainThread() const |
michael@0 | 1643 | { |
michael@0 | 1644 | return perThreadData == &shared_->runtime()->mainThread; |
michael@0 | 1645 | } |
michael@0 | 1646 | |
michael@0 | 1647 | JSRuntime * |
michael@0 | 1648 | ForkJoinContext::runtime() |
michael@0 | 1649 | { |
michael@0 | 1650 | return shared_->runtime(); |
michael@0 | 1651 | } |
michael@0 | 1652 | |
michael@0 | 1653 | JSContext * |
michael@0 | 1654 | ForkJoinContext::acquireJSContext() |
michael@0 | 1655 | { |
michael@0 | 1656 | JSContext *cx = shared_->acquireJSContext(); |
michael@0 | 1657 | JS_ASSERT(!acquiredJSContext_); |
michael@0 | 1658 | acquiredJSContext_ = true; |
michael@0 | 1659 | return cx; |
michael@0 | 1660 | } |
michael@0 | 1661 | |
michael@0 | 1662 | void |
michael@0 | 1663 | ForkJoinContext::releaseJSContext() |
michael@0 | 1664 | { |
michael@0 | 1665 | JS_ASSERT(acquiredJSContext_); |
michael@0 | 1666 | acquiredJSContext_ = false; |
michael@0 | 1667 | return shared_->releaseJSContext(); |
michael@0 | 1668 | } |
michael@0 | 1669 | |
michael@0 | 1670 | bool |
michael@0 | 1671 | ForkJoinContext::hasAcquiredJSContext() const |
michael@0 | 1672 | { |
michael@0 | 1673 | return acquiredJSContext_; |
michael@0 | 1674 | } |
michael@0 | 1675 | |
michael@0 | 1676 | bool |
michael@0 | 1677 | ForkJoinContext::check() |
michael@0 | 1678 | { |
michael@0 | 1679 | if (runtime()->interruptPar) { |
michael@0 | 1680 | shared_->setAbortFlagDueToInterrupt(*this); |
michael@0 | 1681 | return false; |
michael@0 | 1682 | } |
michael@0 | 1683 | return true; |
michael@0 | 1684 | } |
michael@0 | 1685 | |
michael@0 | 1686 | void |
michael@0 | 1687 | ForkJoinContext::requestGC(JS::gcreason::Reason reason) |
michael@0 | 1688 | { |
michael@0 | 1689 | shared_->requestGC(reason); |
michael@0 | 1690 | bailoutRecord->setCause(ParallelBailoutRequestedGC); |
michael@0 | 1691 | shared_->setAbortFlagAndRequestInterrupt(false); |
michael@0 | 1692 | } |
michael@0 | 1693 | |
michael@0 | 1694 | void |
michael@0 | 1695 | ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) |
michael@0 | 1696 | { |
michael@0 | 1697 | shared_->requestZoneGC(zone, reason); |
michael@0 | 1698 | bailoutRecord->setCause(ParallelBailoutRequestedZoneGC); |
michael@0 | 1699 | shared_->setAbortFlagAndRequestInterrupt(false); |
michael@0 | 1700 | } |
michael@0 | 1701 | |
michael@0 | 1702 | bool |
michael@0 | 1703 | ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause) |
michael@0 | 1704 | { |
michael@0 | 1705 | shared_->setPendingAbortFatal(); |
michael@0 | 1706 | bailoutRecord->setCause(cause); |
michael@0 | 1707 | return false; |
michael@0 | 1708 | } |
michael@0 | 1709 | |
michael@0 | 1710 | ////////////////////////////////////////////////////////////////////////////// |
michael@0 | 1711 | // ParallelBailoutRecord |
michael@0 | 1712 | |
michael@0 | 1713 | void |
michael@0 | 1714 | js::ParallelBailoutRecord::init(JSContext *cx) |
michael@0 | 1715 | { |
michael@0 | 1716 | reset(cx); |
michael@0 | 1717 | } |
michael@0 | 1718 | |
michael@0 | 1719 | void |
michael@0 | 1720 | js::ParallelBailoutRecord::reset(JSContext *cx) |
michael@0 | 1721 | { |
michael@0 | 1722 | topScript = nullptr; |
michael@0 | 1723 | cause = ParallelBailoutNone; |
michael@0 | 1724 | depth = 0; |
michael@0 | 1725 | } |
michael@0 | 1726 | |
michael@0 | 1727 | void |
michael@0 | 1728 | js::ParallelBailoutRecord::setCause(ParallelBailoutCause cause, |
michael@0 | 1729 | JSScript *outermostScript, |
michael@0 | 1730 | JSScript *currentScript, |
michael@0 | 1731 | jsbytecode *currentPc) |
michael@0 | 1732 | { |
michael@0 | 1733 | this->cause = cause; |
michael@0 | 1734 | updateCause(cause, outermostScript, currentScript, currentPc); |
michael@0 | 1735 | } |
michael@0 | 1736 | |
michael@0 | 1737 | void |
michael@0 | 1738 | js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause, |
michael@0 | 1739 | JSScript *outermostScript, |
michael@0 | 1740 | JSScript *currentScript, |
michael@0 | 1741 | jsbytecode *currentPc) |
michael@0 | 1742 | { |
michael@0 | 1743 | JS_ASSERT_IF(outermostScript, currentScript); |
michael@0 | 1744 | JS_ASSERT_IF(outermostScript, outermostScript->hasParallelIonScript()); |
michael@0 | 1745 | JS_ASSERT_IF(currentScript, outermostScript); |
michael@0 | 1746 | JS_ASSERT_IF(!currentScript, !currentPc); |
michael@0 | 1747 | |
michael@0 | 1748 | if (this->cause == ParallelBailoutNone) |
michael@0 | 1749 | this->cause = cause; |
michael@0 | 1750 | |
michael@0 | 1751 | if (outermostScript) |
michael@0 | 1752 | this->topScript = outermostScript; |
michael@0 | 1753 | |
michael@0 | 1754 | if (currentScript) |
michael@0 | 1755 | addTrace(currentScript, currentPc); |
michael@0 | 1756 | } |
michael@0 | 1757 | |
michael@0 | 1758 | void |
michael@0 | 1759 | js::ParallelBailoutRecord::addTrace(JSScript *script, |
michael@0 | 1760 | jsbytecode *pc) |
michael@0 | 1761 | { |
michael@0 | 1762 | // Ideally, this should never occur, because we should always have |
michael@0 | 1763 | // a script when we invoke setCause, but I havent' fully |
michael@0 | 1764 | // refactored things to that point yet: |
michael@0 | 1765 | if (topScript == nullptr && script != nullptr) |
michael@0 | 1766 | topScript = script; |
michael@0 | 1767 | |
michael@0 | 1768 | if (depth < MaxDepth) { |
michael@0 | 1769 | trace[depth].script = script; |
michael@0 | 1770 | trace[depth].bytecode = pc; |
michael@0 | 1771 | depth += 1; |
michael@0 | 1772 | } |
michael@0 | 1773 | } |
michael@0 | 1774 | |
michael@0 | 1775 | ////////////////////////////////////////////////////////////////////////////// |
michael@0 | 1776 | |
michael@0 | 1777 | // |
michael@0 | 1778 | // Debug spew |
michael@0 | 1779 | // |
michael@0 | 1780 | |
michael@0 | 1781 | #ifdef DEBUG |
michael@0 | 1782 | |
michael@0 | 1783 | static const char * |
michael@0 | 1784 | ExecutionStatusToString(ExecutionStatus status) |
michael@0 | 1785 | { |
michael@0 | 1786 | switch (status) { |
michael@0 | 1787 | case ExecutionFatal: |
michael@0 | 1788 | return "fatal"; |
michael@0 | 1789 | case ExecutionSequential: |
michael@0 | 1790 | return "sequential"; |
michael@0 | 1791 | case ExecutionWarmup: |
michael@0 | 1792 | return "warmup"; |
michael@0 | 1793 | case ExecutionParallel: |
michael@0 | 1794 | return "parallel"; |
michael@0 | 1795 | } |
michael@0 | 1796 | return "(unknown status)"; |
michael@0 | 1797 | } |
michael@0 | 1798 | |
michael@0 | 1799 | static const char * |
michael@0 | 1800 | MethodStatusToString(MethodStatus status) |
michael@0 | 1801 | { |
michael@0 | 1802 | switch (status) { |
michael@0 | 1803 | case Method_Error: |
michael@0 | 1804 | return "error"; |
michael@0 | 1805 | case Method_CantCompile: |
michael@0 | 1806 | return "can't compile"; |
michael@0 | 1807 | case Method_Skipped: |
michael@0 | 1808 | return "skipped"; |
michael@0 | 1809 | case Method_Compiled: |
michael@0 | 1810 | return "compiled"; |
michael@0 | 1811 | } |
michael@0 | 1812 | return "(unknown status)"; |
michael@0 | 1813 | } |
michael@0 | 1814 | |
michael@0 | 1815 | static unsigned |
michael@0 | 1816 | NumberOfDigits(unsigned n) |
michael@0 | 1817 | { |
michael@0 | 1818 | if (n == 0) |
michael@0 | 1819 | return 1; |
michael@0 | 1820 | unsigned d = 0; |
michael@0 | 1821 | while (n != 0) { |
michael@0 | 1822 | d++; |
michael@0 | 1823 | n /= 10; |
michael@0 | 1824 | } |
michael@0 | 1825 | return d; |
michael@0 | 1826 | } |
michael@0 | 1827 | |
michael@0 | 1828 | static const size_t BufferSize = 4096; |
michael@0 | 1829 | |
michael@0 | 1830 | class ParallelSpewer |
michael@0 | 1831 | { |
michael@0 | 1832 | uint32_t depth; |
michael@0 | 1833 | bool colorable; |
michael@0 | 1834 | bool active[NumSpewChannels]; |
michael@0 | 1835 | |
michael@0 | 1836 | const char *color(const char *colorCode) { |
michael@0 | 1837 | if (!colorable) |
michael@0 | 1838 | return ""; |
michael@0 | 1839 | return colorCode; |
michael@0 | 1840 | } |
michael@0 | 1841 | |
michael@0 | 1842 | const char *reset() { return color("\x1b[0m"); } |
michael@0 | 1843 | const char *bold() { return color("\x1b[1m"); } |
michael@0 | 1844 | const char *red() { return color("\x1b[31m"); } |
michael@0 | 1845 | const char *green() { return color("\x1b[32m"); } |
michael@0 | 1846 | const char *yellow() { return color("\x1b[33m"); } |
michael@0 | 1847 | const char *cyan() { return color("\x1b[36m"); } |
michael@0 | 1848 | const char *workerColor(uint32_t id) { |
michael@0 | 1849 | static const char *colors[] = { |
michael@0 | 1850 | "\x1b[7m\x1b[31m", "\x1b[7m\x1b[32m", "\x1b[7m\x1b[33m", |
michael@0 | 1851 | "\x1b[7m\x1b[34m", "\x1b[7m\x1b[35m", "\x1b[7m\x1b[36m", |
michael@0 | 1852 | "\x1b[7m\x1b[37m", |
michael@0 | 1853 | "\x1b[31m", "\x1b[32m", "\x1b[33m", |
michael@0 | 1854 | "\x1b[34m", "\x1b[35m", "\x1b[36m", |
michael@0 | 1855 | "\x1b[37m" |
michael@0 | 1856 | }; |
michael@0 | 1857 | return color(colors[id % 14]); |
michael@0 | 1858 | } |
michael@0 | 1859 | |
michael@0 | 1860 | public: |
michael@0 | 1861 | ParallelSpewer() |
michael@0 | 1862 | : depth(0) |
michael@0 | 1863 | { |
michael@0 | 1864 | const char *env; |
michael@0 | 1865 | |
michael@0 | 1866 | mozilla::PodArrayZero(active); |
michael@0 | 1867 | env = getenv("PAFLAGS"); |
michael@0 | 1868 | if (env) { |
michael@0 | 1869 | if (strstr(env, "ops")) |
michael@0 | 1870 | active[SpewOps] = true; |
michael@0 | 1871 | if (strstr(env, "compile")) |
michael@0 | 1872 | active[SpewCompile] = true; |
michael@0 | 1873 | if (strstr(env, "bailouts")) |
michael@0 | 1874 | active[SpewBailouts] = true; |
michael@0 | 1875 | if (strstr(env, "full")) { |
michael@0 | 1876 | for (uint32_t i = 0; i < NumSpewChannels; i++) |
michael@0 | 1877 | active[i] = true; |
michael@0 | 1878 | } |
michael@0 | 1879 | } |
michael@0 | 1880 | |
michael@0 | 1881 | env = getenv("TERM"); |
michael@0 | 1882 | if (env && isatty(fileno(stderr))) { |
michael@0 | 1883 | if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) |
michael@0 | 1884 | colorable = true; |
michael@0 | 1885 | } |
michael@0 | 1886 | } |
michael@0 | 1887 | |
michael@0 | 1888 | bool isActive(js::parallel::SpewChannel channel) { |
michael@0 | 1889 | return active[channel]; |
michael@0 | 1890 | } |
michael@0 | 1891 | |
michael@0 | 1892 | void spewVA(js::parallel::SpewChannel channel, const char *fmt, va_list ap) { |
michael@0 | 1893 | if (!active[channel]) |
michael@0 | 1894 | return; |
michael@0 | 1895 | |
michael@0 | 1896 | // Print into a buffer first so we use one fprintf, which usually |
michael@0 | 1897 | // doesn't get interrupted when running with multiple threads. |
michael@0 | 1898 | char buf[BufferSize]; |
michael@0 | 1899 | |
michael@0 | 1900 | if (ForkJoinContext *cx = ForkJoinContext::current()) { |
michael@0 | 1901 | // Print the format first into a buffer to right-justify the |
michael@0 | 1902 | // worker ids. |
michael@0 | 1903 | char bufbuf[BufferSize]; |
michael@0 | 1904 | JS_snprintf(bufbuf, BufferSize, "[%%sParallel:%%0%du%%s] ", |
michael@0 | 1905 | NumberOfDigits(cx->maxWorkerId)); |
michael@0 | 1906 | JS_snprintf(buf, BufferSize, bufbuf, workerColor(cx->workerId()), |
michael@0 | 1907 | cx->workerId(), reset()); |
michael@0 | 1908 | } else { |
michael@0 | 1909 | JS_snprintf(buf, BufferSize, "[Parallel:M] "); |
michael@0 | 1910 | } |
michael@0 | 1911 | |
michael@0 | 1912 | for (uint32_t i = 0; i < depth; i++) |
michael@0 | 1913 | JS_snprintf(buf + strlen(buf), BufferSize, " "); |
michael@0 | 1914 | |
michael@0 | 1915 | JS_vsnprintf(buf + strlen(buf), BufferSize, fmt, ap); |
michael@0 | 1916 | JS_snprintf(buf + strlen(buf), BufferSize, "\n"); |
michael@0 | 1917 | |
michael@0 | 1918 | fprintf(stderr, "%s", buf); |
michael@0 | 1919 | } |
michael@0 | 1920 | |
michael@0 | 1921 | void spew(js::parallel::SpewChannel channel, const char *fmt, ...) { |
michael@0 | 1922 | va_list ap; |
michael@0 | 1923 | va_start(ap, fmt); |
michael@0 | 1924 | spewVA(channel, fmt, ap); |
michael@0 | 1925 | va_end(ap); |
michael@0 | 1926 | } |
michael@0 | 1927 | |
michael@0 | 1928 | void beginOp(JSContext *cx, const char *name) { |
michael@0 | 1929 | if (!active[SpewOps]) |
michael@0 | 1930 | return; |
michael@0 | 1931 | |
michael@0 | 1932 | if (cx) { |
michael@0 | 1933 | jsbytecode *pc; |
michael@0 | 1934 | RootedScript script(cx, cx->currentScript(&pc)); |
michael@0 | 1935 | if (script && pc) { |
michael@0 | 1936 | NonBuiltinScriptFrameIter iter(cx); |
michael@0 | 1937 | if (iter.done()) { |
michael@0 | 1938 | spew(SpewOps, "%sBEGIN %s%s (%s:%u)", bold(), name, reset(), |
michael@0 | 1939 | script->filename(), PCToLineNumber(script, pc)); |
michael@0 | 1940 | } else { |
michael@0 | 1941 | spew(SpewOps, "%sBEGIN %s%s (%s:%u -> %s:%u)", bold(), name, reset(), |
michael@0 | 1942 | iter.script()->filename(), PCToLineNumber(iter.script(), iter.pc()), |
michael@0 | 1943 | script->filename(), PCToLineNumber(script, pc)); |
michael@0 | 1944 | } |
michael@0 | 1945 | } else { |
michael@0 | 1946 | spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset()); |
michael@0 | 1947 | } |
michael@0 | 1948 | } else { |
michael@0 | 1949 | spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset()); |
michael@0 | 1950 | } |
michael@0 | 1951 | |
michael@0 | 1952 | depth++; |
michael@0 | 1953 | } |
michael@0 | 1954 | |
michael@0 | 1955 | void endOp(ExecutionStatus status) { |
michael@0 | 1956 | if (!active[SpewOps]) |
michael@0 | 1957 | return; |
michael@0 | 1958 | |
michael@0 | 1959 | JS_ASSERT(depth > 0); |
michael@0 | 1960 | depth--; |
michael@0 | 1961 | |
michael@0 | 1962 | const char *statusColor; |
michael@0 | 1963 | switch (status) { |
michael@0 | 1964 | case ExecutionFatal: |
michael@0 | 1965 | statusColor = red(); |
michael@0 | 1966 | break; |
michael@0 | 1967 | case ExecutionSequential: |
michael@0 | 1968 | statusColor = yellow(); |
michael@0 | 1969 | break; |
michael@0 | 1970 | case ExecutionParallel: |
michael@0 | 1971 | statusColor = green(); |
michael@0 | 1972 | break; |
michael@0 | 1973 | default: |
michael@0 | 1974 | statusColor = reset(); |
michael@0 | 1975 | break; |
michael@0 | 1976 | } |
michael@0 | 1977 | |
michael@0 | 1978 | spew(SpewOps, "%sEND %s%s%s", bold(), |
michael@0 | 1979 | statusColor, ExecutionStatusToString(status), reset()); |
michael@0 | 1980 | } |
michael@0 | 1981 | |
michael@0 | 1982 | void bailout(uint32_t count, HandleScript script, |
michael@0 | 1983 | jsbytecode *pc, ParallelBailoutCause cause) { |
michael@0 | 1984 | if (!active[SpewOps]) |
michael@0 | 1985 | return; |
michael@0 | 1986 | |
michael@0 | 1987 | const char *filename = ""; |
michael@0 | 1988 | unsigned line=0, column=0; |
michael@0 | 1989 | if (script) { |
michael@0 | 1990 | line = PCToLineNumber(script, pc, &column); |
michael@0 | 1991 | filename = script->filename(); |
michael@0 | 1992 | } |
michael@0 | 1993 | |
michael@0 | 1994 | spew(SpewOps, "%s%sBAILOUT %d%s: %s (%d) at %s:%d:%d", bold(), yellow(), count, reset(), |
michael@0 | 1995 | BailoutExplanation(cause), cause, filename, line, column); |
michael@0 | 1996 | } |
michael@0 | 1997 | |
michael@0 | 1998 | void beginCompile(HandleScript script) { |
michael@0 | 1999 | if (!active[SpewCompile]) |
michael@0 | 2000 | return; |
michael@0 | 2001 | |
michael@0 | 2002 | spew(SpewCompile, "COMPILE %p:%s:%u", script.get(), script->filename(), script->lineno()); |
michael@0 | 2003 | depth++; |
michael@0 | 2004 | } |
michael@0 | 2005 | |
michael@0 | 2006 | void endCompile(MethodStatus status) { |
michael@0 | 2007 | if (!active[SpewCompile]) |
michael@0 | 2008 | return; |
michael@0 | 2009 | |
michael@0 | 2010 | JS_ASSERT(depth > 0); |
michael@0 | 2011 | depth--; |
michael@0 | 2012 | |
michael@0 | 2013 | const char *statusColor; |
michael@0 | 2014 | switch (status) { |
michael@0 | 2015 | case Method_Error: |
michael@0 | 2016 | case Method_CantCompile: |
michael@0 | 2017 | statusColor = red(); |
michael@0 | 2018 | break; |
michael@0 | 2019 | case Method_Skipped: |
michael@0 | 2020 | statusColor = yellow(); |
michael@0 | 2021 | break; |
michael@0 | 2022 | case Method_Compiled: |
michael@0 | 2023 | statusColor = green(); |
michael@0 | 2024 | break; |
michael@0 | 2025 | default: |
michael@0 | 2026 | statusColor = reset(); |
michael@0 | 2027 | break; |
michael@0 | 2028 | } |
michael@0 | 2029 | |
michael@0 | 2030 | spew(SpewCompile, "END %s%s%s", statusColor, MethodStatusToString(status), reset()); |
michael@0 | 2031 | } |
michael@0 | 2032 | |
michael@0 | 2033 | void spewMIR(MDefinition *mir, const char *fmt, va_list ap) { |
michael@0 | 2034 | if (!active[SpewCompile]) |
michael@0 | 2035 | return; |
michael@0 | 2036 | |
michael@0 | 2037 | char buf[BufferSize]; |
michael@0 | 2038 | JS_vsnprintf(buf, BufferSize, fmt, ap); |
michael@0 | 2039 | |
michael@0 | 2040 | JSScript *script = mir->block()->info().script(); |
michael@0 | 2041 | spew(SpewCompile, "%s%s%s: %s (%s:%u)", cyan(), mir->opName(), reset(), buf, |
michael@0 | 2042 | script->filename(), PCToLineNumber(script, mir->trackedPc())); |
michael@0 | 2043 | } |
michael@0 | 2044 | |
michael@0 | 2045 | void spewBailoutIR(IonLIRTraceData *data) { |
michael@0 | 2046 | if (!active[SpewBailouts]) |
michael@0 | 2047 | return; |
michael@0 | 2048 | |
michael@0 | 2049 | // If we didn't bail from a LIR/MIR but from a propagated parallel |
michael@0 | 2050 | // bailout, don't bother printing anything since we've printed it |
michael@0 | 2051 | // elsewhere. |
michael@0 | 2052 | if (data->mirOpName && data->script) { |
michael@0 | 2053 | spew(SpewBailouts, "%sBailout%s: %s / %s%s%s (block %d lir %d) (%s:%u)", yellow(), reset(), |
michael@0 | 2054 | data->lirOpName, cyan(), data->mirOpName, reset(), |
michael@0 | 2055 | data->blockIndex, data->lirIndex, data->script->filename(), |
michael@0 | 2056 | PCToLineNumber(data->script, data->pc)); |
michael@0 | 2057 | } |
michael@0 | 2058 | } |
michael@0 | 2059 | }; |
michael@0 | 2060 | |
michael@0 | 2061 | // Singleton instance of the spewer. |
michael@0 | 2062 | static ParallelSpewer spewer; |
michael@0 | 2063 | |
michael@0 | 2064 | bool |
michael@0 | 2065 | parallel::SpewEnabled(SpewChannel channel) |
michael@0 | 2066 | { |
michael@0 | 2067 | return spewer.isActive(channel); |
michael@0 | 2068 | } |
michael@0 | 2069 | |
michael@0 | 2070 | void |
michael@0 | 2071 | parallel::Spew(SpewChannel channel, const char *fmt, ...) |
michael@0 | 2072 | { |
michael@0 | 2073 | va_list ap; |
michael@0 | 2074 | va_start(ap, fmt); |
michael@0 | 2075 | spewer.spewVA(channel, fmt, ap); |
michael@0 | 2076 | va_end(ap); |
michael@0 | 2077 | } |
michael@0 | 2078 | |
michael@0 | 2079 | void |
michael@0 | 2080 | parallel::SpewBeginOp(JSContext *cx, const char *name) |
michael@0 | 2081 | { |
michael@0 | 2082 | spewer.beginOp(cx, name); |
michael@0 | 2083 | } |
michael@0 | 2084 | |
michael@0 | 2085 | ExecutionStatus |
michael@0 | 2086 | parallel::SpewEndOp(ExecutionStatus status) |
michael@0 | 2087 | { |
michael@0 | 2088 | spewer.endOp(status); |
michael@0 | 2089 | return status; |
michael@0 | 2090 | } |
michael@0 | 2091 | |
michael@0 | 2092 | void |
michael@0 | 2093 | parallel::SpewBailout(uint32_t count, HandleScript script, |
michael@0 | 2094 | jsbytecode *pc, ParallelBailoutCause cause) |
michael@0 | 2095 | { |
michael@0 | 2096 | spewer.bailout(count, script, pc, cause); |
michael@0 | 2097 | } |
michael@0 | 2098 | |
michael@0 | 2099 | void |
michael@0 | 2100 | parallel::SpewBeginCompile(HandleScript script) |
michael@0 | 2101 | { |
michael@0 | 2102 | spewer.beginCompile(script); |
michael@0 | 2103 | } |
michael@0 | 2104 | |
michael@0 | 2105 | MethodStatus |
michael@0 | 2106 | parallel::SpewEndCompile(MethodStatus status) |
michael@0 | 2107 | { |
michael@0 | 2108 | spewer.endCompile(status); |
michael@0 | 2109 | return status; |
michael@0 | 2110 | } |
michael@0 | 2111 | |
michael@0 | 2112 | void |
michael@0 | 2113 | parallel::SpewMIR(MDefinition *mir, const char *fmt, ...) |
michael@0 | 2114 | { |
michael@0 | 2115 | va_list ap; |
michael@0 | 2116 | va_start(ap, fmt); |
michael@0 | 2117 | spewer.spewMIR(mir, fmt, ap); |
michael@0 | 2118 | va_end(ap); |
michael@0 | 2119 | } |
michael@0 | 2120 | |
michael@0 | 2121 | void |
michael@0 | 2122 | parallel::SpewBailoutIR(IonLIRTraceData *data) |
michael@0 | 2123 | { |
michael@0 | 2124 | spewer.spewBailoutIR(data); |
michael@0 | 2125 | } |
michael@0 | 2126 | |
michael@0 | 2127 | #endif // DEBUG |
michael@0 | 2128 | |
michael@0 | 2129 | bool |
michael@0 | 2130 | js::InExclusiveParallelSection() |
michael@0 | 2131 | { |
michael@0 | 2132 | return InParallelSection() && ForkJoinContext::current()->hasAcquiredJSContext(); |
michael@0 | 2133 | } |
michael@0 | 2134 | |
michael@0 | 2135 | bool |
michael@0 | 2136 | js::ParallelTestsShouldPass(JSContext *cx) |
michael@0 | 2137 | { |
michael@0 | 2138 | return jit::IsIonEnabled(cx) && |
michael@0 | 2139 | jit::IsBaselineEnabled(cx) && |
michael@0 | 2140 | !jit::js_JitOptions.eagerCompilation && |
michael@0 | 2141 | jit::js_JitOptions.baselineUsesBeforeCompile != 0 && |
michael@0 | 2142 | cx->runtime()->gcZeal() == 0; |
michael@0 | 2143 | } |
michael@0 | 2144 | |
michael@0 | 2145 | void |
michael@0 | 2146 | js::RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode) |
michael@0 | 2147 | { |
michael@0 | 2148 | if (mode != JSRuntime::RequestInterruptAnyThreadDontStopIon) |
michael@0 | 2149 | rt->interruptPar = true; |
michael@0 | 2150 | } |
michael@0 | 2151 | |
michael@0 | 2152 | bool |
michael@0 | 2153 | js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 2154 | { |
michael@0 | 2155 | // This version of SetForkJoinTargetRegion is called during |
michael@0 | 2156 | // sequential execution. It is a no-op. The parallel version |
michael@0 | 2157 | // is intrinsic_SetForkJoinTargetRegionPar(), below. |
michael@0 | 2158 | return true; |
michael@0 | 2159 | } |
michael@0 | 2160 | |
michael@0 | 2161 | static bool |
michael@0 | 2162 | intrinsic_SetForkJoinTargetRegionPar(ForkJoinContext *cx, unsigned argc, Value *vp) |
michael@0 | 2163 | { |
michael@0 | 2164 | // Sets the *target region*, which is the portion of the output |
michael@0 | 2165 | // buffer that the current iteration is permitted to write to. |
michael@0 | 2166 | // |
michael@0 | 2167 | // Note: it is important that the target region should be an |
michael@0 | 2168 | // entire element (or several elements) of the output array and |
michael@0 | 2169 | // not some region that spans from the middle of one element into |
michael@0 | 2170 | // the middle of another. This is because the guarding code |
michael@0 | 2171 | // assumes that handles, which never straddle across elements, |
michael@0 | 2172 | // will either be contained entirely within the target region or |
michael@0 | 2173 | // be contained entirely without of the region, and not straddling |
michael@0 | 2174 | // the region nor encompassing it. |
michael@0 | 2175 | |
michael@0 | 2176 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 2177 | JS_ASSERT(argc == 3); |
michael@0 | 2178 | JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); |
michael@0 | 2179 | JS_ASSERT(args[1].isInt32()); |
michael@0 | 2180 | JS_ASSERT(args[2].isInt32()); |
michael@0 | 2181 | |
michael@0 | 2182 | uint8_t *mem = args[0].toObject().as<TypedObject>().typedMem(); |
michael@0 | 2183 | int32_t start = args[1].toInt32(); |
michael@0 | 2184 | int32_t end = args[2].toInt32(); |
michael@0 | 2185 | |
michael@0 | 2186 | cx->targetRegionStart = mem + start; |
michael@0 | 2187 | cx->targetRegionEnd = mem + end; |
michael@0 | 2188 | return true; |
michael@0 | 2189 | } |
michael@0 | 2190 | |
michael@0 | 2191 | JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_SetForkJoinTargetRegionInfo, |
michael@0 | 2192 | intrinsic_SetForkJoinTargetRegionPar); |
michael@0 | 2193 | |
michael@0 | 2194 | bool |
michael@0 | 2195 | js::intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 2196 | { |
michael@0 | 2197 | return true; |
michael@0 | 2198 | } |
michael@0 | 2199 | |
michael@0 | 2200 | static bool |
michael@0 | 2201 | intrinsic_ClearThreadLocalArenasPar(ForkJoinContext *cx, unsigned argc, Value *vp) |
michael@0 | 2202 | { |
michael@0 | 2203 | cx->allocator()->arenas.wipeDuringParallelExecution(cx->runtime()); |
michael@0 | 2204 | return true; |
michael@0 | 2205 | } |
michael@0 | 2206 | |
michael@0 | 2207 | JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_ClearThreadLocalArenasInfo, |
michael@0 | 2208 | intrinsic_ClearThreadLocalArenasPar); |
michael@0 | 2209 | |
michael@0 | 2210 | #endif // JS_THREADSAFE && JS_ION |