--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -234,16 +234,40 @@ ValidateArrayView(JSContext *cx, AsmJSMo
{
return LinkFail(cx, "bad typed array constructor");
}
return true;
}
static bool
+ValidateByteLength(JSContext *cx, HandleValue globalVal)
+{
+ RootedPropertyName field(cx, cx->names().byteLength);
+ RootedValue v(cx);
+ if (!GetDataProperty(cx, globalVal, field, &v))
+ return false;
+
+ if (!v.isObject() || !v.toObject().isBoundFunction())
+ return LinkFail(cx, "byteLength must be a bound function object");
+
+ RootedFunction fun(cx, &v.toObject().as<JSFunction>());
+
+ RootedValue boundTarget(cx, ObjectValue(*fun->getBoundFunctionTarget()));
+ if (!IsNativeFunction(boundTarget, js_fun_call))
+ return LinkFail(cx, "bound target of byteLength must be Function.prototype.call");
+
+ RootedValue boundThis(cx, fun->getBoundFunctionThis());
+ if (!IsNativeFunction(boundThis, ArrayBufferObject::byteLengthGetter))
+ return LinkFail(cx, "bound this value must be ArrayBuffer.protototype.byteLength accessor");
+
+ return true;
+}
+
+static bool
ValidateMathBuiltinFunction(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal)
{
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, cx->names().Math, &v))
return false;
RootedPropertyName field(cx, global.mathName());
if (!GetDataProperty(cx, v, field, &v))
@@ -436,19 +460,18 @@ LinkModuleToHeap(JSContext *cx, AsmJSMod
return LinkFail(cx, msg.get());
}
// This check is sufficient without considering the size of the loaded datum because heap
// loads and stores start on an aligned boundary and the heap byteLength has larger alignment.
MOZ_ASSERT((module.minHeapLength() - 1) <= INT32_MAX);
if (heapLength < module.minHeapLength()) {
ScopedJSFreePtr<char> msg(
- JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the "
- "largest constant heap access offset rounded up to the next valid "
- "heap size).",
+ JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (the size implied "
+ "by const heap accesses and/or change-heap minimum-length requirements).",
heapLength,
module.minHeapLength()));
return LinkFail(cx, msg.get());
}
// If we've generated the code with signal handlers in mind (for bounds
// checks on x64 and for interrupt callback requesting on all platforms),
// we need to be able to use signals at runtime. In particular, a module
@@ -467,27 +490,19 @@ LinkModuleToHeap(JSContext *cx, AsmJSMod
return true;
}
static bool
DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module)
{
module.setIsDynamicallyLinked();
- RootedValue globalVal(cx);
- if (args.length() > 0)
- globalVal = args[0];
-
- RootedValue importVal(cx);
- if (args.length() > 1)
- importVal = args[1];
-
- RootedValue bufferVal(cx);
- if (args.length() > 2)
- bufferVal = args[2];
+ HandleValue globalVal = args.get(0);
+ HandleValue importVal = args.get(1);
+ HandleValue bufferVal = args.get(2);
Rooted<ArrayBufferObjectMaybeShared *> heap(cx);
if (module.hasArrayView()) {
if (IsArrayBuffer(bufferVal) || IsSharedArrayBuffer(bufferVal))
heap = &AsAnyArrayBuffer(bufferVal);
else
return LinkFail(cx, "bad ArrayBuffer argument");
if (!LinkModuleToHeap(cx, module, heap))
@@ -509,16 +524,20 @@ DynamicallyLinkModule(JSContext *cx, Cal
if (!ValidateFFI(cx, global, importVal, &ffis))
return false;
break;
case AsmJSModule::Global::ArrayView:
case AsmJSModule::Global::ArrayViewCtor:
if (!ValidateArrayView(cx, global, globalVal))
return false;
break;
+ case AsmJSModule::Global::ByteLength:
+ if (!ValidateByteLength(cx, globalVal))
+ return false;
+ break;
case AsmJSModule::Global::MathBuiltinFunction:
if (!ValidateMathBuiltinFunction(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::Constant:
if (!ValidateConstant(cx, global, globalVal))
return false;
break;
@@ -536,22 +555,42 @@ DynamicallyLinkModule(JSContext *cx, Cal
for (unsigned i = 0; i < module.numExits(); i++)
module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>();
module.initGlobalNaN();
return true;
}
+static bool
+ChangeHeap(JSContext *cx, AsmJSModule &module, CallArgs args)
+{
+ HandleValue bufferArg = args.get(0);
+ if (!IsArrayBuffer(bufferArg)) {
+ ReportIncompatible(cx, args);
+ return false;
+ }
+
+ Rooted<ArrayBufferObject*> newBuffer(cx, &bufferArg.toObject().as<ArrayBufferObject>());
+ bool rval = module.changeHeap(newBuffer, cx);
+
+ args.rval().set(BooleanValue(rval));
+ return true;
+}
+
+// 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
static const unsigned ASM_MODULE_SLOT = 0;
static const unsigned ASM_EXPORT_INDEX_SLOT = 1;
static unsigned
FunctionToExportedFunctionIndex(HandleFunction fun)
{
+ MOZ_ASSERT(IsAsmJSFunction(fun));
Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT);
return v.toInt32();
}
static const AsmJSModule::ExportedFunction &
FunctionToExportedFunction(HandleFunction fun, AsmJSModule &module)
{
unsigned funIndex = FunctionToExportedFunctionIndex(fun);
@@ -559,41 +598,36 @@ FunctionToExportedFunction(HandleFunctio
}
static AsmJSModule &
FunctionToEnclosingModule(HandleFunction fun)
{
return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as<AsmJSModuleObject>().module();
}
-// The JSNative for the functions nested in an asm.js module. Calling this
-// native will trampoline into generated code.
+// This is the js::Native for functions exported by an asm.js module.
static bool
CallAsmJS(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs callArgs = CallArgsFromVp(argc, vp);
RootedFunction callee(cx, &callArgs.callee().as<JSFunction>());
+ AsmJSModule &module = FunctionToEnclosingModule(callee);
+ const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module);
- // 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
- AsmJSModule &module = FunctionToEnclosingModule(callee);
+ // The heap-changing function is a special-case and is implemented by C++.
+ if (func.isChangeHeap())
+ return ChangeHeap(cx, module, callArgs);
// Enable/disable profiling in the asm.js module to match the current global
// profiling state. Don't do this if the module is already active on the
// stack since this would leave the module in a state where profiling is
// enabled but the stack isn't unwindable.
if (module.profilingEnabled() != cx->runtime()->spsProfiler.enabled() && !module.active())
module.setProfilingEnabled(cx->runtime()->spsProfiler.enabled(), cx);
- // An exported function points to the code as well as the exported
- // function's signature, which implies the dynamic coercions performed on
- // the arguments.
- const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module);
-
// The calling convention for an external call into asm.js is to pass an
// array of 16-byte values where each value contains either a coerced int32
// (in the low word), a double value (in the low dword) or a SIMD vector
// 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).
@@ -699,19 +733,19 @@ CallAsmJS(JSContext *cx, unsigned argc,
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::ASMJS_CTOR, cx->global(), name,
- JSFunction::ExtendedFinalizeKind);
+ unsigned numArgs = func.isChangeHeap() ? 1 : func.numArgs();
+ JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, numArgs, JSFunction::ASMJS_CTOR,
+ cx->global(), name, JSFunction::ExtendedFinalizeKind);
if (!fun)
return nullptr;
fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj));
fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex));
return fun;
}
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -332,18 +332,20 @@ AsmJSModule::finish(ExclusiveContext *cx
#if defined(JS_CODEGEN_ARM)
// ARM requires the offsets to be updated.
pod.functionBytes_ = masm.actualOffset(pod.functionBytes_);
for (size_t i = 0; i < heapAccesses_.length(); i++) {
AsmJSHeapAccess &a = heapAccesses_[i];
a.setOffset(masm.actualOffset(a.offset()));
}
- for (unsigned i = 0; i < numExportedFunctions(); i++)
- exportedFunction(i).updateCodeOffset(masm);
+ for (unsigned i = 0; i < numExportedFunctions(); i++) {
+ if (!exportedFunction(i).isChangeHeap())
+ exportedFunction(i).updateCodeOffset(masm);
+ }
for (unsigned i = 0; i < numExits(); i++)
exit(i).updateOffsets(masm);
for (size_t i = 0; i < callSites_.length(); i++) {
CallSite &c = callSites_[i];
c.setReturnAddressOffset(masm.actualOffset(c.returnAddressOffset()));
}
for (size_t i = 0; i < codeRanges_.length(); i++) {
codeRanges_[i].updateOffsets(masm);
@@ -760,40 +762,61 @@ AsmJSModule::initHeap(Handle<ArrayBuffer
if (access.hasLengthCheck())
X86Assembler::setPointer(access.patchLengthAt(code_), heapLength);
void *addr = access.patchOffsetAt(code_);
uint32_t disp = reinterpret_cast<uint32_t>(X86Assembler::getPointer(addr));
MOZ_ASSERT(disp <= INT32_MAX);
X86Assembler::setPointer(addr, (void *)(heapOffset + disp));
}
#elif defined(JS_CODEGEN_X64)
- int32_t heapLength = int32_t(intptr_t(heap->byteLength()));
if (usesSignalHandlersForOOB())
return;
// If we cannot use the signal handlers, we need to patch the heap length
// checks at the right places. All accesses that have been recorded are the
// only ones that need bound checks (see also
// CodeGeneratorX64::visitAsmJS{Load,Store}Heap)
+ int32_t heapLength = int32_t(intptr_t(heap->byteLength()));
for (size_t i = 0; i < heapAccesses_.length(); i++) {
const jit::AsmJSHeapAccess &access = heapAccesses_[i];
if (access.hasLengthCheck())
X86Assembler::setInt32(access.patchLengthAt(code_), heapLength);
}
#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
uint32_t heapLength = heap->byteLength();
for (unsigned i = 0; i < heapAccesses_.length(); i++) {
jit::Assembler::UpdateBoundsCheck(heapLength,
(jit::Instruction*)(heapAccesses_[i].offset() + code_));
}
#endif
}
void
-AsmJSModule::restoreToInitialState(uint8_t *prevCode,
- ArrayBufferObjectMaybeShared *maybePrevBuffer,
+AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer)
+{
+#if defined(JS_CODEGEN_X86)
+ if (maybePrevBuffer) {
+ // Subtract out the base-pointer added by AsmJSModule::initHeap.
+ uint8_t *ptrBase = maybePrevBuffer->dataPointer();
+ for (unsigned i = 0; i < heapAccesses_.length(); i++) {
+ const jit::AsmJSHeapAccess &access = heapAccesses_[i];
+ void *addr = access.patchOffsetAt(code_);
+ uint8_t *ptr = reinterpret_cast<uint8_t*>(X86Assembler::getPointer(addr));
+ MOZ_ASSERT(ptr >= ptrBase);
+ X86Assembler::setPointer(addr, (void *)(ptr - ptrBase));
+ }
+ }
+#endif
+
+ maybeHeap_ = nullptr;
+ heapDatum() = nullptr;
+}
+
+void
+AsmJSModule::restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer,
+ uint8_t *prevCode,
ExclusiveContext *cx)
{
#ifdef DEBUG
// Put the absolute links back to -1 so PatchDataWithValueCheck assertions
// in staticallyLink are valid.
for (size_t imm = 0; imm < AsmJSImm_Limit; imm++) {
void *callee = AddressOf(AsmJSImmKind(imm), cx);
@@ -812,29 +835,17 @@ AsmJSModule::restoreToInitialState(uint8
: callee;
Assembler::PatchDataWithValueCheck(CodeLocationLabel(caller),
PatchedImmPtr((void*)-1),
PatchedImmPtr(originalValue));
}
}
#endif
- if (maybePrevBuffer) {
-#if defined(JS_CODEGEN_X86)
- // Subtract out the base-pointer added by AsmJSModule::initHeap.
- uint8_t *ptrBase = maybePrevBuffer->dataPointer();
- for (unsigned i = 0; i < heapAccesses_.length(); i++) {
- const jit::AsmJSHeapAccess &access = heapAccesses_[i];
- void *addr = access.patchOffsetAt(code_);
- uint8_t *ptr = reinterpret_cast<uint8_t*>(X86Assembler::getPointer(addr));
- MOZ_ASSERT(ptr >= ptrBase);
- X86Assembler::setPointer(addr, (void *)(ptr - ptrBase));
- }
-#endif
- }
+ restoreHeapToInitialState(maybePrevBuffer);
}
static void
AsmJSModuleObject_finalize(FreeOp *fop, JSObject *obj)
{
fop->delete_(&obj->as<AsmJSModuleObject>().module());
}
@@ -1545,17 +1556,32 @@ AsmJSModule::clone(JSContext *cx, Scoped
out.loadedFromCache_ = loadedFromCache_;
out.profilingEnabled_ = profilingEnabled_;
// We already know the exact extent of areas that need to be patched, just make sure we
// flush all of them at once.
out.setAutoFlushICacheRange();
- out.restoreToInitialState(code_, maybeHeap_, cx);
+ out.restoreToInitialState(maybeHeap_, code_, cx);
+ return true;
+}
+
+bool
+AsmJSModule::changeHeap(Handle<ArrayBufferObject*> newBuffer, JSContext *cx)
+{
+ uint32_t heapLength = newBuffer->byteLength();
+ if (heapLength & pod.heapLengthMask_ || heapLength < pod.minHeapLength_)
+ return false;
+
+ MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength));
+ MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength));
+
+ restoreHeapToInitialState(maybeHeap_);
+ initHeap(newBuffer, cx);
return true;
}
void
AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx)
{
MOZ_ASSERT(isDynamicallyLinked());
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -196,17 +196,17 @@ class AsmJSNumLit
// NB: this means that AsmJSModule must be GC-safe.
class AsmJSModule
{
public:
class Global
{
public:
enum Which { Variable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, Constant,
- SimdCtor, SimdOperation};
+ SimdCtor, SimdOperation, ByteLength };
enum VarInitKind { InitConstant, InitImport };
enum ConstantKind { GlobalConstant, MathConstant };
private:
struct Pod {
Which which_;
union {
struct {
@@ -405,41 +405,54 @@ class AsmJSModule
enum ReturnType { Return_Int32, Return_Double, Return_Int32x4, Return_Float32x4, Return_Void };
class ExportedFunction
{
PropertyName *name_;
PropertyName *maybeFieldName_;
ArgCoercionVector argCoercions_;
struct Pod {
+ bool isChangeHeap_;
ReturnType returnType_;
uint32_t codeOffset_;
- // These two fields are offsets to the beginning of the ScriptSource
- // of the module, and thus invariant under serialization (unlike
- // absolute offsets into ScriptSource).
- uint32_t startOffsetInModule_;
- uint32_t endOffsetInModule_;
+ uint32_t startOffsetInModule_; // Store module-start-relative offsets
+ uint32_t endOffsetInModule_; // so preserved by serialization.
} pod;
friend class AsmJSModule;
ExportedFunction(PropertyName *name,
uint32_t startOffsetInModule, uint32_t endOffsetInModule,
PropertyName *maybeFieldName,
ArgCoercionVector &&argCoercions,
ReturnType returnType)
{
+ MOZ_ASSERT(name->isTenured());
+ MOZ_ASSERT_IF(maybeFieldName, maybeFieldName->isTenured());
name_ = name;
maybeFieldName_ = maybeFieldName;
argCoercions_ = mozilla::Move(argCoercions);
+ pod.isChangeHeap_ = false;
pod.returnType_ = returnType;
pod.codeOffset_ = UINT32_MAX;
pod.startOffsetInModule_ = startOffsetInModule;
pod.endOffsetInModule_ = endOffsetInModule;
- MOZ_ASSERT_IF(maybeFieldName_, name_->isTenured());
+ }
+
+ ExportedFunction(PropertyName *name,
+ uint32_t startOffsetInModule, uint32_t endOffsetInModule,
+ PropertyName *maybeFieldName)
+ {
+ MOZ_ASSERT(name->isTenured());
+ MOZ_ASSERT_IF(maybeFieldName, maybeFieldName->isTenured());
+ name_ = name;
+ maybeFieldName_ = maybeFieldName;
+ pod.isChangeHeap_ = true;
+ pod.startOffsetInModule_ = startOffsetInModule;
+ pod.endOffsetInModule_ = endOffsetInModule;
}
void trace(JSTracer *trc) {
MarkStringUnbarriered(trc, &name_, "asm.js export name");
if (maybeFieldName_)
MarkStringUnbarriered(trc, &maybeFieldName_, "asm.js export field");
}
@@ -447,44 +460,53 @@ class AsmJSModule
ExportedFunction() {}
ExportedFunction(ExportedFunction &&rhs) {
name_ = rhs.name_;
maybeFieldName_ = rhs.maybeFieldName_;
argCoercions_ = mozilla::Move(rhs.argCoercions_);
pod = rhs.pod;
}
- void initCodeOffset(unsigned off) {
- MOZ_ASSERT(pod.codeOffset_ == UINT32_MAX);
- pod.codeOffset_ = off;
- }
- void updateCodeOffset(jit::MacroAssembler &masm) {
- pod.codeOffset_ = masm.actualOffset(pod.codeOffset_);
- }
-
-
PropertyName *name() const {
return name_;
}
+ PropertyName *maybeFieldName() const {
+ return maybeFieldName_;
+ }
uint32_t startOffsetInModule() const {
return pod.startOffsetInModule_;
}
uint32_t endOffsetInModule() const {
return pod.endOffsetInModule_;
}
- PropertyName *maybeFieldName() const {
- return maybeFieldName_;
+
+ bool isChangeHeap() const {
+ return pod.isChangeHeap_;
}
+
+ void initCodeOffset(unsigned off) {
+ MOZ_ASSERT(!isChangeHeap());
+ MOZ_ASSERT(pod.codeOffset_ == UINT32_MAX);
+ pod.codeOffset_ = off;
+ }
+ void updateCodeOffset(jit::MacroAssembler &masm) {
+ MOZ_ASSERT(!isChangeHeap());
+ pod.codeOffset_ = masm.actualOffset(pod.codeOffset_);
+ }
+
unsigned numArgs() const {
+ MOZ_ASSERT(!isChangeHeap());
return argCoercions_.length();
}
AsmJSCoercion argCoercion(unsigned i) const {
+ MOZ_ASSERT(!isChangeHeap());
return argCoercions_[i];
}
ReturnType returnType() const {
+ MOZ_ASSERT(!isChangeHeap());
return pod.returnType_;
}
size_t serializedSize() const;
uint8_t *serialize(uint8_t *cursor) const;
const uint8_t *deserialize(ExclusiveContext *cx, const uint8_t *cursor);
bool clone(ExclusiveContext *cx, ExportedFunction *out) const;
};
@@ -747,23 +769,25 @@ class AsmJSModule
private:
struct Pod {
size_t funcPtrTableAndExitBytes_;
size_t functionBytes_; // just the function bodies, no stubs
size_t codeBytes_; // function bodies and stubs
size_t totalBytes_; // function bodies, stubs, and global data
uint32_t minHeapLength_;
+ uint32_t heapLengthMask_;
uint32_t numGlobalScalarVars_;
uint32_t numGlobalSimdVars_;
uint32_t numFFIs_;
uint32_t srcLength_;
uint32_t srcLengthWithRightBrace_;
bool strict_;
bool hasArrayView_;
+ bool hasFixedMinHeapLength_;
bool usesSignalHandlers_;
} pod;
// These two fields need to be kept out pod as they depend on the position
// of the module within the ScriptSource and thus aren't invariant with
// respect to caching.
const uint32_t srcStart_;
const uint32_t srcBodyStart_;
@@ -956,16 +980,21 @@ class AsmJSModule
}
bool addArrayViewCtor(Scalar::Type vt, PropertyName *field) {
MOZ_ASSERT(!isFinishedWithModulePrologue());
MOZ_ASSERT(field);
Global g(Global::ArrayViewCtor, field);
g.pod.u.viewType_ = vt;
return globals_.append(g);
}
+ bool addByteLength() {
+ MOZ_ASSERT(!isFinishedWithModulePrologue());
+ Global g(Global::ByteLength, nullptr);
+ return globals_.append(g);
+ }
bool addMathBuiltinFunction(AsmJSMathBuiltinFunction func, PropertyName *field) {
MOZ_ASSERT(!isFinishedWithModulePrologue());
Global g(Global::MathBuiltinFunction, field);
g.pod.u.mathBuiltinFunc_ = func;
return globals_.append(g);
}
bool addMathBuiltinConstant(double value, PropertyName *field) {
MOZ_ASSERT(!isFinishedWithModulePrologue());
@@ -1005,21 +1034,33 @@ class AsmJSModule
MOZ_ASSERT(!isFinishedWithModulePrologue());
pod.funcPtrTableAndExitBytes_ = 0;
MOZ_ASSERT(isFinishedWithModulePrologue());
}
/*************************************************************************/
// These functions are called while parsing/compiling function bodies:
- void requireHeapLengthToBeAtLeast(uint32_t len) {
+ void addChangeHeap(uint32_t mask, uint32_t min) {
+ MOZ_ASSERT(isFinishedWithModulePrologue());
+ MOZ_ASSERT(!pod.hasFixedMinHeapLength_);
+ MOZ_ASSERT(IsValidAsmJSHeapLength(mask + 1));
+ MOZ_ASSERT(min >= RoundUpToNextValidAsmJSHeapLength(0));
+ pod.heapLengthMask_ = mask;
+ pod.minHeapLength_ = min;
+ pod.hasFixedMinHeapLength_ = true;
+ }
+ bool tryRequireHeapLengthToBeAtLeast(uint32_t len) {
MOZ_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
+ if (pod.hasFixedMinHeapLength_ && len > pod.minHeapLength_)
+ return false;
len = RoundUpToNextValidAsmJSHeapLength(len);
if (len > pod.minHeapLength_)
pod.minHeapLength_ = len;
+ return true;
}
bool addCodeRange(CodeRange::Kind kind, uint32_t begin, uint32_t end) {
return codeRanges_.append(CodeRange(kind, begin, end));
}
bool addCodeRange(CodeRange::Kind kind, uint32_t begin, uint32_t pret, uint32_t end) {
return codeRanges_.append(CodeRange(kind, begin, pret, end));
}
bool addFunctionCodeRange(PropertyName *name, uint32_t lineNumber,
@@ -1137,16 +1178,29 @@ class AsmJSModule
// the beginning of the module (so that they are caching-invariant).
MOZ_ASSERT(isFinishedWithFunctionBodies() && !isFinished());
MOZ_ASSERT(srcStart_ < funcSrcBegin);
MOZ_ASSERT(funcSrcBegin < funcSrcEnd);
ExportedFunction func(name, funcSrcBegin - srcStart_, funcSrcEnd - srcStart_,
maybeFieldName, mozilla::Move(argCoercions), returnType);
return exports_.length() < UINT32_MAX && exports_.append(mozilla::Move(func));
}
+ bool addExportedChangeHeap(PropertyName *name,
+ uint32_t funcSrcBegin,
+ uint32_t funcSrcEnd,
+ PropertyName *maybeFieldName)
+ {
+ // See addExportedFunction.
+ MOZ_ASSERT(isFinishedWithFunctionBodies() && !isFinished());
+ MOZ_ASSERT(srcStart_ < funcSrcBegin);
+ MOZ_ASSERT(funcSrcBegin < funcSrcEnd);
+ ExportedFunction func(name, funcSrcBegin - srcStart_, funcSrcEnd - srcStart_,
+ maybeFieldName);
+ return exports_.length() < UINT32_MAX && exports_.append(mozilla::Move(func));
+ }
unsigned numExportedFunctions() const {
MOZ_ASSERT(isFinishedWithFunctionBodies());
return exports_.length();
}
const ExportedFunction &exportedFunction(unsigned i) const {
MOZ_ASSERT(isFinishedWithFunctionBodies());
return exports_[i];
}
@@ -1268,16 +1322,17 @@ class AsmJSModule
}
AsmJSActivation *&activation() const {
return *(AsmJSActivation**)(globalData() + activationGlobalDataOffset());
}
bool active() const {
return activation() != nullptr;
}
static unsigned heapGlobalDataOffset() {
+ JS_STATIC_ASSERT(jit::AsmJSHeapGlobalDataOffset == sizeof(void*));
return sizeof(void*);
}
uint8_t *&heapDatum() const {
MOZ_ASSERT(isFinished());
return *(uint8_t**)(globalData() + heapGlobalDataOffset());
}
static unsigned nan64GlobalDataOffset() {
static_assert(jit::AsmJSNaN64GlobalDataOffset % sizeof(double) == 0,
@@ -1382,26 +1437,29 @@ class AsmJSModule
// variables. A given asm.js module cannot be dynamically linked more than
// once so, if JS tries, the module is cloned.
void setIsDynamicallyLinked() {
MOZ_ASSERT(!isDynamicallyLinked());
dynamicallyLinked_ = true;
MOZ_ASSERT(isDynamicallyLinked());
}
void initHeap(Handle<ArrayBufferObjectMaybeShared*> heap, JSContext *cx);
+ void restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer);
+ void restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer,
+ uint8_t *prevCode,
+ ExclusiveContext *cx);
bool clone(JSContext *cx, ScopedJSDeletePtr<AsmJSModule> *moduleOut) const;
- void restoreToInitialState(uint8_t *prevCode,
- ArrayBufferObjectMaybeShared *maybePrevBuffer,
- ExclusiveContext *cx);
+ bool changeHeap(Handle<ArrayBufferObject*> newHeap, JSContext *cx);
/*************************************************************************/
// Functions that can be called after dynamic linking succeeds:
CodePtr entryTrampoline(const ExportedFunction &func) const {
MOZ_ASSERT(isDynamicallyLinked());
+ MOZ_ASSERT(!func.isChangeHeap());
return JS_DATA_TO_FUNC_PTR(CodePtr, code_ + func.pod.codeOffset_);
}
uint8_t *interruptExit() const {
MOZ_ASSERT(isDynamicallyLinked());
return interruptExit_;
}
uint8_t *maybeHeap() const {
MOZ_ASSERT(isDynamicallyLinked());
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -1084,17 +1084,19 @@ class MOZ_STACK_CLASS ModuleCompiler
ConstantImport,
Function,
FuncPtrTable,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction,
SimdCtor,
- SimdOperation
+ SimdOperation,
+ ByteLength,
+ ChangeHeap
};
private:
Which which_;
union {
struct {
VarType::Which type_;
uint32_t index_;
@@ -1105,16 +1107,20 @@ class MOZ_STACK_CLASS ModuleCompiler
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
AsmJSSimdType simdCtorType_;
struct {
AsmJSSimdType type_;
AsmJSSimdOperation which_;
} simdOp;
+ struct {
+ uint32_t srcBegin_;
+ uint32_t srcEnd_;
+ } changeHeap;
} u;
friend class ModuleCompiler;
friend class js::LifoAlloc;
explicit Global(Which which) : which_(which) {}
public:
@@ -1172,16 +1178,24 @@ class MOZ_STACK_CLASS ModuleCompiler
AsmJSSimdOperation simdOperation() const {
MOZ_ASSERT(which_ == SimdOperation);
return u.simdOp.which_;
}
AsmJSSimdType simdOperationType() const {
MOZ_ASSERT(which_ == SimdOperation);
return u.simdOp.type_;
}
+ uint32_t changeHeapSrcBegin() const {
+ MOZ_ASSERT(which_ == ChangeHeap);
+ return u.changeHeap.srcBegin_;
+ }
+ uint32_t changeHeapSrcEnd() const {
+ MOZ_ASSERT(which_ == ChangeHeap);
+ return u.changeHeap.srcEnd_;
+ }
};
typedef Vector<const Func*> FuncPtrVector;
class FuncPtrTable
{
Signature sig_;
uint32_t mask_;
@@ -1259,16 +1273,26 @@ class MOZ_STACK_CLASS ModuleCompiler
explicit MathBuiltin(double cst) : kind(Constant) {
u.cst = cst;
}
explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
u.func = func;
}
};
+ struct ArrayView
+ {
+ ArrayView(PropertyName *name, Scalar::Type type)
+ : name(name), type(type)
+ {}
+
+ PropertyName *name;
+ Scalar::Type type;
+ };
+
private:
struct SlowFunction
{
SlowFunction(PropertyName *name, unsigned ms, unsigned line, unsigned column)
: name(name), ms(ms), line(line), column(column)
{}
PropertyName *name;
@@ -1278,46 +1302,50 @@ class MOZ_STACK_CLASS ModuleCompiler
};
typedef HashMap<PropertyName*, MathBuiltin> MathNameMap;
typedef HashMap<PropertyName*, AsmJSSimdOperation> SimdOperationNameMap;
typedef HashMap<PropertyName*, Global*> GlobalMap;
typedef Vector<Func*> FuncVector;
typedef Vector<AsmJSGlobalAccess> GlobalAccessVector;
typedef Vector<SlowFunction> SlowFunctionVector;
+ typedef Vector<ArrayView> ArrayViewVector;
ExclusiveContext * cx_;
AsmJSParser & parser_;
MacroAssembler masm_;
ScopedJSDeletePtr<AsmJSModule> module_;
LifoAlloc moduleLifo_;
ParseNode * moduleFunctionNode_;
PropertyName * moduleFunctionName_;
GlobalMap globals_;
FuncVector functions_;
FuncPtrTableVector funcPtrTables_;
+ ArrayViewVector arrayViews_;
ExitMap exits_;
MathNameMap standardLibraryMathNames_;
SimdOperationNameMap standardLibrarySimdOpNames_;
NonAssertingLabel stackOverflowLabel_;
NonAssertingLabel asyncInterruptLabel_;
NonAssertingLabel syncInterruptLabel_;
UniquePtr<char[], JS::FreePolicy> errorString_;
uint32_t errorOffset_;
bool errorOverRecursed_;
int64_t usecBefore_;
SlowFunctionVector slowFunctions_;
DebugOnly<bool> finishedFunctionBodies_;
bool supportsSimd_;
+ bool canValidateChangeHeap_;
+ bool hasChangeHeap_;
bool addStandardLibraryMathName(const char *name, AsmJSMathBuiltinFunction func) {
JSAtom *atom = Atomize(cx_, name, strlen(name));
if (!atom)
return false;
MathBuiltin builtin(func);
return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin);
}
@@ -1341,26 +1369,29 @@ class MOZ_STACK_CLASS ModuleCompiler
parser_(parser),
masm_(MacroAssembler::AsmJSToken()),
moduleLifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
moduleFunctionNode_(parser.pc->maybeFunction),
moduleFunctionName_(nullptr),
globals_(cx),
functions_(cx),
funcPtrTables_(cx),
+ arrayViews_(cx),
exits_(cx),
standardLibraryMathNames_(cx),
standardLibrarySimdOpNames_(cx),
errorString_(nullptr),
errorOffset_(UINT32_MAX),
errorOverRecursed_(false),
usecBefore_(PRMJ_Now()),
slowFunctions_(cx),
finishedFunctionBodies_(false),
- supportsSimd_(cx->jitSupportsSimd())
+ supportsSimd_(cx->jitSupportsSimd()),
+ canValidateChangeHeap_(false),
+ hasChangeHeap_(false)
{
MOZ_ASSERT(moduleFunctionNode_->pn_funbox == parser.pc->sc->asFunctionBox());
}
~ModuleCompiler() {
if (errorString_) {
MOZ_ASSERT(errorOffset_ != UINT32_MAX);
tokenStream().reportAsmJSError(errorOffset_,
@@ -1537,16 +1568,22 @@ class MOZ_STACK_CLASS ModuleCompiler
*op = p->value();
return true;
}
return false;
}
ExitMap::Range allExits() const {
return exits_.all();
}
+ unsigned numArrayViews() const {
+ return arrayViews_.length();
+ }
+ const ArrayView &arrayView(unsigned i) const {
+ return arrayViews_[i];
+ }
/***************************************************** Mutable interface */
void initModuleFunctionName(PropertyName *name) { moduleFunctionName_ = name; }
void initGlobalArgumentName(PropertyName *n) { module_->initGlobalArgumentName(n); }
void initImportArgumentName(PropertyName *n) { module_->initImportArgumentName(n); }
void initBufferArgumentName(PropertyName *n) { module_->initBufferArgumentName(n); }
@@ -1609,27 +1646,46 @@ class MOZ_STACK_CLASS ModuleCompiler
if (!module_->addFuncPtrTable(/* numElems = */ mask + 1, &globalDataOffset))
return false;
FuncPtrTable tmpTable(cx_, Move(sig), mask, globalDataOffset);
if (!funcPtrTables_.append(Move(tmpTable)))
return false;
*table = &funcPtrTables_.back();
return true;
}
+ bool addByteLength(PropertyName *name) {
+ canValidateChangeHeap_ = true;
+ if (!module_->addByteLength())
+ return false;
+ Global *global = moduleLifo_.new_<Global>(Global::ByteLength);
+ return global && globals_.putNew(name, global);
+ }
+ bool addChangeHeap(PropertyName *name, ParseNode *fn, uint32_t mask, uint32_t min) {
+ hasChangeHeap_ = true;
+ module_->addChangeHeap(mask, min);
+ Global *global = moduleLifo_.new_<Global>(Global::ChangeHeap);
+ if (!global)
+ return false;
+ global->u.changeHeap.srcBegin_ = fn->pn_pos.begin;
+ global->u.changeHeap.srcEnd_ = fn->pn_pos.end;
+ return globals_.putNew(name, global);
+ }
bool addFFI(PropertyName *varName, PropertyName *field) {
Global *global = moduleLifo_.new_<Global>(Global::FFI);
if (!global)
return false;
uint32_t index;
if (!module_->addFFI(field, &index))
return false;
global->u.ffiIndex_ = index;
return globals_.putNew(varName, global);
}
bool addArrayView(PropertyName *varName, Scalar::Type vt, PropertyName *maybeField) {
+ if (!arrayViews_.append(ArrayView(varName, vt)))
+ return false;
Global *global = moduleLifo_.new_<Global>(Global::ArrayView);
if (!global)
return false;
if (!module_->addArrayView(vt, maybeField))
return false;
global->u.viewType_ = vt;
return globals_.putNew(varName, global);
}
@@ -1688,52 +1744,64 @@ class MOZ_STACK_CLASS ModuleCompiler
return false;
return addGlobalDoubleConstant(varName, constant);
}
bool addGlobalConstant(PropertyName *varName, double constant, PropertyName *fieldName) {
if (!module_->addGlobalConstant(constant, fieldName))
return false;
return addGlobalDoubleConstant(varName, constant);
}
- bool addExportedFunction(const Func *func, PropertyName *maybeFieldName) {
+ bool addExportedFunction(const Func &func, PropertyName *maybeFieldName) {
AsmJSModule::ArgCoercionVector argCoercions;
- const VarTypeVector &args = func->sig().args();
+ const VarTypeVector &args = func.sig().args();
if (!argCoercions.resize(args.length()))
return false;
for (unsigned i = 0; i < args.length(); i++)
argCoercions[i] = args[i].toCoercion();
- AsmJSModule::ReturnType retType = func->sig().retType().toModuleReturnType();
- return module_->addExportedFunction(func->name(), func->srcBegin(), func->srcEnd(),
+ AsmJSModule::ReturnType retType = func.sig().retType().toModuleReturnType();
+ return module_->addExportedFunction(func.name(), func.srcBegin(), func.srcEnd(),
maybeFieldName, Move(argCoercions), retType);
}
+ bool addExportedChangeHeap(PropertyName *name, const Global &g, PropertyName *maybeFieldName) {
+ return module_->addExportedChangeHeap(name, g.changeHeapSrcBegin(), g.changeHeapSrcEnd(),
+ maybeFieldName);
+ }
bool addExit(unsigned ffiIndex, PropertyName *name, Signature &&sig, unsigned *exitIndex) {
ExitDescriptor exitDescriptor(name, Move(sig));
ExitMap::AddPtr p = exits_.lookupForAdd(exitDescriptor);
if (p) {
*exitIndex = p->value();
return true;
}
if (!module_->addExit(ffiIndex, exitIndex))
return false;
return exits_.add(p, Move(exitDescriptor), *exitIndex);
}
- void requireHeapLengthToBeAtLeast(uint32_t len) {
- module_->requireHeapLengthToBeAtLeast(len);
+ bool tryRequireHeapLengthToBeAtLeast(uint32_t len) {
+ return module_->tryRequireHeapLengthToBeAtLeast(len);
}
uint32_t minHeapLength() const {
return module_->minHeapLength();
}
LifoAlloc &lifo() {
return moduleLifo_;
}
void startFunctionBodies() {
module_->startFunctionBodies();
}
+ bool tryOnceToValidateChangeHeap() {
+ bool ret = canValidateChangeHeap_;
+ canValidateChangeHeap_ = false;
+ return ret;
+ }
+ bool hasChangeHeap() {
+ return hasChangeHeap_;
+ }
bool finishGeneratingFunction(Func &func, CodeGenerator &codegen,
const AsmJSFunctionLabels &labels)
{
uint32_t line, column;
tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
if (!module_->addFunctionCodeRange(func.name(), line, labels))
return false;
@@ -1971,18 +2039,20 @@ IsSimdTuple(ModuleCompiler &m, ParseNode
return false;
*type = global->simdCtorType();
return true;
}
static bool
IsNumericLiteral(ModuleCompiler &m, ParseNode *pn);
+
static AsmJSNumLit
ExtractNumericLiteral(ModuleCompiler &m, ParseNode *pn);
+
static inline bool
IsLiteralInt(ModuleCompiler &m, ParseNode *pn, uint32_t *u32);
static bool
IsSimdLiteral(ModuleCompiler &m, ParseNode *pn)
{
AsmJSSimdType type;
if (!IsSimdTuple(m, pn, &type))
@@ -2187,16 +2257,18 @@ class FunctionCompiler
NodeStack loopStack_;
NodeStack breakableStack_;
UnlabeledBlockMap unlabeledBreaks_;
UnlabeledBlockMap unlabeledContinues_;
LabeledBlockMap labeledBreaks_;
LabeledBlockMap labeledContinues_;
+ unsigned heapExpressionDepth_;
+
public:
FunctionCompiler(ModuleCompiler &m, ParseNode *fn, LifoAlloc &lifo)
: m_(m),
lifo_(lifo),
fn_(fn),
locals_(m.cx()),
varInitializers_(m.cx()),
alloc_(nullptr),
@@ -2204,17 +2276,18 @@ class FunctionCompiler
info_(nullptr),
mirGen_(nullptr),
curBlock_(nullptr),
loopStack_(m.cx()),
breakableStack_(m.cx()),
unlabeledBreaks_(m.cx()),
unlabeledContinues_(m.cx()),
labeledBreaks_(m.cx()),
- labeledContinues_(m.cx())
+ labeledContinues_(m.cx()),
+ heapExpressionDepth_(0)
{}
ModuleCompiler & m() const { return m_; }
TempAllocator & alloc() const { return *alloc_; }
LifoAlloc & lifo() const { return lifo_; }
ParseNode * fn() const { return fn_; }
ExclusiveContext * cx() const { return m_.cx(); }
const AsmJSModule & module() const { return m_.module(); }
@@ -2366,16 +2439,29 @@ class FunctionCompiler
return nullptr;
return m_.lookupGlobal(name);
}
bool supportsSimd() const {
return m_.supportsSimd();
}
+ /*************************************************************************/
+
+ void enterHeapExpression() {
+ heapExpressionDepth_++;
+ }
+ void leaveHeapExpression() {
+ MOZ_ASSERT(heapExpressionDepth_ > 0);
+ heapExpressionDepth_--;
+ }
+ bool canCall() const {
+ return heapExpressionDepth_ == 0 || !m_.hasChangeHeap();
+ }
+
/***************************** Code generation (after local scope setup) */
MDefinition *constant(const AsmJSNumLit &lit)
{
if (inDeadCode())
return nullptr;
MInstruction *constant;
@@ -3530,16 +3616,29 @@ IsArrayViewCtorName(ModuleCompiler &m, P
else if (name == names.Float64Array || name == names.SharedFloat64Array)
*type = Scalar::Float64;
else
return false;
return true;
}
static bool
+CheckNewArrayViewArgs(ModuleCompiler &m, ParseNode *ctorExpr, PropertyName *bufferName)
+{
+ ParseNode *bufArg = NextNode(ctorExpr);
+ if (!bufArg || NextNode(bufArg) != nullptr)
+ return m.fail(ctorExpr, "array view constructor takes exactly one argument");
+
+ if (!IsUseOfName(bufArg, bufferName))
+ return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName);
+
+ return true;
+}
+
+static bool
CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr)
{
PropertyName *globalName = m.module().globalArgumentName();
if (!globalName)
return m.fail(newExpr, "cannot create array view without an asm.js global parameter");
PropertyName *bufferName = m.module().bufferArgumentName();
if (!bufferName)
@@ -3569,22 +3668,18 @@ CheckNewArrayView(ModuleCompiler &m, Pro
if (global->which() != ModuleCompiler::Global::ArrayViewCtor)
return m.failName(ctorExpr, "%s must be an imported array view constructor", globalName);
field = nullptr;
type = global->viewType();
}
- ParseNode *bufArg = NextNode(ctorExpr);
- if (!bufArg || NextNode(bufArg) != nullptr)
- return m.fail(ctorExpr, "array view constructor takes exactly one argument");
-
- if (!IsUseOfName(bufArg, bufferName))
- return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName);
+ if (!CheckNewArrayViewArgs(m, ctorExpr, bufferName))
+ return false;
return m.addArrayView(varName, type, field);
}
static bool
IsSimdTypeName(ModuleCompiler &m, PropertyName *name, AsmJSSimdType *type)
{
if (name == m.cx()->names().int32x4) {
@@ -3694,16 +3789,18 @@ CheckGlobalDotImport(ModuleCompiler &m,
if (!base->isKind(PNK_NAME))
return m.fail(base, "expected name of variable or parameter");
if (base->name() == m.module().globalArgumentName()) {
if (field == m.cx()->names().NaN)
return m.addGlobalConstant(varName, GenericNaN(), field);
if (field == m.cx()->names().Infinity)
return m.addGlobalConstant(varName, PositiveInfinity<double>(), field);
+ if (field == m.cx()->names().byteLength)
+ return m.addByteLength(varName);
Scalar::Type type;
if (IsArrayViewCtorName(m, field, &type))
return m.addArrayViewCtor(varName, type, field);
return m.failName(initNode, "'%s' is not a standard constant or typed array name", field);
}
@@ -3968,16 +4065,18 @@ CheckVarRef(FunctionCompiler &f, ParseNo
case ModuleCompiler::Global::Function:
case ModuleCompiler::Global::FFI:
case ModuleCompiler::Global::MathBuiltinFunction:
case ModuleCompiler::Global::FuncPtrTable:
case ModuleCompiler::Global::ArrayView:
case ModuleCompiler::Global::ArrayViewCtor:
case ModuleCompiler::Global::SimdCtor:
case ModuleCompiler::Global::SimdOperation:
+ case ModuleCompiler::Global::ByteLength:
+ case ModuleCompiler::Global::ChangeHeap:
return f.failName(varRef, "'%s' may not be accessed by ordinary expressions", name);
}
return true;
}
return f.failName(varRef, "'%s' not found in local or asm.js module scope", name);
}
@@ -4045,17 +4144,20 @@ CheckArrayAccess(FunctionCompiler &f, Pa
uint32_t index;
if (IsLiteralOrConstInt(f, indexExpr, &index)) {
uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType);
if (byteOffset > INT32_MAX)
return f.fail(indexExpr, "constant index out of range");
unsigned elementSize = 1 << TypedArrayShift(*viewType);
- f.m().requireHeapLengthToBeAtLeast(byteOffset + elementSize);
+ if (!f.m().tryRequireHeapLengthToBeAtLeast(byteOffset + elementSize)) {
+ return f.failf(indexExpr, "constant index outside heap size declared by the "
+ "change-heap function (0x%x)", f.m().minHeapLength());
+ }
*needsBoundsCheck = NO_BOUNDS_CHECK;
*def = f.constant(Int32Value(byteOffset), Type::Int);
return true;
}
// Mask off the low bits to account for the clearing effect of a right shift
// followed by the left shift implicit in the array access. E.g., H32[i>>2]
@@ -4073,36 +4175,44 @@ CheckArrayAccess(FunctionCompiler &f, Pa
unsigned requiredShift = TypedArrayShift(*viewType);
if (shift != requiredShift)
return f.failf(shiftNode, "shift amount must be %u", requiredShift);
if (pointerNode->isKind(PNK_BITAND))
FoldMaskedArrayIndex(f, &pointerNode, &mask, needsBoundsCheck);
+ f.enterHeapExpression();
+
Type pointerType;
if (!CheckExpr(f, pointerNode, &pointerDef, &pointerType))
return false;
+ f.leaveHeapExpression();
+
if (!pointerType.isIntish())
return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
} else {
if (TypedArrayShift(*viewType) != 0)
return f.fail(indexExpr, "index expression isn't shifted; must be an Int8/Uint8 access");
MOZ_ASSERT(mask == -1);
bool folded = false;
if (indexExpr->isKind(PNK_BITAND))
folded = FoldMaskedArrayIndex(f, &indexExpr, &mask, needsBoundsCheck);
+ f.enterHeapExpression();
+
Type pointerType;
if (!CheckExpr(f, indexExpr, &pointerDef, &pointerType))
return false;
+ f.leaveHeapExpression();
+
if (folded) {
if (!pointerType.isIntish())
return f.failf(indexExpr, "%s is not a subtype of intish", pointerType.toChars());
} else {
if (!pointerType.isInt())
return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
}
}
@@ -4176,21 +4286,25 @@ static bool
CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition **def, Type *type)
{
Scalar::Type viewType;
MDefinition *pointerDef;
NeedsBoundsCheck needsBoundsCheck;
if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef, &needsBoundsCheck))
return false;
+ f.enterHeapExpression();
+
MDefinition *rhsDef;
Type rhsType;
if (!CheckExpr(f, rhs, &rhsDef, &rhsType))
return false;
+ f.leaveHeapExpression();
+
switch (viewType) {
case Scalar::Int8:
case Scalar::Int16:
case Scalar::Int32:
case Scalar::Uint8:
case Scalar::Uint16:
case Scalar::Uint32:
if (!rhsType.isIntish())
@@ -5195,16 +5309,21 @@ CheckCoercedSimdCall(FunctionCompiler &f
return CoerceResult(f, call, retType, *def, *type, def, type);
}
static bool
CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinition **def, Type *type)
{
JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
+ if (!f.canCall()) {
+ return f.fail(call, "call expressions may not be nested inside heap expressions when "
+ "the module contains a change-heap function");
+ }
+
if (IsNumericLiteral(f.m(), call)) {
AsmJSNumLit literal = ExtractNumericLiteral(f.m(), call);
MDefinition *result = f.constant(literal);
return CoerceResult(f, call, retType, result, Type::Of(literal), def, type);
}
ParseNode *callee = CallCallee(call);
@@ -5223,16 +5342,18 @@ CheckCoercedCall(FunctionCompiler &f, Pa
case ModuleCompiler::Global::MathBuiltinFunction:
return CheckCoercedMathBuiltinCall(f, call, global->mathBuiltinFunction(), retType, def, type);
case ModuleCompiler::Global::ConstantLiteral:
case ModuleCompiler::Global::ConstantImport:
case ModuleCompiler::Global::Variable:
case ModuleCompiler::Global::FuncPtrTable:
case ModuleCompiler::Global::ArrayView:
case ModuleCompiler::Global::ArrayViewCtor:
+ case ModuleCompiler::Global::ByteLength:
+ case ModuleCompiler::Global::ChangeHeap:
return f.failName(callee, "'%s' is not callable function", callee->name());
case ModuleCompiler::Global::SimdCtor:
case ModuleCompiler::Global::SimdOperation:
return CheckCoercedSimdCall(f, call, global, retType, def, type);
case ModuleCompiler::Global::Function:
break;
}
}
@@ -6363,16 +6484,205 @@ CheckStatement(FunctionCompiler &f, Pars
case PNK_CONTINUE: return f.addContinue(LoopControlMaybeLabel(stmt));
default:;
}
return f.fail(stmt, "unexpected statement kind");
}
static bool
+CheckByteLengthCall(ModuleCompiler &m, ParseNode *pn, PropertyName *newBufferName)
+{
+ if (!pn->isKind(PNK_CALL) || !CallCallee(pn)->isKind(PNK_NAME))
+ return m.fail(pn, "expecting call to imported byteLength");
+
+ const ModuleCompiler::Global *global = m.lookupGlobal(CallCallee(pn)->name());
+ if (!global || global->which() != ModuleCompiler::Global::ByteLength)
+ return m.fail(pn, "expecting call to imported byteLength");
+
+ if (CallArgListLength(pn) != 1 || !IsUseOfName(CallArgList(pn), newBufferName))
+ return m.failName(pn, "expecting %s as argument to byteLength call", newBufferName);
+
+ return true;
+}
+
+static bool
+CheckHeapLengthCondition(ModuleCompiler &m, ParseNode *cond, PropertyName *newBufferName,
+ uint32_t *mask, uint32_t *minimumLength)
+{
+ if (!cond->isKind(PNK_OR))
+ return m.fail(cond, "expecting byteLength & K || byteLength <= L");
+
+ ParseNode *leftCond = BinaryLeft(cond);
+ ParseNode *rightCond = BinaryRight(cond);
+
+ if (!leftCond->isKind(PNK_BITAND))
+ return m.fail(leftCond, "expecting byteLength & K");
+
+ if (!CheckByteLengthCall(m, BinaryLeft(leftCond), newBufferName))
+ return false;
+
+ ParseNode *maskNode = BinaryRight(leftCond);
+ if (!IsLiteralInt(m, maskNode, mask))
+ return m.fail(maskNode, "expecting integer literal mask");
+ if ((*mask & 0xffffff) != 0xffffff)
+ return m.fail(maskNode, "mask value must have the bits 0xffffff set");
+
+ if (!rightCond->isKind(PNK_LE))
+ return m.fail(rightCond, "expecting byteLength <= L");
+
+ if (!CheckByteLengthCall(m, BinaryLeft(rightCond), newBufferName))
+ return false;
+
+ ParseNode *minLengthNode = BinaryRight(rightCond);
+ uint32_t minLengthExclusive;
+ if (!IsLiteralInt(m, minLengthNode, &minLengthExclusive))
+ return m.fail(minLengthNode, "expecting integer limit literal");
+ if (minLengthExclusive < 0xffffff)
+ return m.fail(minLengthNode, "limit value must be >= 0xffffff");
+
+ // Add one to convert from exclusive (the branch rejects if ==) to inclusive.
+ *minimumLength = minLengthExclusive + 1;
+ return true;
+}
+
+static bool
+CheckReturnBoolLiteral(ModuleCompiler &m, ParseNode *stmt, bool retval)
+{
+ if (!stmt)
+ return m.fail(stmt, "expected return statement");
+
+ if (stmt->isKind(PNK_STATEMENTLIST)) {
+ stmt = SkipEmptyStatements(ListHead(stmt));
+ if (!stmt || NextNonEmptyStatement(stmt))
+ return m.fail(stmt, "expected single return statement");
+ }
+
+ if (!stmt->isKind(PNK_RETURN))
+ return m.fail(stmt, "expected return statement");
+
+ ParseNode *returnExpr = ReturnExpr(stmt);
+ if (!returnExpr || !returnExpr->isKind(retval ? PNK_TRUE : PNK_FALSE))
+ return m.failf(stmt, "expected 'return %s;'", retval ? "true" : "false");
+
+ return true;
+}
+
+static bool
+CheckReassignmentTo(ModuleCompiler &m, ParseNode *stmt, PropertyName *lhsName, ParseNode **rhs)
+{
+ if (!stmt || !stmt->isKind(PNK_SEMI))
+ return m.fail(stmt, "missing reassignment");
+
+ ParseNode *assign = UnaryKid(stmt);
+ if (!assign || !assign->isKind(PNK_ASSIGN))
+ return m.fail(stmt, "missing reassignment");
+
+ ParseNode *lhs = BinaryLeft(assign);
+ if (!IsUseOfName(lhs, lhsName))
+ return m.failName(lhs, "expecting reassignment of %s", lhsName);
+
+ *rhs = BinaryRight(assign);
+ return true;
+}
+
+static bool
+CheckChangeHeap(ModuleCompiler &m, ParseNode *fn, bool *validated)
+{
+ MOZ_ASSERT(fn->isKind(PNK_FUNCTION));
+
+ // We don't yet know whether this is a change-heap function.
+ // The point at which we know we have a change-heap function is once we see
+ // whether the argument is coerced according to the normal asm.js rules. If
+ // it is coerced, it's not change-heap and must validate according to normal
+ // rules; otherwise it must validate as a change-heap function.
+ *validated = false;
+
+ PropertyName *changeHeapName = FunctionName(fn);
+ if (!CheckModuleLevelName(m, fn, changeHeapName))
+ return false;
+
+ unsigned numFormals;
+ ParseNode *arg = FunctionArgsList(fn, &numFormals);
+ if (numFormals != 1)
+ return true;
+
+ PropertyName *newBufferName;
+ if (!CheckArgument(m, arg, &newBufferName))
+ return false;
+
+ ParseNode *stmtIter = SkipEmptyStatements(ListHead(FunctionStatementList(fn)));
+ if (!stmtIter || !stmtIter->isKind(PNK_IF))
+ return true;
+
+ // We can now issue validation failures if we see something that isn't a
+ // valid change-heap function.
+ *validated = true;
+
+ PropertyName *bufferName = m.module().bufferArgumentName();
+ if (!bufferName)
+ return m.fail(fn, "to change heaps, the module must have a buffer argument");
+
+ ParseNode *cond = TernaryKid1(stmtIter);
+ ParseNode *thenStmt = TernaryKid2(stmtIter);
+ if (ParseNode *elseStmt = TernaryKid3(stmtIter))
+ return m.fail(elseStmt, "unexpected else statement");
+
+ uint32_t mask, min;
+ if (!CheckHeapLengthCondition(m, cond, newBufferName, &mask, &min))
+ return false;
+
+ if (!CheckReturnBoolLiteral(m, thenStmt, false))
+ return false;
+
+ stmtIter = NextNonEmptyStatement(stmtIter);
+
+ for (unsigned i = 0; i < m.numArrayViews(); i++, stmtIter = NextNonEmptyStatement(stmtIter)) {
+ const ModuleCompiler::ArrayView &view = m.arrayView(i);
+
+ ParseNode *rhs;
+ if (!CheckReassignmentTo(m, stmtIter, view.name, &rhs))
+ return false;
+
+ if (!rhs->isKind(PNK_NEW))
+ return m.failName(rhs, "expecting assignment of new array view to %s", view.name);
+
+ ParseNode *ctorExpr = ListHead(rhs);
+ if (!ctorExpr->isKind(PNK_NAME))
+ return m.fail(rhs, "expecting name of imported typed array constructor");
+
+ const ModuleCompiler::Global *global = m.lookupGlobal(ctorExpr->name());
+ if (!global || global->which() != ModuleCompiler::Global::ArrayViewCtor)
+ return m.fail(rhs, "expecting name of imported typed array constructor");
+ if (global->viewType() != view.type)
+ return m.fail(rhs, "can't change the type of a global view variable");
+
+ if (!CheckNewArrayViewArgs(m, ctorExpr, newBufferName))
+ return false;
+ }
+
+ ParseNode *rhs;
+ if (!CheckReassignmentTo(m, stmtIter, bufferName, &rhs))
+ return false;
+ if (!IsUseOfName(rhs, newBufferName))
+ return m.failName(stmtIter, "expecting assignment of new buffer to %s", bufferName);
+
+ stmtIter = NextNonEmptyStatement(stmtIter);
+
+ if (!CheckReturnBoolLiteral(m, stmtIter, true))
+ return false;
+
+ stmtIter = NextNonEmptyStatement(stmtIter);
+ if (stmtIter)
+ return m.fail(stmtIter, "expecting end of function");
+
+ return m.addChangeHeap(changeHeapName, fn, mask, min);
+}
+
+static bool
ParseFunction(ModuleCompiler &m, ParseNode **fnOut)
{
TokenStream &tokenStream = m.tokenStream();
DebugOnly<TokenKind> tk = tokenStream.getToken();
MOZ_ASSERT(tk == TOK_FUNCTION);
RootedPropertyName name(m.cx());
@@ -6437,16 +6747,26 @@ CheckFunction(ModuleCompiler &m, LifoAll
ParseNode *fn;
if (!ParseFunction(m, &fn))
return false;
if (!CheckFunctionHead(m, fn))
return false;
+ if (m.tryOnceToValidateChangeHeap()) {
+ bool validated;
+ if (!CheckChangeHeap(m, fn, &validated))
+ return false;
+ if (validated) {
+ *mir = nullptr;
+ return true;
+ }
+ }
+
FunctionCompiler f(m, fn, lifo);
if (!f.init())
return false;
ParseNode *stmtIter = ListHead(FunctionStatementList(fn));
VarTypeVector argTypes(m.lifo());
if (!CheckArguments(f, &stmtIter, &argTypes))
@@ -6546,16 +6866,20 @@ CheckFunctionsSequential(ModuleCompiler
while (PeekToken(m.parser()) == TOK_FUNCTION) {
LifoAllocScope scope(&lifo);
MIRGenerator *mir;
ModuleCompiler::Func *func;
if (!CheckFunction(m, lifo, &mir, &func))
return false;
+ // In the case of the change-heap function, no MIR is produced.
+ if (!mir)
+ continue;
+
int64_t before = PRMJ_Now();
IonContext icx(m.cx(), &mir->alloc());
IonSpewNewFunction(&mir->graph(), NullPtr());
if (!OptimizeMIR(mir))
return m.failOffset(func->srcBegin(), "internal compiler failure (probably out of memory)");
@@ -6643,17 +6967,17 @@ GetFinishedCompilation(ModuleCompiler &m
}
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
}
return nullptr;
}
static bool
-GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask)
+GetUsedTask(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask)
{
// Block until a used LifoAlloc becomes available.
AsmJSParallelTask *task = GetFinishedCompilation(m, group);
if (!task)
return false;
ModuleCompiler::Func &func = *reinterpret_cast<ModuleCompiler::Func *>(task->func);
func.accumulateCompileTime(task->compileTime);
@@ -6695,40 +7019,44 @@ CheckFunctionsParallel(ModuleCompiler &m
{
AutoLockHelperThreadState lock;
MOZ_ASSERT(HelperThreadState().asmJSWorklist().empty());
MOZ_ASSERT(HelperThreadState().asmJSFinishedList().empty());
}
#endif
HelperThreadState().resetAsmJSFailureState();
+ AsmJSParallelTask *task = nullptr;
for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) {
- // Get exclusive access to an empty LifoAlloc from the thread group's pool.
- AsmJSParallelTask *task = nullptr;
- if (!GetUnusedTask(group, i, &task) && !GenerateCodeForFinishedJob(m, group, &task))
+ if (!task && !GetUnusedTask(group, i, &task) && !GetUsedTask(m, group, &task))
return false;
// Generate MIR into the LifoAlloc on the main thread.
MIRGenerator *mir;
ModuleCompiler::Func *func;
if (!CheckFunction(m, task->lifo, &mir, &func))
return false;
+ // In the case of the change-heap function, no MIR is produced.
+ if (!mir)
+ continue;
+
// Perform optimizations and LIR generation on a helper thread.
task->init(m.cx()->compartment()->runtimeFromAnyThread(), func, mir);
if (!StartOffThreadAsmJSCompile(m.cx(), task))
return false;
group.outstandingJobs++;
+ task = nullptr;
}
// Block for all outstanding helpers to complete.
while (group.outstandingJobs > 0) {
AsmJSParallelTask *ignored = nullptr;
- if (!GenerateCodeForFinishedJob(m, group, &ignored))
+ if (!GetUsedTask(m, group, &ignored))
return false;
}
if (!CheckAllFunctionsDefined(m))
return false;
MOZ_ASSERT(group.outstandingJobs == 0);
MOZ_ASSERT(group.compiledJobs == m.numFunctions());
@@ -6900,28 +7228,33 @@ CheckFuncPtrTables(ModuleCompiler &m)
if (!m.funcPtrTable(i).initialized())
return m.fail(nullptr, "expecting function-pointer table");
}
return true;
}
static bool
-CheckModuleExportFunction(ModuleCompiler &m, ParseNode *returnExpr)
-{
- if (!returnExpr->isKind(PNK_NAME))
- return m.fail(returnExpr, "export statement must be of the form 'return name'");
-
- PropertyName *funcName = returnExpr->name();
-
- const ModuleCompiler::Func *func = m.lookupFunction(funcName);
- if (!func)
- return m.failName(returnExpr, "exported function name '%s' not found", funcName);
-
- return m.addExportedFunction(func, /* maybeFieldName = */ nullptr);
+CheckModuleExportFunction(ModuleCompiler &m, ParseNode *pn, PropertyName *maybeFieldName = nullptr)
+{
+ if (!pn->isKind(PNK_NAME))
+ return m.fail(pn, "expected name of exported function");
+
+ PropertyName *funcName = pn->name();
+ const ModuleCompiler::Global *global = m.lookupGlobal(funcName);
+ if (!global)
+ return m.failName(pn, "exported function name '%s' not found", funcName);
+
+ if (global->which() == ModuleCompiler::Global::Function)
+ return m.addExportedFunction(m.function(global->funcIndex()), maybeFieldName);
+
+ if (global->which() == ModuleCompiler::Global::ChangeHeap)
+ return m.addExportedChangeHeap(funcName, *global, maybeFieldName);
+
+ return m.failName(pn, "'%s' is not a function", funcName);
}
static bool
CheckModuleExportObject(ModuleCompiler &m, ParseNode *object)
{
MOZ_ASSERT(object->isKind(PNK_OBJECT));
for (ParseNode *pn = ListHead(object); pn; pn = NextNode(pn)) {
@@ -6929,23 +7262,17 @@ CheckModuleExportObject(ModuleCompiler &
return m.fail(pn, "only normal object properties may be used in the export object literal");
PropertyName *fieldName = ObjectNormalFieldName(m.cx(), pn);
ParseNode *initNode = ObjectNormalFieldInitializer(m.cx(), pn);
if (!initNode->isKind(PNK_NAME))
return m.fail(initNode, "initializer of exported object literal must be name of function");
- PropertyName *funcName = initNode->name();
-
- const ModuleCompiler::Func *func = m.lookupFunction(funcName);
- if (!func)
- return m.failName(initNode, "exported function name '%s' not found", funcName);
-
- if (!m.addExportedFunction(func, fieldName))
+ if (!CheckModuleExportFunction(m, initNode, fieldName))
return false;
}
return true;
}
static bool
CheckModuleReturn(ModuleCompiler &m)
@@ -7070,20 +7397,19 @@ GenerateEntry(ModuleCompiler &m, unsigne
// addressing, x86 uses immediates in effective addresses). For the
// AsmJSGlobalRegBias addition, see Assembler-(mips,arm).h.
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
masm.movePtr(IntArgReg1, GlobalReg);
masm.addPtr(Imm32(AsmJSGlobalRegBias), GlobalReg);
#endif
// ARM, MIPS and x64 have a globally-pinned HeapReg (x86 uses immediates in
- // effective addresses).
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
- masm.loadPtr(Address(IntArgReg1, AsmJSModule::heapGlobalDataOffset()), HeapReg);
-#endif
+ // effective addresses). Loading the heap register depends on the global
+ // register already having been loaded.
+ masm.loadAsmJSHeapRegisterFromGlobalData();
// Put the 'argv' argument into a non-argument/return register so that we
// can use 'argv' while we fill in the arguments for the asm.js callee.
// Also, save 'argv' on the stack so that we can recover it after the call.
// Use a second non-argument/return register as temporary scratch.
Register argv = ABIArgGenerator::NonArgReturnReg0;
Register scratch = ABIArgGenerator::NonArgReturnReg1;
#if defined(JS_CODEGEN_X86)
@@ -7333,67 +7659,63 @@ GenerateFFIInterpExit(ModuleCompiler &m,
break;
case RetType::Float:
MOZ_CRASH("Float32 shouldn't be returned from a FFI");
case RetType::Int32x4:
case RetType::Float32x4:
MOZ_CRASH("SIMD types shouldn't be returned from a FFI");
}
+ // The heap pointer may have changed during the FFI, so reload it.
+ masm.loadAsmJSHeapRegisterFromGlobalData();
+
Label profilingReturn;
GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::SlowFFI, &profilingReturn);
return m.finishGeneratingInterpExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
}
// On ARM/MIPS, we need to include an extra word of space at the top of the
// stack so we can explicitly store the return address before making the call
-// to C++ or Ion. On x86/x64, this isn't necessary since the call instruction
-// pushes the return address.
+// to C++ or Ion and an extra word to store the pinned global-data register. On
+// x86/x64, neither is necessary since the call instruction pushes the return
+// address and global data is reachable via immediate or rip-relative
+// addressing.
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
static const unsigned MaybeRetAddr = sizeof(void*);
+static const unsigned MaybeSavedGlobalReg = sizeof(void*);
#else
static const unsigned MaybeRetAddr = 0;
+static const unsigned MaybeSavedGlobalReg = 0;
#endif
static bool
GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
unsigned exitIndex, Label *throwLabel)
{
MacroAssembler &masm = m.masm();
- // Even though the caller has saved volatile registers, we still need to
- // save/restore globally-pinned asm.js registers at Ion calls since Ion does
- // not preserve non-volatile registers.
-#if defined(JS_CODEGEN_X64)
- unsigned savedRegBytes = 1 * sizeof(void*); // HeapReg
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
- unsigned savedRegBytes = 2 * sizeof(void*); // HeapReg, GlobalReg
-#else
- unsigned savedRegBytes = 0;
-#endif
-
// The same stack frame is used for the call into Ion and (possibly) a call
// into C++ to coerce the return type. To do this, we compute the amount of
// space required for both calls and take the maximum. In both cases,
- // include space for savedRegBytes, since these go below the Ion/coerce.
+ // include space for MaybeSavedGlobalReg, since this goes below the Ion/coerce.
// Ion calls use the following stack layout (sp grows to the left):
// | return address | descriptor | callee | argc | this | arg1 | arg2 | ...
unsigned offsetToIonArgs = MaybeRetAddr;
unsigned ionArgBytes = 3 * sizeof(size_t) + (1 + exit.sig().args().length()) * sizeof(Value);
- unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + savedRegBytes;
+ unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + MaybeSavedGlobalReg;
unsigned ionFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalIonBytes);
// Coercion calls use the following stack layout (sp grows to the left):
// | stack args | padding | Value argv[1] | ...
// The padding between args and argv ensures that argv is aligned.
MIRTypeVector coerceArgTypes(m.cx());
coerceArgTypes.infallibleAppend(MIRType_Pointer); // argv
unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(double));
- unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + savedRegBytes;
+ unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + MaybeSavedGlobalReg;
unsigned coerceFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalCoerceBytes);
unsigned framePushed = Max(ionFrameSize, coerceFrameSize);
Label begin;
GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::IonFFI, &begin);
// 1. Descriptor
@@ -7437,25 +7759,27 @@ GenerateFFIIonExit(ModuleCompiler &m, co
argOffset += sizeof(Value);
// 5. Fill the arguments
unsigned offsetToCallerStackArgs = framePushed + sizeof(AsmJSFrame);
FillArgumentArray(m, exit.sig().args(), argOffset, offsetToCallerStackArgs, scratch);
argOffset += exit.sig().args().length() * sizeof(Value);
MOZ_ASSERT(argOffset == offsetToIonArgs + ionArgBytes);
- // 6. Store asm.js pinned registers
-#if defined(JS_CODEGEN_X64)
- unsigned savedHeapOffset = framePushed - sizeof(void*);
- masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset));
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
- unsigned savedHeapOffset = framePushed - 1 * sizeof(void*);
- unsigned savedGlobalOffset = framePushed - 2 * sizeof(void*);
- masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset));
- masm.storePtr(GlobalReg, Address(StackPointer, savedGlobalOffset));
+ // 6. Ion will clobber all registers, even non-volatiles. GlobalReg and
+ // HeapReg are removed from the general register set for asm.js code, so
+ // these will not have been saved by the caller like all other registers,
+ // so they must be explicitly preserved. Only save GlobalReg since
+ // HeapReg must be reloaded (from global data) after the call since the
+ // heap may change during the FFI call.
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+ JS_STATIC_ASSERT(MaybeSavedGlobalReg > 0);
+ unsigned savedGlobalOffset = framePushed - MaybeSavedGlobalReg;
+#else
+ JS_STATIC_ASSERT(MaybeSavedGlobalReg == 0);
#endif
{
// Enable Activation.
//
// This sequence requires four registers, and needs to preserve the 'callee'
// register, so there are five live registers.
MOZ_ASSERT(callee == AsmJSIonExitRegCallee);
@@ -7519,24 +7843,16 @@ GenerateFFIIonExit(ModuleCompiler &m, co
masm.loadPtr(Address(reg0, offsetOfActivation), reg1);
masm.store8(Imm32(0), Address(reg1, JitActivation::offsetOfActiveUint8()));
masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitTop()), reg2);
masm.storePtr(reg2, Address(reg0, offsetOfJitTop));
masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitJSContext()), reg2);
masm.storePtr(reg2, Address(reg0, offsetOfJitJSContext));
}
- MOZ_ASSERT(masm.framePushed() == framePushed);
-#if defined(JS_CODEGEN_X64)
- masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg);
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
- masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg);
- masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg);
-#endif
-
masm.branchTestMagic(Assembler::Equal, JSReturnOperand, throwLabel);
Label oolConvert;
switch (exit.sig().retType().which()) {
case RetType::Void:
break;
case RetType::Signed:
masm.convertValueToInt32(JSReturnOperand, ReturnDoubleReg, ReturnReg, &oolConvert,
@@ -7550,16 +7866,27 @@ GenerateFFIIonExit(ModuleCompiler &m, co
case RetType::Int32x4:
case RetType::Float32x4:
MOZ_CRASH("SIMD types shouldn't be returned from a FFI");
}
Label done;
masm.bind(&done);
+ MOZ_ASSERT(masm.framePushed() == framePushed);
+
+ // Reload pinned registers after all calls into arbitrary JS.
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+ JS_STATIC_ASSERT(MaybeSavedGlobalReg > 0);
+ masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg);
+#else
+ JS_STATIC_ASSERT(MaybeSavedGlobalReg == 0);
+#endif
+ masm.loadAsmJSHeapRegisterFromGlobalData();
+
Label profilingReturn;
GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::IonFFI, &profilingReturn);
if (oolConvert.used()) {
masm.bind(&oolConvert);
masm.setFramePushed(framePushed);
// Store return value into argv[0]
@@ -7762,16 +8089,17 @@ GenerateAsyncInterruptExit(ModuleCompile
masm.branchIfFalseBool(ReturnReg, throwLabel);
// Restore the StackPointer to it's position before the call.
masm.mov(ABIArgGenerator::NonVolatileReg, StackPointer);
// Restore the machine state to before the interrupt.
masm.PopRegsInMask(AllRegsExceptSP, AllRegsExceptSP.fpus()); // restore all GP/FP registers (except SP)
+ masm.loadAsmJSHeapRegisterFromGlobalData(); // In case there was a changeHeap
masm.popFlags(); // after this, nothing that sets conditions
masm.ret(); // pop resumePC into PC
#elif defined(JS_CODEGEN_MIPS)
// Reserve space to store resumePC.
masm.subPtr(Imm32(sizeof(intptr_t)), StackPointer);
// set to zero so we can use masm.framePushed() below.
masm.setFramePushed(0);
// When this platform supports SIMD extensions, we'll need to push high lanes
@@ -7801,21 +8129,19 @@ GenerateAsyncInterruptExit(ModuleCompile
masm.branchIfFalseBool(ReturnReg, throwLabel);
// This will restore stack to the address before the call.
masm.movePtr(s0, StackPointer);
masm.PopRegsInMask(AllRegsExceptSP);
// Pop resumePC into PC. Clobber HeapReg to make the jump and restore it
// during jump delay slot.
- MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSModule::heapGlobalDataOffset() - AsmJSGlobalRegBias));
masm.pop(HeapReg);
masm.as_jr(HeapReg);
- masm.loadPtr(Address(GlobalReg, AsmJSModule::heapGlobalDataOffset() - AsmJSGlobalRegBias),
- HeapReg);
+ masm.loadAsmJSHeapRegisterFromGlobalData(); // In case there was a changeHeap
#elif defined(JS_CODEGEN_ARM)
masm.setFramePushed(0); // set to zero so we can use masm.framePushed() below
masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(Registers::AllMask & ~(1<<Registers::sp)), FloatRegisterSet(uint32_t(0)))); // save all GP registers,excep sp
// Save both the APSR and FPSCR in non-volatile registers.
masm.as_mrs(r4);
masm.as_vmrs(r5);
// Save the stack pointer in a non-volatile register.
@@ -7855,16 +8181,17 @@ GenerateAsyncInterruptExit(ModuleCompile
masm.transferReg(r7);
masm.transferReg(r8);
masm.transferReg(r9);
masm.transferReg(r10);
masm.transferReg(r11);
masm.transferReg(r12);
masm.transferReg(lr);
masm.finishDataTransfer();
+ masm.loadAsmJSHeapRegisterFromGlobalData(); // In case there was a changeHeap
masm.ret();
#elif defined (JS_CODEGEN_NONE)
MOZ_CRASH();
#else
# error "Unknown architecture!"
#endif
@@ -7880,16 +8207,19 @@ GenerateSyncInterruptExit(ModuleCompiler
unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, ShadowStackSpace);
GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Interrupt, &m.syncInterruptLabel());
AssertStackAlignment(masm, ABIStackAlignment);
masm.call(AsmJSImmPtr(AsmJSImm_HandleExecutionInterrupt));
masm.branchIfFalseBool(ReturnReg, throwLabel);
+ // Reload the heap register in case the callback changed heaps.
+ masm.loadAsmJSHeapRegisterFromGlobalData();
+
Label profilingReturn;
GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Interrupt, &profilingReturn);
return m.finishGeneratingInterrupt(&m.syncInterruptLabel(), &profilingReturn) && !masm.oom();
}
// If an exception is thrown, simply pop all frames (since asm.js does not
// contain try/catch). To do this:
// 1. Restore 'sp' to it's value right after the PushRegsInMask in GenerateEntry.
@@ -7920,16 +8250,18 @@ GenerateThrowStub(ModuleCompiler &m, Lab
return m.finishGeneratingInlineStub(throwLabel) && !masm.oom();
}
static bool
GenerateStubs(ModuleCompiler &m)
{
for (unsigned i = 0; i < m.module().numExportedFunctions(); i++) {
+ if (m.module().exportedFunction(i).isChangeHeap())
+ continue;
if (!GenerateEntry(m, i))
return false;
}
Label throwLabel;
for (ModuleCompiler::ExitMap::Range r = m.allExits(); !r.empty(); r.popFront()) {
if (!GenerateFFIExits(m, r.front().key(), r.front().value(), &throwLabel))
--- a/js/src/jit-test/lib/asm.js
+++ b/js/src/jit-test/lib/asm.js
@@ -7,16 +7,17 @@ const ASM_TYPE_FAIL_STRING = "asm.js typ
const ASM_DIRECTIVE_FAIL_STRING = "\"use asm\" is only meaningful in the Directive Prologue of a function body";
const USE_ASM = '"use asm";';
const HEAP_IMPORTS = "const i8=new glob.Int8Array(b);var u8=new glob.Uint8Array(b);"+
"const i16=new glob.Int16Array(b);var u16=new glob.Uint16Array(b);"+
"const i32=new glob.Int32Array(b);var u32=new glob.Uint32Array(b);"+
"const f32=new glob.Float32Array(b);var f64=new glob.Float64Array(b);";
const BUF_MIN = 64 * 1024;
+const BUF_CHANGE_MIN = 16 * 1024 * 1024;
const BUF_64KB = new ArrayBuffer(BUF_MIN);
function asmCompile()
{
var f = Function.apply(null, arguments);
assertEq(!isAsmJSCompilationAvailable() || isAsmJSModule(f), true);
return f;
}
--- a/js/src/jit-test/tests/asm.js/testResize.js
+++ b/js/src/jit-test/tests/asm.js/testResize.js
@@ -1,9 +1,12 @@
load(libdir + "asm.js");
+load(libdir + "asserts.js");
+
+// Tests for importing typed array view constructors
assertAsmTypeFail('glob', USE_ASM + "var I32=glob.Int32Arra; function f() {} return f");
var m = asmCompile('glob', USE_ASM + "var I32=glob.Int32Array; function f() {} return f");
assertAsmLinkFail(m, {});
assertAsmLinkFail(m, {Int32Array:null});
assertAsmLinkFail(m, {Int32Array:{}});
assertAsmLinkFail(m, {Int32Array:Uint32Array});
assertEq(asmLink(m, {Int32Array:Int32Array})(), undefined);
@@ -25,8 +28,246 @@ assertAsmLinkAlwaysFail(m, this, null, n
assertAsmLinkFail(m, this, null, new ArrayBuffer(100));
assertEq(asmLink(m, this, null, BUF_64KB)(), undefined);
var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var F32=glob.Float32Array; var i32=new glob.Int32Array(buf); function f() {} return f');
assertAsmLinkFail(m, this, null, {});
assertAsmLinkAlwaysFail(m, this, null, null);
assertAsmLinkFail(m, this, null, new ArrayBuffer(100));
assertEq(asmLink(m, this, null, BUF_64KB)(), undefined);
+
+// Tests for link-time validation of byteLength import
+
+assertAsmTypeFail('glob', 'ffis', 'buf', USE_ASM + 'var byteLength=glob.byteLength; function f() { return byteLength(1)|0 } return f');
+
+var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var byteLength=glob.byteLength; function f() { return 42 } return f');
+assertEq('byteLength' in this, false);
+assertAsmLinkFail(m, this);
+this['byteLength'] = null;
+assertAsmLinkFail(m, this);
+this['byteLength'] = {};
+assertAsmLinkFail(m, this);
+this['byteLength'] = function(){}
+assertAsmLinkFail(m, this);
+this['byteLength'] = (function(){}).bind(null);
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind();
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind({});
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind(function f() {});
+assertAsmLinkFail(m, this);
+this['byteLength'] = Function.prototype.call.bind(Math.sin);
+assertAsmLinkFail(m, this);
+this['byteLength'] =
+ Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get);
+assertEq(asmLink(m, this)(), 42);
+
+var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var b1=glob.byteLength, b2=glob.byteLength; function f() { return 43 } return f');
+assertEq(asmLink(m, this)(), 43);
+
+// Tests for validation of change-heap function
+
+const BYTELENGTH_IMPORT = "var len = glob.byteLength; ";
+const IMPORT0 = BYTELENGTH_IMPORT;
+const IMPORT1 = "var I8=glob.Int8Array; var i8=new I8(b); " + BYTELENGTH_IMPORT;
+const IMPORT2 = "var I8=glob.Int8Array; var i8=new I8(b); var I32=glob.Int32Array; var i32=new I32(b); var II32=glob.Int32Array; " + BYTELENGTH_IMPORT;
+
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function f() { return 42 } function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function b(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function f(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2=1) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2,xyz) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(...r) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2,...r) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch({b2}) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { ;if((len((b2))) & (0xffffff) || (len((b2)) <= (0xffffff))) {;;return false;;} ; i8=new I8(b2);; b=b2;; return true;; } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function ch2(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { 3; if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { b2=b2|0; if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(1) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(1 || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(1 & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || 1) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(i8(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(xyz) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff && len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) | 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) == 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xfffffe || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0x1ffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) < 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xfffffe) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0x1000000) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) ; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) {} i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) {return false} i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return true; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT0 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i7=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=1; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new 1; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I7(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new b(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8; b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(1); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2,1); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); xyz=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=1; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; 1; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return 1 } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return false } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true; 1 } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); i8=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i32=new I32(b2); b=b2; return true } function f() { return 42 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I32(b2); i32=new I8(b2); b=b2; return true } function f() { return 42 } return f');
+ asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i32=new II32(b2); b=b2; return true } function f() { return 42 } return f');
+
+// Tests for no calls in heap index expressions
+
+const SETUP = USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i8=new I8(b2); i32=new I32(b2); b=b2; return true }';
+
+ asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { i32[0] } return f');
+ asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { i32[0] = 0 } return f');
+ asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] } return f');
+ asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] = 0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = g()|0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] = 0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = i32[(g()|0)>>2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] = 0 } function g() { return 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = (i32[i>>2]|0) + (g()|0) } function g() { return 0 } return f');
+
+// Tests for constant heap accesses when change-heap is used
+
+const HEADER = USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & MASK || len(b2) <= MIN) return false; i8=new I8(b2); b=b2; return true } ';
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { i8[0x1000000] = 0 } return f');
+ asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { i8[0xffffff] = 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { i8[0x1000001] = 0 } return f');
+ asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { i8[0x1000000] = 0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { return i8[0x1000000]|0 } return f');
+ asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { return i8[0xffffff]|0 } return f');
+assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { return i8[0x1000001]|0 } return f');
+ asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { return i8[0x1000000]|0 } return f');
+
+// Tests for validation of heap length
+
+var body = USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & MASK || len(b2) <= MIN) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return ch';
+var m = asmCompile('glob', 'ffis', 'b', body.replace('MASK', '0xffffff').replace('MIN', '0x1ffffff'));
+assertAsmLinkFail(m, this, null, new ArrayBuffer(BUF_CHANGE_MIN));
+assertAsmLinkFail(m, this, null, new ArrayBuffer(0x1000000));
+var changeHeap = asmLink(m, this, null, new ArrayBuffer(0x2000000));
+assertEq(changeHeap(new ArrayBuffer(0x1000000)), false);
+assertEq(changeHeap(new ArrayBuffer(0x2000000)), true);
+assertEq(changeHeap(new ArrayBuffer(0x2000001)), false);
+assertThrowsInstanceOf(() => changeHeap(null), TypeError);
+assertThrowsInstanceOf(() => changeHeap({}), TypeError);
+assertThrowsInstanceOf(() => changeHeap(new Int32Array(100)), TypeError);
+
+var detached = new ArrayBuffer(BUF_CHANGE_MIN);
+neuter(detached, "change-data");
+assertEq(changeHeap(detached), false);
+
+// Tests for runtime changing heap
+
+const CHANGE_HEAP = 'var changeHeap = glob.byteLength;';
+
+var changeHeapSource = `function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }`;
+var body = `var I32=glob.Int32Array; var i32=new I32(b);
+ var len=glob.byteLength;` +
+ changeHeapSource +
+ `function get(i) { i=i|0; return i32[i>>2]|0 }
+ function set(i, v) { i=i|0; v=v|0; i32[i>>2] = v }
+ return {get:get, set:set, changeHeap:ch}`;
+
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM + body);
+var buf1 = new ArrayBuffer(BUF_CHANGE_MIN);
+var {get, set, changeHeap} = asmLink(m, this, null, buf1);
+
+assertEq(m.toString(), "function anonymous(glob, ffis, b) {\n" + USE_ASM + body + "\n}");
+assertEq(m.toSource(), "(function anonymous(glob, ffis, b) {\n" + USE_ASM + body + "\n})");
+assertEq(changeHeap.toString(), changeHeapSource);
+assertEq(changeHeap.toSource(), changeHeapSource);
+
+set(0, 42);
+set(4, 13);
+assertEq(get(0), 42);
+assertEq(get(4), 13);
+set(BUF_CHANGE_MIN, 262);
+assertEq(get(BUF_CHANGE_MIN), 0);
+var buf2 = new ArrayBuffer(2*BUF_CHANGE_MIN);
+assertEq(changeHeap(buf2), true);
+assertEq(get(0), 0);
+assertEq(get(4), 0);
+set(BUF_CHANGE_MIN, 262);
+assertEq(get(BUF_CHANGE_MIN), 262);
+changeHeap(buf1);
+assertEq(get(0), 42);
+assertEq(get(4), 13);
+set(BUF_CHANGE_MIN, 262);
+assertEq(get(BUF_CHANGE_MIN), 0);
+
+var buf1 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf1)[0] = 13;
+var buf2 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf2)[0] = 42;
+
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+ `var len=glob.byteLength;
+ function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true }
+ return ch`);
+var changeHeap = asmLink(m, this, null, buf1);
+changeHeap(buf2);
+
+// Tests for changing heap during an FFI:
+
+// Set the warmup to '2' so we can hit both interp and ion FFI exits
+setJitCompilerOption("ion.warmup.trigger", 2);
+setJitCompilerOption("baseline.warmup.trigger", 0);
+setJitCompilerOption("offthread-compilation.enable", 0);
+
+var changeToBuf = null;
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+ `var ffi=ffis.ffi;
+ var I32=glob.Int32Array; var i32=new I32(b);
+ var len=glob.byteLength;
+ function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }
+ function test(i) { i=i|0; var sum=0; sum = i32[i>>2]|0; sum = (sum + (ffi()|0))|0; sum = (sum + (i32[i>>2]|0))|0; return sum|0 }
+ return {test:test, changeHeap:ch}`);
+var ffi = function() { changeHeap(changeToBuf); return 1 }
+var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf1);
+changeToBuf = buf1;
+assertEq(test(0), 27);
+changeToBuf = buf2;
+assertEq(test(0), 56);
+changeToBuf = buf2;
+assertEq(test(0), 85);
+changeToBuf = buf1;
+assertEq(test(0), 56);
+changeToBuf = buf1;
+assertEq(test(0), 27);
+
+var ffi = function() { return { valueOf:function() { changeHeap(changeToBuf); return 100 } } };
+var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf1);
+changeToBuf = buf1;
+assertEq(test(0), 126);
+changeToBuf = buf2;
+assertEq(test(0), 155);
+changeToBuf = buf2;
+assertEq(test(0), 184);
+changeToBuf = buf1;
+assertEq(test(0), 155);
+changeToBuf = buf1;
+assertEq(test(0), 126);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout7.js
@@ -0,0 +1,41 @@
+load(libdir + "asm.js");
+
+// This test may iloop for valid reasons if not compiled with asm.js (namely,
+// inlining may allow the heap load to be hoisted out of the loop).
+if (!isAsmJSCompilationAvailable())
+ quit();
+
+var byteLength =
+ Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get);
+
+var buf1 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf1)[0] = 13;
+var buf2 = new ArrayBuffer(BUF_CHANGE_MIN);
+new Int32Array(buf2)[0] = 42;
+
+// Test changeHeap from interrupt (as if that could ever happen...)
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+ `var I32=glob.Int32Array; var i32=new I32(b);
+ var len=glob.byteLength;
+ function changeHeap(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }
+ function f() {}
+ function loop(i) { i=i|0; while((i32[i>>2]|0) == 13) { f() } }
+ return {loop:loop, changeHeap:changeHeap}`);
+var { loop, changeHeap } = asmLink(m, this, null, buf1);
+timeout(1, function() { changeHeap(buf2); return true });
+loop(0);
+timeout(-1);
+
+// Try again, but this time with signals disabled
+setJitCompilerOption("signals.enable", 0);
+var m = asmCompile('glob', 'ffis', 'b', USE_ASM +
+ `var I32=glob.Int32Array; var i32=new I32(b);
+ var len=glob.byteLength;
+ function changeHeap(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }
+ function f() {}
+ function loop(i) { i=i|0; while((i32[i>>2]|0) == 13) { f() } }
+ return {loop:loop, changeHeap:changeHeap}`);
+var { loop, changeHeap } = asmLink(m, this, null, buf1);
+timeout(1, function() { changeHeap(buf2); return true });
+loop(0);
+timeout(-1);
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1640,16 +1640,19 @@ class MacroAssemblerARMCompat : public M
#ifdef JSGC_GENERATIONAL
void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label);
void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label *label);
#endif
void loadAsmJSActivation(Register dest) {
loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest);
}
+ void loadAsmJSHeapRegisterFromGlobalData() {
+ loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
+ }
};
typedef MacroAssemblerARMCompat MacroAssemblerSpecific;
} // namespace jit
} // namespace js
#endif /* jit_arm_MacroAssembler_arm_h */
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -1299,16 +1299,20 @@ public:
void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label);
void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp,
Label *label);
#endif
void loadAsmJSActivation(Register dest) {
loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest);
}
+ void loadAsmJSHeapRegisterFromGlobalData() {
+ MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias));
+ loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg);
+ }
};
typedef MacroAssemblerMIPSCompat MacroAssemblerSpecific;
} // namespace jit
} // namespace js
#endif /* jit_mips_MacroAssembler_mips_h */
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -657,16 +657,17 @@ struct AsmJSFrame
void *returnAddress;
};
static_assert(sizeof(AsmJSFrame) == 2 * sizeof(void*), "?!");
static const uint32_t AsmJSFrameBytesAfterReturnAddress = sizeof(void*);
// A hoisting of constants that would otherwise require #including AsmJSModule.h
// everywhere. Values are asserted in AsmJSModule.h.
static const unsigned AsmJSActivationGlobalDataOffset = 0;
+static const unsigned AsmJSHeapGlobalDataOffset = sizeof(void*);
static const unsigned AsmJSNaN64GlobalDataOffset = 2 * sizeof(void*);
static const unsigned AsmJSNaN32GlobalDataOffset = 2 * sizeof(void*) + sizeof(double);
// Summarizes a heap access made by asm.js code that needs to be patched later
// and/or looked up by the asm.js signal handlers. Different architectures need
// to know different things (x64: offset and length, ARM: where to patch in
// heap length, x86: where to patch in heap length and base) hence the massive
// #ifdefery.
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -629,16 +629,20 @@ class Assembler : public AssemblerX86Sha
CodeOffsetLabel leaRipRelative(Register dest) {
return CodeOffsetLabel(masm.leaq_rip(dest.code()).offset());
}
void loadAsmJSActivation(Register dest) {
CodeOffsetLabel label = loadRipRelativeInt64(dest);
append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset));
}
+ void loadAsmJSHeapRegisterFromGlobalData() {
+ CodeOffsetLabel label = loadRipRelativeInt64(HeapReg);
+ append(AsmJSGlobalAccess(label, AsmJSHeapGlobalDataOffset));
+ }
// The below cmpq methods switch the lhs and rhs when it invokes the
// macroassembler to conform with intel standard. When calling this
// function put the left operand on the left as you would expect.
void cmpq(const Operand &lhs, Register rhs) {
switch (lhs.kind()) {
case Operand::REG:
masm.cmpq_rr(rhs.code(), lhs.reg());
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -239,16 +239,27 @@ CodeGeneratorX64::visitStoreTypedArrayEl
{
MOZ_CRASH("NYI");
}
bool
CodeGeneratorX64::visitAsmJSCall(LAsmJSCall *ins)
{
emitAsmJSCall(ins);
+
+#ifdef DEBUG
+ Register scratch = ABIArgGenerator::NonReturn_VolatileReg0;
+ masm.movePtr(HeapReg, scratch);
+ masm.loadAsmJSHeapRegisterFromGlobalData();
+ Label ok;
+ masm.branchPtr(Assembler::Equal, HeapReg, scratch, &ok);
+ masm.breakpoint();
+ masm.bind(&ok);
+#endif
+
return true;
}
bool
CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
{
MAsmJSLoadHeap *mir = ins->mir();
Scalar::Type vt = mir->viewType();
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -567,16 +567,19 @@ class Assembler : public AssemblerX86Sha
masm.movaps_rm(src.code(), dest.addr);
return CodeOffsetLabel(masm.currentOffset());
}
void loadAsmJSActivation(Register dest) {
CodeOffsetLabel label = movlWithPatch(PatchedAbsoluteAddress(), dest);
append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset));
}
+ void loadAsmJSHeapRegisterFromGlobalData() {
+ // x86 doesn't have a pinned heap register.
+ }
static bool canUseInSingleByteInstruction(Register reg) {
return !ByteRegRequiresRex(reg.code());
}
};
// Get a register in which we plan to put a quantity that will be used as an
// integer argument. This differs from GetIntArgReg in that if we have no more