blob: ab57be30dfb6e9a98f36513b64597ab9d099b598 [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 "jsmath.h"
#include "jscntxt.h"
#include "jstypedarrayinlines.h"
#include "AsmJS.h"
#include "AsmJSModule.h"
#include "frontend/BytecodeCompiler.h"
#include "Ion.h"
#ifdef MOZ_VTUNE
# include "jitprofiling.h"
#endif
using namespace js;
using namespace js::jit;
using namespace mozilla;
static bool
LinkFail(JSContext *cx, const char *str)
{
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, js_GetErrorMessage,
NULL, JSMSG_USE_ASM_LINK_FAIL, str);
return false;
}
static bool
ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global,
HandleValue importVal)
{
JS_ASSERT(global.which() == AsmJSModule::Global::Variable);
void *datum = module.globalVarIndexToGlobalDatum(global.varIndex());
switch (global.varInitKind()) {
case AsmJSModule::Global::InitConstant: {
const Value &v = global.varInitConstant();
if (v.isInt32())
*(int32_t *)datum = v.toInt32();
else
*(double *)datum = v.toDouble();
break;
}
case AsmJSModule::Global::InitImport: {
RootedPropertyName field(cx, global.varImportField());
RootedValue v(cx);
if (!GetProperty(cx, importVal, field, &v))
return false;
switch (global.varImportCoercion()) {
case AsmJS_ToInt32:
if (!ToInt32(cx, v, (int32_t *)datum))
return false;
break;
case AsmJS_ToNumber:
if (!ToNumber(cx, v, (double *)datum))
return false;
break;
}
break;
}
}
return true;
}
static bool
ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal,
AutoObjectVector *ffis)
{
RootedPropertyName field(cx, global.ffiField());
RootedValue v(cx);
if (!GetProperty(cx, importVal, field, &v))
return false;
if (!v.isObject() || !v.toObject().is<JSFunction>())
return LinkFail(cx, "FFI imports must be functions");
(*ffis)[global.ffiIndex()] = &v.toObject().as<JSFunction>();
return true;
}
static bool
ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal,
HandleValue bufferVal)
{
RootedPropertyName field(cx, global.viewName());
RootedValue v(cx);
if (!GetProperty(cx, globalVal, field, &v))
return false;
if (!IsTypedArrayConstructor(v, global.viewType()))
return LinkFail(cx, "bad typed array constructor");
return true;
}
static bool
ValidateMathBuiltin(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal)
{
RootedValue v(cx);
if (!GetProperty(cx, globalVal, cx->names().Math, &v))
return false;
RootedPropertyName field(cx, global.mathName());
if (!GetProperty(cx, v, field, &v))
return false;
Native native = NULL;
switch (global.mathBuiltin()) {
case AsmJSMathBuiltin_sin: native = math_sin; break;
case AsmJSMathBuiltin_cos: native = math_cos; break;
case AsmJSMathBuiltin_tan: native = math_tan; break;
case AsmJSMathBuiltin_asin: native = math_asin; break;
case AsmJSMathBuiltin_acos: native = math_acos; break;
case AsmJSMathBuiltin_atan: native = math_atan; break;
case AsmJSMathBuiltin_ceil: native = js_math_ceil; break;
case AsmJSMathBuiltin_floor: native = js_math_floor; break;
case AsmJSMathBuiltin_exp: native = math_exp; break;
case AsmJSMathBuiltin_log: native = math_log; break;
case AsmJSMathBuiltin_pow: native = js_math_pow; break;
case AsmJSMathBuiltin_sqrt: native = js_math_sqrt; break;
case AsmJSMathBuiltin_abs: native = js_math_abs; break;
case AsmJSMathBuiltin_atan2: native = math_atan2; break;
case AsmJSMathBuiltin_imul: native = math_imul; break;
}
if (!IsNativeFunction(v, native))
return LinkFail(cx, "bad Math.* builtin");
return true;
}
static bool
ValidateGlobalConstant(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal)
{
RootedPropertyName field(cx, global.constantName());
RootedValue v(cx);
if (!GetProperty(cx, globalVal, field, &v))
return false;
if (!v.isNumber())
return LinkFail(cx, "global constant value needs to be a number");
// NaN != NaN
if (IsNaN(global.constantValue())) {
if (!IsNaN(v.toNumber()))
return LinkFail(cx, "global constant value needs to be NaN");
} else {
if (v.toNumber() != global.constantValue())
return LinkFail(cx, "global constant value mismatch");
}
return true;
}
static bool
DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module)
{
if (module.isLinked())
return LinkFail(cx, "As a temporary limitation, modules cannot be linked more than "
"once. This limitation should be removed in a future release. To "
"work around this, compile a second module (e.g., using the "
"Function constructor).");
RootedValue globalVal(cx, UndefinedValue());
if (args.length() > 0)
globalVal = args[0];
RootedValue importVal(cx, UndefinedValue());
if (args.length() > 1)
importVal = args[1];
RootedValue bufferVal(cx, UndefinedValue());
if (args.length() > 2)
bufferVal = args[2];
Rooted<ArrayBufferObject*> heap(cx);
if (module.hasArrayView()) {
if (!IsTypedArrayBuffer(bufferVal))
return LinkFail(cx, "bad ArrayBuffer argument");
heap = &bufferVal.toObject().as<ArrayBufferObject>();
if (!IsPowerOfTwo(heap->byteLength()) || heap->byteLength() < AsmJSAllocationGranularity)
return LinkFail(cx, "ArrayBuffer byteLength must be a power of two greater than or equal to 4096");
if (!ArrayBufferObject::prepareForAsmJS(cx, heap))
return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
#if defined(JS_CPU_X86)
void *heapOffset = (void*)heap->dataPointer();
void *heapLength = (void*)heap->byteLength();
uint8_t *code = module.functionCode();
for (unsigned i = 0; i < module.numHeapAccesses(); i++) {
const AsmJSHeapAccess &access = module.heapAccess(i);
JSC::X86Assembler::setPointer(access.patchLengthAt(code), heapLength);
JSC::X86Assembler::setPointer(access.patchOffsetAt(code), heapOffset);
}
#elif defined(JS_CPU_ARM)
// Now the length of the array is know, patch all of the bounds check sites
// with the new length.
jit::IonContext ic(cx, NULL);
module.patchBoundsChecks(heap->byteLength());
#endif
}
AutoObjectVector ffis(cx);
if (!ffis.resize(module.numFFIs()))
return false;
for (unsigned i = 0; i < module.numGlobals(); i++) {
AsmJSModule::Global &global = module.global(i);
switch (global.which()) {
case AsmJSModule::Global::Variable:
if (!ValidateGlobalVariable(cx, module, global, importVal))
return false;
break;
case AsmJSModule::Global::FFI:
if (!ValidateFFI(cx, global, importVal, &ffis))
return false;
break;
case AsmJSModule::Global::ArrayView:
if (!ValidateArrayView(cx, global, globalVal, bufferVal))
return false;
break;
case AsmJSModule::Global::MathBuiltin:
if (!ValidateMathBuiltin(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::Constant:
if (!ValidateGlobalConstant(cx, global, globalVal))
return false;
break;
}
}
for (unsigned i = 0; i < module.numExits(); i++)
module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>();
module.setIsLinked(heap);
return true;
}
AsmJSActivation::AsmJSActivation(JSContext *cx, const AsmJSModule &module)
: cx_(cx),
module_(module),
errorRejoinSP_(NULL),
profiler_(NULL),
resumePC_(NULL)
{
if (cx->runtime()->spsProfiler.enabled()) {
profiler_ = &cx->runtime()->spsProfiler;
profiler_->enterNative("asm.js code", this);
}
prev_ = cx_->runtime()->mainThread.asmJSActivationStack_;
JSRuntime::AutoLockForOperationCallback lock(cx_->runtime());
cx_->runtime()->mainThread.asmJSActivationStack_ = this;
(void) errorRejoinSP_; // squelch GCC warning
}
AsmJSActivation::~AsmJSActivation()
{
if (profiler_)
profiler_->exitNative();
JS_ASSERT(cx_->runtime()->mainThread.asmJSActivationStack_ == this);
JSRuntime::AutoLockForOperationCallback lock(cx_->runtime());
cx_->runtime()->mainThread.asmJSActivationStack_ = prev_;
}
static const unsigned ASM_MODULE_SLOT = 0;
static const unsigned ASM_EXPORT_INDEX_SLOT = 1;
extern JSBool
js::CallAsmJS(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs callArgs = CallArgsFromVp(argc, vp);
RootedFunction callee(cx, &callArgs.callee().as<JSFunction>());
// An asm.js function stores, in its extended slots:
// - a pointer to the module from which it was returned
// - its index in the ordered list of exported functions
RootedObject moduleObj(cx, &callee->getExtendedSlot(ASM_MODULE_SLOT).toObject());
const AsmJSModule &module = AsmJSModuleObjectToModule(moduleObj);
// An exported function points to the code as well as the exported
// function's signature, which implies the dynamic coercions performed on
// the arguments.
unsigned exportIndex = callee->getExtendedSlot(ASM_EXPORT_INDEX_SLOT).toInt32();
const AsmJSModule::ExportedFunction &func = module.exportedFunction(exportIndex);
// The calling convention for an external call into asm.js is to pass an
// array of 8-byte values where each value contains either a coerced int32
// (in the low word) or double value, with the coercions specified by the
// asm.js signature. The external entry point unpacks this array into the
// system-ABI-specified registers and stack memory and then calls into the
// internal entry point. The return value is stored in the first element of
// the array (which, therefore, must have length >= 1).
Vector<uint64_t, 8> coercedArgs(cx);
if (!coercedArgs.resize(Max<size_t>(1, func.numArgs())))
return false;
RootedValue v(cx);
for (unsigned i = 0; i < func.numArgs(); ++i) {
v = i < callArgs.length() ? callArgs[i] : UndefinedValue();
switch (func.argCoercion(i)) {
case AsmJS_ToInt32:
if (!ToInt32(cx, v, (int32_t*)&coercedArgs[i]))
return false;
break;
case AsmJS_ToNumber:
if (!ToNumber(cx, v, (double*)&coercedArgs[i]))
return false;
break;
}
}
{
AsmJSActivation activation(cx, module);
jit::IonContext ictx(cx, NULL);
JitActivation jitActivation(cx, /* firstFrameIsConstructing = */ false, /* active */ false);
// Call into generated code.
#ifdef JS_CPU_ARM
if (!func.code()(coercedArgs.begin(), module.globalData()))
return false;
#else
if (!func.code()(coercedArgs.begin()))
return false;
#endif
}
switch (func.returnType()) {
case AsmJSModule::Return_Void:
callArgs.rval().set(UndefinedValue());
break;
case AsmJSModule::Return_Int32:
callArgs.rval().set(Int32Value(*(int32_t*)&coercedArgs[0]));
break;
case AsmJSModule::Return_Double:
callArgs.rval().set(NumberValue(*(double*)&coercedArgs[0]));
break;
}
return true;
}
static JSFunction *
NewExportedFunction(JSContext *cx, const AsmJSModule::ExportedFunction &func,
HandleObject moduleObj, unsigned exportIndex)
{
RootedPropertyName name(cx, func.name());
JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, func.numArgs(),
JSFunction::NATIVE_FUN, cx->global(), name,
JSFunction::ExtendedFinalizeKind);
if (!fun)
return NULL;
fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj));
fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex));
return fun;
}
static bool
HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name)
{
if (cx->isExceptionPending())
return false;
const AsmJSModule::PostLinkFailureInfo &info = module.postLinkFailureInfo();
uint32_t length = info.bufEnd_ - info.bufStart_;
Rooted<JSStableString*> src(cx, info.scriptSource_->substring(cx, info.bufStart_, info.bufEnd_));
if (!src)
return false;
const jschar *chars = src->chars().get();
RootedFunction fun(cx, NewFunction(cx, NullPtr(), NULL, 0, JSFunction::INTERPRETED,
cx->global(), name, JSFunction::FinalizeKind,
TenuredObject));
if (!fun)
return false;
AutoNameVector formals(cx);
formals.reserve(3);
if (module.globalArgumentName())
formals.infallibleAppend(module.globalArgumentName());
if (module.importArgumentName())
formals.infallibleAppend(module.importArgumentName());
if (module.bufferArgumentName())
formals.infallibleAppend(module.bufferArgumentName());
if (!frontend::CompileFunctionBody(cx, &fun, info.options_, formals, chars, length,
/* isAsmJSRecompile = */ true))
return false;
// Call the function we just recompiled.
unsigned argc = args.length();
InvokeArgs args2(cx);
if (!args2.init(argc))
return false;
args2.setCallee(ObjectValue(*fun));
args2.setThis(args.thisv());
for (unsigned i = 0; i < argc; i++)
args2[i] = args[i];
if (!Invoke(cx, args2))
return false;
args.rval().set(args2.rval());
return true;
}
#ifdef MOZ_VTUNE
static bool
SendFunctionsToVTune(JSContext *cx, AsmJSModule &module)
{
uint8_t *base = module.functionCode();
for (unsigned i = 0; i < module.numProfiledFunctions(); i++) {
const AsmJSModule::ProfiledFunction &func = module.profiledFunction(i);
uint8_t *start = base + func.startCodeOffset;
uint8_t *end = base + func.endCodeOffset;
JS_ASSERT(end >= start);
unsigned method_id = iJIT_GetNewMethodID();
if (method_id == 0)
return false;
JSAutoByteString bytes;
const char *method_name = js_AtomToPrintableString(cx, func.name, &bytes);
if (!method_name)
return false;
iJIT_Method_Load method;
method.method_id = method_id;
method.method_name = const_cast<char *>(method_name);
method.method_load_address = (void *)start;
method.method_size = unsigned(end - start);
method.line_number_size = 0;
method.line_number_table = NULL;
method.class_id = 0;
method.class_file_name = NULL;
method.source_file_name = NULL;
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void *)&method);
}
return true;
}
#endif
JSBool
js::LinkAsmJS(JSContext *cx, unsigned argc, JS::Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction fun(cx, &args.callee().as<JSFunction>());
RootedObject moduleObj(cx, &AsmJSModuleObject(fun));
AsmJSModule &module = AsmJSModuleObjectToModule(moduleObj);
// If linking fails, recompile the function (including emitting bytecode)
// as if it's normal JS code.
if (!DynamicallyLinkModule(cx, args, module)) {
RootedPropertyName name(cx, fun->name());
return HandleDynamicLinkFailure(cx, args, module, name);
}
#if defined(MOZ_VTUNE)
if (!SendFunctionsToVTune(cx, module))
return false;
#endif
if (module.numExportedFunctions() == 1) {
const AsmJSModule::ExportedFunction &func = module.exportedFunction(0);
if (!func.maybeFieldName()) {
RootedFunction fun(cx, NewExportedFunction(cx, func, moduleObj, 0));
if (!fun)
return false;
args.rval().set(ObjectValue(*fun));
return true;
}
}
gc::AllocKind allocKind = gc::GetGCObjectKind(module.numExportedFunctions());
RootedObject obj(cx, NewBuiltinClassInstance(cx, &ObjectClass, allocKind));
if (!obj)
return false;
for (unsigned i = 0; i < module.numExportedFunctions(); i++) {
const AsmJSModule::ExportedFunction &func = module.exportedFunction(i);
RootedFunction fun(cx, NewExportedFunction(cx, func, moduleObj, i));
if (!fun)
return false;
JS_ASSERT(func.maybeFieldName() != NULL);
RootedId id(cx, NameToId(func.maybeFieldName()));
RootedValue val(cx, ObjectValue(*fun));
if (!DefineNativeProperty(cx, obj, id, val, NULL, NULL, JSPROP_ENUMERATE, 0, 0))
return false;
}
args.rval().set(ObjectValue(*obj));
return true;
}
bool
js::IsAsmJSModuleNative(js::Native native)
{
return native == LinkAsmJS;
}