blob: ba1513bbf1f2cc2e3e1f20937b1e5d423e3577d8 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/ParallelFunctions.h"
#include "jit/IonSpewer.h"
#include "vm/Interpreter.h"
#include "jscompartmentinlines.h"
#include "jsstrinlines.h"
#include "vm/Interpreter-inl.h"
using namespace js;
using namespace jit;
using parallel::Spew;
using parallel::SpewOps;
using parallel::SpewBailouts;
using parallel::SpewBailoutIR;
// Load the current thread context.
ForkJoinSlice *
jit::ParForkJoinSlice()
{
return ForkJoinSlice::Current();
}
// ParNewGCThing() is called in place of NewGCThing() when executing
// parallel code. It uses the ArenaLists for the current thread and
// allocates from there.
JSObject *
jit::ParNewGCThing(gc::AllocKind allocKind)
{
ForkJoinSlice *slice = ForkJoinSlice::Current();
uint32_t thingSize = (uint32_t)gc::Arena::thingSize(allocKind);
return gc::NewGCThing<JSObject, NoGC>(slice, allocKind, thingSize, gc::DefaultHeap);
}
// Check that the object was created by the current thread
// (and hence is writable).
bool
jit::ParWriteGuard(ForkJoinSlice *slice, JSObject *object)
{
JS_ASSERT(ForkJoinSlice::Current() == slice);
return !IsInsideNursery(slice->runtime(), object) &&
slice->allocator()->arenas.containsArena(slice->runtime(), object->arenaHeader());
}
#ifdef DEBUG
static void
printTrace(const char *prefix, struct IonLIRTraceData *cached)
{
fprintf(stderr, "%s / Block %3u / LIR %3u / Mode %u / LIR %s\n",
prefix,
cached->bblock, cached->lir, cached->execModeInt, cached->lirOpName);
}
struct IonLIRTraceData seqTraceData;
#endif
void
jit::TraceLIR(uint32_t bblock, uint32_t lir, uint32_t execModeInt,
const char *lirOpName, const char *mirOpName,
JSScript *script, jsbytecode *pc)
{
#ifdef DEBUG
static enum { NotSet, All, Bailouts } traceMode;
// If you set IONFLAGS=trace, this function will be invoked before every LIR.
//
// You can either modify it to do whatever you like, or use gdb scripting.
// For example:
//
// break ParTrace
// commands
// continue
// exit
if (traceMode == NotSet) {
// Racy, but that's ok.
const char *env = getenv("IONFLAGS");
if (strstr(env, "trace-all"))
traceMode = All;
else
traceMode = Bailouts;
}
IonLIRTraceData *cached;
if (execModeInt == 0)
cached = &seqTraceData;
else
cached = &ForkJoinSlice::Current()->traceData;
if (bblock == 0xDEADBEEF) {
if (execModeInt == 0)
printTrace("BAILOUT", cached);
else
SpewBailoutIR(cached->bblock, cached->lir,
cached->lirOpName, cached->mirOpName,
cached->script, cached->pc);
}
cached->bblock = bblock;
cached->lir = lir;
cached->execModeInt = execModeInt;
cached->lirOpName = lirOpName;
cached->mirOpName = mirOpName;
cached->script = script;
cached->pc = pc;
if (traceMode == All)
printTrace("Exec", cached);
#endif
}
bool
jit::ParCheckOverRecursed(ForkJoinSlice *slice)
{
JS_ASSERT(ForkJoinSlice::Current() == slice);
int stackDummy_;
// When an interrupt is triggered, the main thread stack limit is
// overwritten with a sentinel value that brings us here.
// Therefore, we must check whether this is really a stack overrun
// and, if not, check whether an interrupt is needed.
//
// When not on the main thread, we don't overwrite the stack
// limit, but we do still call into this routine if the interrupt
// flag is set, so we still need to double check.
uintptr_t realStackLimit;
if (slice->isMainThread())
realStackLimit = js::GetNativeStackLimit(slice->runtime());
else
realStackLimit = slice->perThreadData->ionStackLimit;
if (!JS_CHECK_STACK_SIZE(realStackLimit, &stackDummy_)) {
slice->bailoutRecord->setCause(ParallelBailoutOverRecursed,
NULL, NULL, NULL);
return false;
}
return ParCheckInterrupt(slice);
}
bool
jit::ParCheckInterrupt(ForkJoinSlice *slice)
{
JS_ASSERT(ForkJoinSlice::Current() == slice);
bool result = slice->check();
if (!result) {
// Do not set the cause here. Either it was set by this
// thread already by some code that then triggered an abort,
// or else we are just picking up an abort from some other
// thread. Either way we have nothing useful to contribute so
// we might as well leave our bailout case unset.
return false;
}
return true;
}
void
jit::ParDumpValue(Value *v)
{
#ifdef DEBUG
js_DumpValue(*v);
#endif
}
JSObject*
jit::ParPush(ParPushArgs *args)
{
// It is awkward to have the MIR pass the current slice in, so
// just fetch it from TLS. Extending the array is kind of the
// slow path anyhow as it reallocates the elements vector.
ForkJoinSlice *slice = js::ForkJoinSlice::Current();
JSObject::EnsureDenseResult res =
args->object->parExtendDenseElements(slice, &args->value, 1);
if (res != JSObject::ED_OK)
return NULL;
return args->object;
}
JSObject *
jit::ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length)
{
JSObject::EnsureDenseResult res =
array->parExtendDenseElements(slice, NULL, length);
if (res != JSObject::ED_OK)
return NULL;
return array;
}
#define PAR_RELATIONAL_OP(OP, EXPECTED) \
do { \
/* Optimize for two int-tagged operands (typical loop control). */ \
if (lhs.isInt32() && rhs.isInt32()) { \
*res = (lhs.toInt32() OP rhs.toInt32()) == EXPECTED; \
} else if (lhs.isNumber() && rhs.isNumber()) { \
double l = lhs.toNumber(), r = rhs.toNumber(); \
*res = (l OP r) == EXPECTED; \
} else if (lhs.isBoolean() && rhs.isBoolean()) { \
bool l = lhs.toBoolean(); \
bool r = rhs.toBoolean(); \
*res = (l OP r) == EXPECTED; \
} else if (lhs.isBoolean() && rhs.isNumber()) { \
bool l = lhs.toBoolean(); \
double r = rhs.toNumber(); \
*res = (l OP r) == EXPECTED; \
} else if (lhs.isNumber() && rhs.isBoolean()) { \
double l = lhs.toNumber(); \
bool r = rhs.toBoolean(); \
*res = (l OP r) == EXPECTED; \
} else { \
int32_t vsZero; \
ParallelResult ret = ParCompareMaybeStrings(slice, lhs, rhs, &vsZero); \
if (ret != TP_SUCCESS) \
return ret; \
*res = (vsZero OP 0) == EXPECTED; \
} \
return TP_SUCCESS; \
} while(0)
static ParallelResult
ParCompareStrings(ForkJoinSlice *slice, HandleString str1,
HandleString str2, int32_t *res)
{
if (!str1->isLinear())
return TP_RETRY_SEQUENTIALLY;
if (!str2->isLinear())
return TP_RETRY_SEQUENTIALLY;
JSLinearString &linearStr1 = str1->asLinear();
JSLinearString &linearStr2 = str2->asLinear();
if (!CompareChars(linearStr1.chars(), linearStr1.length(),
linearStr2.chars(), linearStr2.length(),
res))
return TP_FATAL;
return TP_SUCCESS;
}
static ParallelResult
ParCompareMaybeStrings(ForkJoinSlice *slice,
HandleValue v1,
HandleValue v2,
int32_t *res)
{
if (!v1.isString())
return TP_RETRY_SEQUENTIALLY;
if (!v2.isString())
return TP_RETRY_SEQUENTIALLY;
RootedString str1(slice->perThreadData, v1.toString());
RootedString str2(slice->perThreadData, v2.toString());
return ParCompareStrings(slice, str1, str2, res);
}
template<bool Equal>
ParallelResult
ParLooselyEqualImpl(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
PAR_RELATIONAL_OP(==, Equal);
}
ParallelResult
js::jit::ParLooselyEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
return ParLooselyEqualImpl<true>(slice, lhs, rhs, res);
}
ParallelResult
js::jit::ParLooselyUnequal(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
return ParLooselyEqualImpl<false>(slice, lhs, rhs, res);
}
template<bool Equal>
ParallelResult
ParStrictlyEqualImpl(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
if (lhs.isNumber()) {
if (rhs.isNumber()) {
*res = (lhs.toNumber() == rhs.toNumber()) == Equal;
return TP_SUCCESS;
}
} else if (lhs.isBoolean()) {
if (rhs.isBoolean()) {
*res = (lhs.toBoolean() == rhs.toBoolean()) == Equal;
return TP_SUCCESS;
}
} else if (lhs.isNull()) {
if (rhs.isNull()) {
*res = Equal;
return TP_SUCCESS;
}
} else if (lhs.isUndefined()) {
if (rhs.isUndefined()) {
*res = Equal;
return TP_SUCCESS;
}
} else if (lhs.isObject()) {
if (rhs.isObject()) {
*res = (lhs.toObjectOrNull() == rhs.toObjectOrNull()) == Equal;
return TP_SUCCESS;
}
} else if (lhs.isString()) {
if (rhs.isString())
return ParLooselyEqualImpl<Equal>(slice, lhs, rhs, res);
}
*res = false;
return TP_SUCCESS;
}
ParallelResult
js::jit::ParStrictlyEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
return ParStrictlyEqualImpl<true>(slice, lhs, rhs, res);
}
ParallelResult
js::jit::ParStrictlyUnequal(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
return ParStrictlyEqualImpl<false>(slice, lhs, rhs, res);
}
ParallelResult
js::jit::ParLessThan(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
PAR_RELATIONAL_OP(<, true);
}
ParallelResult
js::jit::ParLessThanOrEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
PAR_RELATIONAL_OP(<=, true);
}
ParallelResult
js::jit::ParGreaterThan(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
PAR_RELATIONAL_OP(>, true);
}
ParallelResult
js::jit::ParGreaterThanOrEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
{
PAR_RELATIONAL_OP(>=, true);
}
template<bool Equal>
ParallelResult
ParStringsEqualImpl(ForkJoinSlice *slice, HandleString lhs, HandleString rhs, JSBool *res)
{
int32_t vsZero;
ParallelResult ret = ParCompareStrings(slice, lhs, rhs, &vsZero);
if (ret != TP_SUCCESS)
return ret;
*res = (vsZero == 0) == Equal;
return TP_SUCCESS;
}
ParallelResult
js::jit::ParStringsEqual(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *res)
{
return ParStringsEqualImpl<true>(slice, v1, v2, res);
}
ParallelResult
js::jit::ParStringsUnequal(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *res)
{
return ParStringsEqualImpl<false>(slice, v1, v2, res);
}
void
jit::ParallelAbort(ParallelBailoutCause cause,
JSScript *outermostScript,
JSScript *currentScript,
jsbytecode *bytecode)
{
// Spew before asserts to help with diagnosing failures.
Spew(SpewBailouts,
"Parallel abort with cause %d in %p:%s:%d "
"(%p:%s:%d at line %d)",
cause,
outermostScript, outermostScript->filename(), outermostScript->lineno,
currentScript, currentScript->filename(), currentScript->lineno,
(currentScript ? PCToLineNumber(currentScript, bytecode) : 0));
JS_ASSERT(InParallelSection());
JS_ASSERT(outermostScript != NULL);
JS_ASSERT(currentScript != NULL);
JS_ASSERT(outermostScript->hasParallelIonScript());
ForkJoinSlice *slice = ForkJoinSlice::Current();
JS_ASSERT(slice->bailoutRecord->depth == 0);
slice->bailoutRecord->setCause(cause, outermostScript,
currentScript, bytecode);
}
void
jit::PropagateParallelAbort(JSScript *outermostScript,
JSScript *currentScript)
{
Spew(SpewBailouts,
"Propagate parallel abort via %p:%s:%d (%p:%s:%d)",
outermostScript, outermostScript->filename(), outermostScript->lineno,
currentScript, currentScript->filename(), currentScript->lineno);
JS_ASSERT(InParallelSection());
JS_ASSERT(outermostScript->hasParallelIonScript());
outermostScript->parallelIonScript()->setHasUncompiledCallTarget();
ForkJoinSlice *slice = ForkJoinSlice::Current();
if (currentScript)
slice->bailoutRecord->addTrace(currentScript, NULL);
}
void
jit::ParCallToUncompiledScript(JSFunction *func)
{
JS_ASSERT(InParallelSection());
#ifdef DEBUG
static const int max_bound_function_unrolling = 5;
if (func->hasScript()) {
JSScript *script = func->nonLazyScript();
Spew(SpewBailouts, "Call to uncompiled script: %p:%s:%d",
script, script->filename(), script->lineno);
} else if (func->isInterpretedLazy()) {
Spew(SpewBailouts, "Call to uncompiled lazy script");
} else if (func->isBoundFunction()) {
int depth = 0;
JSFunction *target = &func->getBoundFunctionTarget()->as<JSFunction>();
while (depth < max_bound_function_unrolling) {
if (target->hasScript())
break;
if (target->isBoundFunction())
target = &target->getBoundFunctionTarget()->as<JSFunction>();
depth--;
}
if (target->hasScript()) {
JSScript *script = target->nonLazyScript();
Spew(SpewBailouts, "Call to bound function leading (depth: %d) to script: %p:%s:%d",
depth, script, script->filename(), script->lineno);
} else {
Spew(SpewBailouts, "Call to bound function (excessive depth: %d)", depth);
}
} else {
JS_NOT_REACHED("ParCall'ed functions must have scripts or be ES6 bound functions.");
}
#endif
}
ParallelResult
jit::InitRestParameter(ForkJoinSlice *slice, uint32_t length, Value *rest,
HandleObject templateObj, HandleObject res,
MutableHandleObject out)
{
// In parallel execution, we should always have succeeded in allocation
// before this point. We can do the allocation here like in the sequential
// path, but duplicating the initGCThing logic is too tedious.
JS_ASSERT(res);
JS_ASSERT(res->isArray());
JS_ASSERT(!res->getDenseInitializedLength());
JS_ASSERT(res->type() == templateObj->type());
// See note in visitRest in ParallelArrayAnalysis.
JS_ASSERT(res->type()->unknownProperties());
if (length) {
JSObject::EnsureDenseResult edr = res->parExtendDenseElements(slice, rest, length);
if (edr != JSObject::ED_OK)
return TP_FATAL;
}
out.set(res);
return TP_SUCCESS;
}