Bug 925233 - OdinMonkey: fix neutering interaction with asm.js (r=sfink)

This commit is contained in:
Luke Wagner 2013-10-24 09:00:04 -05:00
parent db439a9bf3
commit 2abbde5d32
7 changed files with 140 additions and 37 deletions

View File

@ -0,0 +1,74 @@
load(libdir + "asm.js");
function f(stdlib, foreign, buffer) {
"use asm";
var i32 = new stdlib.Int32Array(buffer);
function set(i,j) {
i=i|0;
j=j|0;
i32[i>>2] = j;
}
function get(i) {
i=i|0;
return i32[i>>2]|0
}
return {get:get, set:set}
}
if (isAsmJSCompilationAvailable())
assertEq(isAsmJSModule(f), true);
var i32 = new Int32Array(1024);
var buffer = i32.buffer;
var {get, set} = f(this, null, buffer);
if (isAsmJSCompilationAvailable())
assertEq(isAsmJSFunction(get) && isAsmJSFunction(set), true);
set(4, 42);
assertEq(get(4), 42);
neuter(buffer);
// These operations may throw internal errors
try {
assertEq(get(4), 0);
set(0, 42);
assertEq(get(0), 0);
} catch (e) {
assertEq(String(e).indexOf("InternalError"), 0);
}
function f2(stdlib, foreign, buffer) {
"use asm";
var i32 = new stdlib.Int32Array(buffer);
var ffi = foreign.ffi;
function inner(i) {
i=i|0;
ffi();
return i32[i>>2]|0
}
return inner
}
if (isAsmJSCompilationAvailable())
assertEq(isAsmJSModule(f2), true);
var i32 = new Int32Array(1024);
var buffer = i32.buffer;
var threw = false;
function ffi() {
try {
neuter(buffer);
} catch (e) {
assertEq(String(e).indexOf("InternalError"), 0);
threw = true;
}
}
var inner = f2(this, {ffi:ffi}, buffer);
if (isAsmJSCompilationAvailable())
assertEq(isAsmJSFunction(inner), true);
i32[2] = 13;
var result = inner(8);
if (threw)
assertEq(result, 13);
else
assertEq(result, 0);

View File

@ -60,6 +60,7 @@ class AsmJSActivation
JSContext *cx() { return cx_; }
AsmJSModule &module() const { return module_; }
AsmJSActivation *prev() const { return prev_; }
// Read by JIT code:
static unsigned offsetOfContext() { return offsetof(AsmJSActivation, cx_); }

View File

@ -331,6 +331,15 @@ CallAsmJS(JSContext *cx, unsigned argc, Value *vp)
unsigned exportIndex = callee->getExtendedSlot(ASM_EXPORT_INDEX_SLOT).toInt32();
const AsmJSModule::ExportedFunction &func = module.exportedFunction(exportIndex);
// An asm.js module is specialized to its heap's base address and length
// which is normally immutable except for the neuter operation that occurs
// when an ArrayBuffer is transfered. Throw an internal error if we try to
// run with a neutered heap.
if (module.maybeHeapBufferObject() && module.maybeHeapBufferObject()->isNeutered()) {
js_ReportOverRecursed(cx);
return false;
}
// The calling convention for an external call into asm.js is to pass an
// array of 8-byte values where each value contains either a coerced int32
// (in the low word) or double value, with the coercions specified by the

View File

