From 123db6349f243be2d973453d4cef8038281fe229 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 17 Oct 2014 00:15:30 -0500 Subject: [PATCH] Bug 1081379 - Optimize ArrayBuffer.transfer for 64-bit asm.js-ified ArrayBuffers (r=sfink) --HG-- extra : rebase_source : fc2af81e096354a1c12e51b08e5886a3744a77c0 --- js/src/jit-test/tests/asm.js/testResize.js | 61 +++++++++ .../tests/basic/testArrayBufferTransfer.js | 28 ++++ js/src/vm/ArrayBufferObject.cpp | 124 +++++++++++++++--- 3 files changed, 193 insertions(+), 20 deletions(-) diff --git a/js/src/jit-test/tests/asm.js/testResize.js b/js/src/jit-test/tests/asm.js/testResize.js index a94a67d14fa..6b3bf162b30 100644 --- a/js/src/jit-test/tests/asm.js/testResize.js +++ b/js/src/jit-test/tests/asm.js/testResize.js @@ -235,6 +235,54 @@ assertEq(get(4), 13); set(BUF_CHANGE_MIN, 262); assertEq(get(BUF_CHANGE_MIN), 0); +if (ArrayBuffer.transfer) { + var buf1 = new ArrayBuffer(BUF_CHANGE_MIN); + var {get, set, changeHeap} = asmLink(m, this, null, buf1); + set(0, 100); + set(BUF_CHANGE_MIN - 4, 101); + set(BUF_CHANGE_MIN, 102); + var buf2 = ArrayBuffer.transfer(buf1); + assertEq(changeHeap(buf2), true); + assertEq(buf1.byteLength, 0); + assertEq(buf2.byteLength, BUF_CHANGE_MIN); + assertEq(get(0), 100); + assertEq(get(BUF_CHANGE_MIN-4), 101); + assertEq(get(BUF_CHANGE_MIN), 0); + assertEq(get(2*BUF_CHANGE_MIN-4), 0); + var buf3 = ArrayBuffer.transfer(buf2, 3*BUF_CHANGE_MIN); + assertEq(changeHeap(buf3), true); + assertEq(buf2.byteLength, 0); + assertEq(buf3.byteLength, 3*BUF_CHANGE_MIN); + assertEq(get(0), 100); + assertEq(get(BUF_CHANGE_MIN-4), 101); + assertEq(get(BUF_CHANGE_MIN), 0); + assertEq(get(2*BUF_CHANGE_MIN), 0); + set(BUF_CHANGE_MIN, 102); + set(2*BUF_CHANGE_MIN, 103); + assertEq(get(BUF_CHANGE_MIN), 102); + assertEq(get(2*BUF_CHANGE_MIN), 103); + var buf4 = ArrayBuffer.transfer(buf3, 2*BUF_CHANGE_MIN); + assertEq(changeHeap(buf4), true); + assertEq(buf3.byteLength, 0); + assertEq(buf4.byteLength, 2*BUF_CHANGE_MIN); + assertEq(get(0), 100); + assertEq(get(BUF_CHANGE_MIN-4), 101); + assertEq(get(BUF_CHANGE_MIN), 102); + assertEq(get(2*BUF_CHANGE_MIN), 0); + var buf5 = ArrayBuffer.transfer(buf4, 3*BUF_CHANGE_MIN); + assertEq(changeHeap(buf5), true); + assertEq(buf4.byteLength, 0); + assertEq(buf5.byteLength, 3*BUF_CHANGE_MIN); + assertEq(get(0), 100); + assertEq(get(BUF_CHANGE_MIN-4), 101); + assertEq(get(BUF_CHANGE_MIN), 102); + assertEq(get(2*BUF_CHANGE_MIN), 0); + var buf6 = ArrayBuffer.transfer(buf5, 0); + assertEq(buf5.byteLength, 0); + assertEq(buf6.byteLength, 0); + assertEq(changeHeap(buf6), false); +} + var buf1 = new ArrayBuffer(BUF_CHANGE_MIN); var buf2 = new ArrayBuffer(BUF_CHANGE_MIN); var m = asmCompile('glob', 'ffis', 'b', USE_ASM + @@ -292,3 +340,16 @@ changeToBuf = buf1; assertEq(test(0), 155); changeToBuf = buf1; assertEq(test(0), 126); + +if (ArrayBuffer.transfer) { + var buf = new ArrayBuffer(BUF_CHANGE_MIN); + new Int32Array(buf)[0] = 3; + var ffi = function() { + var buf2 = ArrayBuffer.transfer(buf, 2*BUF_CHANGE_MIN); + new Int32Array(buf2)[BUF_CHANGE_MIN/4] = 13; + assertEq(changeHeap(buf2), true); + return 1 + } + var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf); + assertEq(test(BUF_CHANGE_MIN), 14); +} diff --git a/js/src/jit-test/tests/basic/testArrayBufferTransfer.js b/js/src/jit-test/tests/basic/testArrayBufferTransfer.js index ef70d263d91..2c2a647018a 100644 --- a/js/src/jit-test/tests/basic/testArrayBufferTransfer.js +++ b/js/src/jit-test/tests/basic/testArrayBufferTransfer.js @@ -1,4 +1,5 @@ load(libdir + "asserts.js"); +load(libdir + "asm.js"); // Currently, ArrayBuffer.transfer is #ifdef NIGHTLY_BUILD. When // ArrayBuffer.transfer is enabled on release, this test should be removed. @@ -83,3 +84,30 @@ test(4, 1000); test(1000, 0); test(1000, 4); test(1000, 1000); + +// asm.js: +function testAsmJS(N1, N2) { + var buf1 = new ArrayBuffer(N1); + asmLink(asmCompile('stdlib', 'ffis', 'buf', USE_ASM + "var i32=new stdlib.Int32Array(buf); function f() {} return f"), this, null, buf1); + var i32 = new Int32Array(buf1); + for (var i = 0; i < i32.length; i+=100) + i32[i] = i; + + var buf2 = XF(buf1, N2); + + assertEq(buf1.byteLength, 0); + assertEq(i32.length, 0); + assertEq(buf2.byteLength, N2); + var i32 = new Int32Array(buf2); + var i = 0; + for (; i < Math.min(N1, N2)/4; i+=100) + assertEq(i32[i], i); + for (; i < i32.length; i+=100) { + assertEq(i32[i], 0); + i32[i] = -i; + } +} +testAsmJS(BUF_MIN, 0); +testAsmJS(BUF_MIN, BUF_MIN); +testAsmJS(BUF_MIN, 2*BUF_MIN); +testAsmJS(2*BUF_MIN, BUF_MIN); diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 9c53004cce2..e92603b9741 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -242,7 +242,103 @@ ArrayBufferObject::fun_isView(JSContext *cx, unsigned argc, Value *vp) return true; } +#ifdef JS_CPU_X64 +static void +ReleaseAsmJSMappedData(void *base) +{ + MOZ_ASSERT(uintptr_t(base) % AsmJSPageSize == 0); +# ifdef XP_WIN + VirtualFree(base, 0, MEM_RELEASE); +# else + munmap(base, AsmJSMappedSize); +# if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE) + // Tell Valgrind/Memcheck to recommence reporting accesses in the + // previously-inaccessible region. + if (AsmJSMappedSize > 0) { + VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, AsmJSMappedSize); + } +# endif +# endif +} +#else +static void +ReleaseAsmJSMappedData(void *base) +{ + MOZ_CRASH("Only x64 has asm.js mapped buffers"); +} +#endif + #ifdef NIGHTLY_BUILD +# ifdef JS_CPU_X64 +static bool +TransferAsmJSMappedBuffer(JSContext *cx, CallArgs args, Handle oldBuffer, + size_t newByteLength) +{ + size_t oldByteLength = oldBuffer->byteLength(); + MOZ_ASSERT(oldByteLength % AsmJSPageSize == 0); + MOZ_ASSERT(newByteLength % AsmJSPageSize == 0); + + ArrayBufferObject::BufferContents stolen = + ArrayBufferObject::stealContents(cx, oldBuffer, /* hasStealableContents = */ true); + if (!stolen) + return false; + + MOZ_ASSERT(stolen.kind() == ArrayBufferObject::ASMJS_MAPPED); + uint8_t *data = stolen.data(); + + if (newByteLength > oldByteLength) { + void *diffStart = data + oldByteLength; + size_t diffLength = newByteLength - oldByteLength; +# ifdef XP_WIN + if (!VirtualAlloc(diffStart, diffLength, MEM_COMMIT, PAGE_READWRITE)) { + ReleaseAsmJSMappedData(data); + js_ReportOutOfMemory(cx); + return false; + } +# else + // To avoid memset, use MAP_FIXED to clobber the newly-accessible pages + // with zero pages. + int flags = MAP_FIXED | MAP_PRIVATE | MAP_ANON; + if (mmap(diffStart, diffLength, PROT_READ | PROT_WRITE, flags, -1, 0) == MAP_FAILED) { + ReleaseAsmJSMappedData(data); + js_ReportOutOfMemory(cx); + return false; + } +# endif + } else if (newByteLength < oldByteLength) { + void *diffStart = data + newByteLength; + size_t diffLength = oldByteLength - newByteLength; +# ifdef XP_WIN + if (!VirtualFree(diffStart, diffLength, MEM_DECOMMIT)) { + ReleaseAsmJSMappedData(data); + js_ReportOutOfMemory(cx); + return false; + } +# else + if (madvise(diffStart, diffLength, MADV_DONTNEED) || + mprotect(diffStart, diffLength, PROT_NONE)) + { + ReleaseAsmJSMappedData(data); + js_ReportOutOfMemory(cx); + return false; + } +# endif + } + + ArrayBufferObject::BufferContents newContents = + ArrayBufferObject::BufferContents::create(data); + + RootedObject newBuffer(cx, ArrayBufferObject::create(cx, newByteLength, newContents)); + if (!newBuffer) { + ReleaseAsmJSMappedData(data); + return false; + } + + args.rval().setObject(*newBuffer); + return true; +} +# endif // defined(JS_CPU_X64) + /* * Experimental implementation of ArrayBuffer.transfer: * https://gist.github.com/andhow/95fb9e49996615764eff @@ -306,6 +402,13 @@ ArrayBufferObject::fun_transfer(JSContext *cx, unsigned argc, Value *vp) return false; newData = nullptr; } else { +# ifdef JS_CPU_X64 + // With a 4gb mapped asm.js buffer, we can simply enable/disable access + // to the delta as long as the requested length is page-sized. + if (oldBuffer->isAsmJSMapped() && (newByteLength % AsmJSPageSize) == 0) + return TransferAsmJSMappedBuffer(cx, args, oldBuffer, newByteLength); +# endif + // Since we try to realloc below, only allow stealing malloc'd buffers. // If !hasMallocedContents, stealContents will malloc a copy which we // can then realloc. @@ -338,7 +441,7 @@ ArrayBufferObject::fun_transfer(JSContext *cx, unsigned argc, Value *vp) args.rval().setObject(*newBuffer); return true; } -#endif +#endif // defined(NIGHTLY_BUILD) /* * new ArrayBuffer(byteLength) @@ -537,25 +640,6 @@ ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle buf return true; } - -static void -ReleaseAsmJSMappedData(void *base) -{ - MOZ_ASSERT(uintptr_t(base) % AsmJSPageSize == 0); -#ifdef XP_WIN - VirtualFree(base, 0, MEM_RELEASE); -#else - munmap(base, AsmJSMappedSize); -# if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE) - // Tell Valgrind/Memcheck to recommence reporting accesses in the - // previously-inaccessible region. - if (AsmJSMappedSize > 0) { - VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, AsmJSMappedSize); - } -# endif -#endif -} - #else // JS_CODEGEN_X64 bool ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle buffer,