@ -729,6 +729,10 @@ class AsmJSModule
JS_ASSERT(linked_);
return heapDatum();
}
ArrayBufferObject *maybeHeapBufferObject() const {
JS_ASSERT(linked_);
return maybeHeap_;
}
size_t heapLength() const {
JS_ASSERT(linked_);
return maybeHeap_ ? maybeHeap_->byteLength() : 0;

View File

@ -755,12 +755,13 @@ class ObjectElements
{
public:
enum Flags {
CONVERT_DOUBLE_ELEMENTS = 0x1,
ASMJS_ARRAY_BUFFER = 0x2,
CONVERT_DOUBLE_ELEMENTS = 0x1,
ASMJS_ARRAY_BUFFER = 0x2,
NEUTERED_BUFFER = 0x4,
// Present only if these elements correspond to an array with
// non-writable length; never present for non-arrays.
NONWRITABLE_ARRAY_LENGTH = 0x4
NONWRITABLE_ARRAY_LENGTH = 0x8
};
private:
@ -768,6 +769,7 @@ class ObjectElements
friend class ObjectImpl;
friend class ArrayObject;
friend class ArrayBufferObject;
friend class TypedArrayObject;
friend class Nursery;
template <ExecutionMode mode>
@ -817,6 +819,12 @@ class ObjectElements
void setIsAsmJSArrayBuffer() {
flags |= ASMJS_ARRAY_BUFFER;
}
bool isNeuteredBuffer() const {
return flags & NEUTERED_BUFFER;
}
void setIsNeuteredBuffer() {
flags |= NEUTERED_BUFFER;
}
bool hasNonwritableArrayLength() const {
return flags & NONWRITABLE_ARRAY_LENGTH;
}

View File

@ -31,6 +31,7 @@
#include "gc/Barrier.h"
#include "gc/Marking.h"
#include "jit/AsmJS.h"
#include "jit/AsmJSModule.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/NumericConversions.h"
@ -330,27 +331,32 @@ GetViewListRef(ArrayBufferObject *obj)
return reinterpret_cast<OldObjectRepresentationHack*>(obj->getElementsHeader())->views;
}
void
ArrayBufferObject::neuterViews(JSContext *maybecx)
bool
ArrayBufferObject::neuterViews(JSContext *cx)
{
ArrayBufferViewObject *view;
for (view = GetViewList(this); view; view = view->nextView()) {
view->neuter();
// Notify compiled jit code that the base pointer has moved.
if (maybecx)
MarkObjectStateChange(maybecx, view);
MarkObjectStateChange(cx, view);
}
// neuterAsmJSArrayBuffer adjusts state specific to the ArrayBuffer data
// itself, but it only affects the behavior of views
if (isAsmJSArrayBuffer())
ArrayBufferObject::neuterAsmJSArrayBuffer(*this);
if (isAsmJSArrayBuffer()) {
if (!ArrayBufferObject::neuterAsmJSArrayBuffer(cx, *this))
return false;
}
return true;
}
void
ArrayBufferObject::changeContents(JSContext *maybecx, ObjectElements *newHeader)
{
JS_ASSERT(!isAsmJSArrayBuffer());
// Grab out data before invalidating it.
uint32_t byteLengthCopy = byteLength();
uintptr_t oldDataPointer = uintptr_t(dataPointer());
@ -391,6 +397,8 @@ ArrayBufferObject::neuter(JSContext *cx)
uint32_t byteLen = 0;
updateElementsHeader(getElementsHeader(), byteLen);
getElementsHeader()->setIsNeuteredBuffer();
}
bool
@ -523,22 +531,6 @@ ArrayBufferObject::releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj)
munmap(p, AsmJSMappedSize);
# endif
}
void
ArrayBufferObject::neuterAsmJSArrayBuffer(ArrayBufferObject &buffer)
{
// Protect all the pages so that any read/write will generate a fault which
// the AsmJSMemoryFaultHandler will turn into the expected result value.
JS_ASSERT(buffer.isAsmJSArrayBuffer());
JS_ASSERT(buffer.byteLength() % AsmJSAllocationGranularity == 0);
#ifdef XP_WIN
if (!VirtualAlloc(buffer.dataPointer(), buffer.byteLength(), MEM_RESERVE, PAGE_NOACCESS))
MOZ_CRASH();
#else
if (mprotect(buffer.dataPointer(), buffer.byteLength(), PROT_NONE))
MOZ_CRASH();
#endif
}
#else /* defined(JS_ION) && defined(JS_CPU_X64) */
bool
ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer)
@ -559,14 +551,23 @@ ArrayBufferObject::releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj)
{
fop->free_(obj->as<ArrayBufferObject>().getElementsHeader());
}
void
ArrayBufferObject::neuterAsmJSArrayBuffer(ArrayBufferObject &buffer)
{
// TODO: be ever-so-slightly unsound (but safe) for now.
}
#endif
bool
ArrayBufferObject::neuterAsmJSArrayBuffer(JSContext *cx, ArrayBufferObject &buffer)
{
AsmJSActivation *act = cx->mainThread().asmJSActivationStackFromOwnerThread();
for (; act; act = act->prev()) {
if (act->module().maybeHeapBufferObject() == &buffer)
break;
}
if (!act)
return true;
js_ReportOverRecursed(cx);
return false;
}
void
ArrayBufferObject::addView(ArrayBufferViewObject *view)
{
@ -679,7 +680,8 @@ ArrayBufferObject::stealContents(JSContext *cx, JSObject *obj, void **contents,
// Neuter the views, which may also mprotect(PROT_NONE) the buffer. So do
// it after copying out the data.
buffer.neuterViews(cx);
if (!buffer.neuterViews(cx))
return false;
if (!own) {
// If header has dynamically allocated elements, revert it back to
@ -4024,12 +4026,14 @@ JS_GetArrayBufferData(JSObject *obj)
return buffer.dataPointer();
}
JS_FRIEND_API(void)
JS_NeuterArrayBuffer(JSObject *obj, JSContext *cx)
JS_FRIEND_API(bool)
JS_NeuterArrayBuffer(JSContext *cx, JSObject *obj)
{
ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
buffer.neuterViews(cx);
if (!buffer.neuterViews(cx))
return false;
buffer.neuter(cx);
return true;
}
JS_FRIEND_API(JSObject *)

View File

@ -198,7 +198,7 @@ class ArrayBufferObject : public JSObject
/*
* Neuter all views of an ArrayBuffer.
*/
void neuterViews(JSContext *maybecx);
bool neuterViews(JSContext *cx);
inline uint8_t * dataPointer() const {
return (uint8_t *) elements;
@ -221,8 +221,11 @@ class ArrayBufferObject : public JSObject
bool isAsmJSArrayBuffer() const {
return getElementsHeader()->isAsmJSArrayBuffer();
}
bool isNeutered() const {
return getElementsHeader()->isNeuteredBuffer();
}
static bool prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer);
static void neuterAsmJSArrayBuffer(ArrayBufferObject &buffer);
static bool neuterAsmJSArrayBuffer(JSContext *cx, ArrayBufferObject &buffer);
static void releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj);
};