author | Ryan VanderMeulen <ryanvm@gmail.com> |
Sat, 30 Aug 2014 22:39:33 -0400 | |
changeset 202721 | 1db35d2c9a2f66db04dc323977fa2aa35ba9db4a |
parent 202720 | 2a6eccc8a67b27c04306b6920c24651ac3f60ee2 (current diff) |
parent 202637 | 7ace84a1570b69bbec068f6ea76daa5427864fb6 (diff) |
child 202722 | 8c9024c845a9282fbb995050c30f3a4bbd036473 |
child 202754 | 7cbf5e0d28f019e300e9a4b345891efb8783f8ef |
child 202771 | e0638ce22c6e8e01bd2ea12246c9eb6c737e4bf6 |
push id | 48464 |
push user | ryanvm@gmail.com |
push date | Sun, 31 Aug 2014 02:53:07 +0000 |
treeherder | mozilla-inbound@1db35d2c9a2f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 34.0a1 |
first release with | nightly linux32
1db35d2c9a2f
/
34.0a1
/
20140831030206
/
files
nightly linux64
1db35d2c9a2f
/
34.0a1
/
20140831030206
/
files
nightly mac
1db35d2c9a2f
/
34.0a1
/
20140831030206
/
files
nightly win32
1db35d2c9a2f
/
34.0a1
/
20140831030206
/
files
nightly win64
1db35d2c9a2f
/
34.0a1
/
20140831030206
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
34.0a1
/
20140831030206
/
pushlog to previous
nightly linux64
34.0a1
/
20140831030206
/
pushlog to previous
nightly mac
34.0a1
/
20140831030206
/
pushlog to previous
nightly win32
34.0a1
/
20140831030206
/
pushlog to previous
nightly win64
34.0a1
/
20140831030206
/
pushlog to previous
|
--- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -545,16 +545,20 @@ GK_ATOM(lowerFirst, "lower-first") GK_ATOM(lowest, "lowest") GK_ATOM(lowsrc, "lowsrc") GK_ATOM(ltr, "ltr") GK_ATOM(lwtheme, "lwtheme") GK_ATOM(lwthemetextcolor, "lwthemetextcolor") GK_ATOM(main, "main") GK_ATOM(map, "map") GK_ATOM(manifest, "manifest") +GK_ATOM(marginBottom, "margin-bottom") +GK_ATOM(marginLeft, "margin-left") +GK_ATOM(marginRight, "margin-right") +GK_ATOM(marginTop, "margin-top") GK_ATOM(marginheight, "marginheight") GK_ATOM(marginwidth, "marginwidth") GK_ATOM(mark, "mark") GK_ATOM(marquee, "marquee") GK_ATOM(match, "match") GK_ATOM(max, "max") GK_ATOM(maxheight, "maxheight") GK_ATOM(maximum_scale, "maximum-scale") @@ -1106,16 +1110,17 @@ GK_ATOM(target, "target") GK_ATOM(targets, "targets") GK_ATOM(tbody, "tbody") GK_ATOM(td, "td") GK_ATOM(_template, "template") GK_ATOM(text_decoration, "text-decoration") GK_ATOM(terminate, "terminate") GK_ATOM(test, "test") GK_ATOM(text, "text") +GK_ATOM(textAlign, "text-align") GK_ATOM(textarea, "textarea") GK_ATOM(textbox, "textbox") GK_ATOM(textnode, "textnode") GK_ATOM(textNodeDirectionalityMap, "textNodeDirectionalityMap") GK_ATOM(tfoot, "tfoot") GK_ATOM(th, "th") GK_ATOM(thead, "thead") GK_ATOM(thumb, "thumb") @@ -2229,20 +2234,16 @@ GK_ATOM(hitregion, "hitregion") GK_ATOM(InlineBlockFrame, "InlineBlockFrame") GK_ATOM(inlinevalue, "inline") GK_ATOM(invalid, "invalid") GK_ATOM(item, "item") GK_ATOM(itemset, "itemset") GK_ATOM(lineNumber, "line-number") GK_ATOM(linkedPanel, "linkedpanel") GK_ATOM(live, "live") -GK_ATOM(marginBottom, "margin-bottom") -GK_ATOM(marginLeft, "margin-left") -GK_ATOM(marginRight, "margin-right") -GK_ATOM(marginTop, "margin-top") GK_ATOM(menuitemcheckbox, "menuitemcheckbox") GK_ATOM(menuitemradio, "menuitemradio") GK_ATOM(mixed, "mixed") GK_ATOM(multiline, "multiline") GK_ATOM(password, "password") GK_ATOM(posinset, "posinset") GK_ATOM(presentation, "presentation") GK_ATOM(progressbar, "progressbar") @@ -2251,17 +2252,16 @@ GK_ATOM(rowgroup, "rowgroup") GK_ATOM(rowheader, "rowheader") GK_ATOM(select1, "select1") GK_ATOM(setsize, "setsize") GK_ATOM(spelling, "spelling") GK_ATOM(spinbutton, "spinbutton") GK_ATOM(status, "status") GK_ATOM(tableCellIndex, "table-cell-index") GK_ATOM(tablist, "tablist") -GK_ATOM(textAlign, "text-align") GK_ATOM(textIndent, "text-indent") GK_ATOM(textInputType, "text-input-type") GK_ATOM(textLineThroughColor, "text-line-through-color") GK_ATOM(textLineThroughStyle, "text-line-through-style") GK_ATOM(textPosition, "text-position") GK_ATOM(textUnderlineColor, "text-underline-color") GK_ATOM(textUnderlineStyle, "text-underline-style") GK_ATOM(timer, "timer")
--- a/content/base/src/nsImageLoadingContent.cpp +++ b/content/base/src/nsImageLoadingContent.cpp @@ -254,25 +254,34 @@ nsImageLoadingContent::OnStopRequest(img // XXXkhuey should this be GetOurCurrentDoc? Decoding if we're not in // the document seems silly. bool startedDecoding = false; nsIDocument* doc = GetOurOwnerDoc(); nsIPresShell* shell = doc ? doc->GetShell() : nullptr; if (shell && shell->IsVisible() && (!shell->DidInitialize() || shell->IsPaintingSuppressed())) { - // If we've gotten a frame and that frame has called FrameCreate and that - // frame has been reflowed then we know that it checked it's own visibility - // so we can trust our visible count and we don't start decode if we are not - // visible. nsIFrame* f = GetOurPrimaryFrame(); - if (!mFrameCreateCalled || !f || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) || - mVisibleCount > 0 || shell->AssumeAllImagesVisible()) { - if (NS_SUCCEEDED(mCurrentRequest->StartDecoding())) { - startedDecoding = true; + // If we haven't gotten a frame yet either we aren't going to (so don't + // bother kicking off a decode), or we will get very soon on the next + // refresh driver tick when it flushes. And it will most likely be a + // specific image type frame (we only create generic (ie inline) type + // frames for images that don't have a size, and since we have all the data + // we should have the size) which will check its own visibility on its + // first reflow. + if (f) { + // If we've gotten a frame and that frame has called FrameCreate and that + // frame has been reflowed then we know that it checked it's own visibility + // so we can trust our visible count and we don't start decode if we are not + // visible. + if (!mFrameCreateCalled || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) || + mVisibleCount > 0 || shell->AssumeAllImagesVisible()) { + if (NS_SUCCEEDED(mCurrentRequest->StartDecoding())) { + startedDecoding = true; + } } } } // We want to give the decoder a chance to find errors. If we haven't found // an error yet and we've started decoding, either from the above // StartDecoding or from some other place, we must only fire these events // after we finish decoding.
--- a/gfx/layers/d3d11/TextureD3D11.cpp +++ b/gfx/layers/d3d11/TextureD3D11.cpp @@ -22,16 +22,20 @@ namespace layers { static DXGI_FORMAT SurfaceFormatToDXGIFormat(gfx::SurfaceFormat aFormat) { switch (aFormat) { case SurfaceFormat::B8G8R8A8: return DXGI_FORMAT_B8G8R8A8_UNORM; case SurfaceFormat::B8G8R8X8: return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::R8G8B8A8: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case SurfaceFormat::R8G8B8X8: + return DXGI_FORMAT_R8G8B8A8_UNORM; case SurfaceFormat::A8: return DXGI_FORMAT_A8_UNORM; default: MOZ_ASSERT(false, "unsupported format"); return DXGI_FORMAT_UNKNOWN; } }
--- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -3380,19 +3380,36 @@ gfxFont::DrawGlyphs(gfxShapedText } gfxPoint pt(ToDeviceUnits(glyphX, aRunParams.devPerApp), ToDeviceUnits(aPt->y, aRunParams.devPerApp)); gfxFloat advanceDevUnits = ToDeviceUnits(advance, aRunParams.devPerApp); gfxFloat height = GetMetrics().maxAscent; gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); + + // If there's a fake-italic skew in effect as part + // of the drawTarget's transform, we need to remove + // this before drawing the hexbox. (Bug 983985) + Matrix oldMat; + if (aFontParams.passedInvMatrix) { + oldMat = aRunParams.dt->GetTransform(); + aRunParams.dt->SetTransform( + *aFontParams.passedInvMatrix * oldMat); + } + gfxFontMissingGlyphs::DrawMissingGlyph( aRunParams.context, glyphRect, details->mGlyphID, aShapedText->GetAppUnitsPerDevUnit()); + + // Restore the matrix, if we modified it before + // drawing the hexbox. + if (aFontParams.passedInvMatrix) { + aRunParams.dt->SetTransform(oldMat); + } } } else { gfxPoint glyphXY(*aPt); glyphXY.x += details->mXOffset; glyphXY.y += details->mYOffset; DrawOneGlyph(details->mGlyphID, advance, &glyphXY, buffer, &emittedGlyphs); }
--- a/js/src/asmjs/AsmJSFrameIterator.cpp +++ b/js/src/asmjs/AsmJSFrameIterator.cpp @@ -351,21 +351,21 @@ js::GenerateAsmJSStackOverflowExit(Macro // value again. Do not update AsmJSFrame::callerFP as it is not necessary in // the non-profiling case (there is no return path from this point) and, in // the profiling case, it is already correct. Register activation = ABIArgGenerator::NonArgReturnReg0; masm.loadAsmJSActivation(activation); masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfFP())); // Prepare the stack for calling C++. - if (unsigned stackDec = StackDecrementForCall(sizeof(AsmJSFrame), ShadowStackSpace)) - masm.subPtr(Imm32(stackDec), StackPointer); + if (uint32_t d = StackDecrementForCall(ABIStackAlignment, sizeof(AsmJSFrame), ShadowStackSpace)) + masm.subPtr(Imm32(d), StackPointer); // No need to restore the stack; the throw stub pops everything. - masm.assertStackAlignment(); + masm.assertStackAlignment(ABIStackAlignment); masm.call(AsmJSImmPtr(AsmJSImm_ReportOverRecursed)); masm.jump(throwLabel); } void js::GenerateAsmJSExitPrologue(MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, Label *begin) {
--- a/js/src/asmjs/AsmJSFrameIterator.h +++ b/js/src/asmjs/AsmJSFrameIterator.h @@ -165,12 +165,11 @@ GenerateAsmJSStackOverflowExit(jit::Macr void GenerateAsmJSExitPrologue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, jit::Label *begin); void GenerateAsmJSExitEpilogue(jit::MacroAssembler &masm, unsigned framePushed, AsmJSExit::Reason reason, jit::Label *profilingReturn); - } // namespace js #endif // asmjs_AsmJSFrameIterator_h
--- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -25,16 +25,17 @@ #endif #include "jscntxt.h" #include "jsmath.h" #include "jsprf.h" #include "jswrapper.h" #include "asmjs/AsmJSModule.h" +#include "builtin/SIMD.h" #include "frontend/BytecodeCompiler.h" #include "jit/Ion.h" #include "jit/JitCommon.h" #ifdef JS_ION_PERF # include "jit/PerfSpewer.h" #endif #include "vm/StringBuffer.h" @@ -92,81 +93,107 @@ GetDataProperty(JSContext *cx, HandleVal if (desc.hasGetterOrSetterObject()) return LinkFail(cx, "property is not a data property"); v.set(desc.value()); return true; } static bool +HasPureCoercion(JSContext *cx, HandleValue v) +{ + if (IsVectorObject<Int32x4>(v) || IsVectorObject<Float32x4>(v)) + return true; + + // Ideally, we'd reject all non-SIMD non-primitives, but Emscripten has a + // bug that generates code that passes functions for some imports. To avoid + // breaking all the code that contains this bug, we make an exception for + // functions that don't have user-defined valueOf or toString, for their + // coercions are not observable and coercion via ToNumber/ToInt32 + // definitely produces NaN/0. We should remove this special case later once + // most apps have been built with newer Emscripten. + jsid toString = NameToId(cx->names().toString); + if (v.toObject().is<JSFunction>() && + HasObjectValueOf(&v.toObject(), cx) && + ClassMethodIsNative(cx, &v.toObject(), &JSFunction::class_, toString, fun_toString)) + { + return true; + } + + return false; +} + +static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { JS_ASSERT(global.which() == AsmJSModule::Global::Variable); - void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); + void *datum = module.globalVarToGlobalDatum(global); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const AsmJSNumLit &lit = global.varInitNumLit(); - const Value &v = lit.value(); switch (lit.which()) { case AsmJSNumLit::Fixnum: case AsmJSNumLit::NegativeInt: case AsmJSNumLit::BigUnsigned: - *(int32_t *)datum = v.toInt32(); + *(int32_t *)datum = lit.scalarValue().toInt32(); break; case AsmJSNumLit::Double: - *(double *)datum = v.toDouble(); + *(double *)datum = lit.scalarValue().toDouble(); break; case AsmJSNumLit::Float: - *(float *)datum = static_cast<float>(v.toDouble()); + *(float *)datum = static_cast<float>(lit.scalarValue().toDouble()); + break; + case AsmJSNumLit::Int32x4: + memcpy(datum, lit.simdValue().asInt32x4(), Simd128DataSize); + break; + case AsmJSNumLit::Float32x4: + memcpy(datum, lit.simdValue().asFloat32x4(), Simd128DataSize); break; case AsmJSNumLit::OutOfRangeInt: MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("OutOfRangeInt isn't valid in the first place"); } break; } + case AsmJSModule::Global::InitImport: { RootedPropertyName field(cx, global.varImportField()); RootedValue v(cx); if (!GetDataProperty(cx, importVal, field, &v)) return false; - if (!v.isPrimitive()) { - // Ideally, we'd reject all non-primitives, but Emscripten has a bug - // that generates code that passes functions for some imports. To - // avoid breaking all the code that contains this bug, we make an - // exception for functions that don't have user-defined valueOf or - // toString, for their coercions are not observable and coercion via - // ToNumber/ToInt32 definitely produces NaN/0. We should remove this - // special case later once most apps have been built with newer - // Emscripten. - jsid toString = NameToId(cx->names().toString); - if (!v.toObject().is<JSFunction>() || - !HasObjectValueOf(&v.toObject(), cx) || - !ClassMethodIsNative(cx, &v.toObject(), &JSFunction::class_, toString, fun_toString)) - { - return LinkFail(cx, "Imported values must be primitives"); - } - } + if (!v.isPrimitive() && !HasPureCoercion(cx, v)) + return LinkFail(cx, "Imported values must be primitives"); + SimdConstant simdConstant; switch (global.varInitCoercion()) { case AsmJS_ToInt32: if (!ToInt32(cx, v, (int32_t *)datum)) return false; break; case AsmJS_ToNumber: if (!ToNumber(cx, v, (double *)datum)) return false; break; case AsmJS_FRound: if (!RoundFloat32(cx, v, (float *)datum)) return false; break; + case AsmJS_ToInt32x4: + if (!ToSimdConstant<Int32x4>(cx, v, &simdConstant)) + return false; + memcpy(datum, simdConstant.asInt32x4(), Simd128DataSize); + break; + case AsmJS_ToFloat32x4: + if (!ToSimdConstant<Float32x4>(cx, v, &simdConstant)) + return false; + memcpy(datum, simdConstant.asFloat32x4(), Simd128DataSize); + break; } break; } } return true; } @@ -236,16 +263,113 @@ ValidateMathBuiltinFunction(JSContext *c } if (!IsNativeFunction(v, native)) return LinkFail(cx, "bad Math.* builtin function"); return true; } +static PropertyName * +SimdTypeToName(JSContext *cx, AsmJSSimdType type) +{ + switch (type) { + case AsmJSSimdType_int32x4: return cx->names().int32x4; + case AsmJSSimdType_float32x4: return cx->names().float32x4; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected SIMD type"); +} + +static X4TypeDescr::Type +AsmJSSimdTypeToTypeDescrType(AsmJSSimdType type) +{ + switch (type) { + case AsmJSSimdType_int32x4: return Int32x4::type; + case AsmJSSimdType_float32x4: return Float32x4::type; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected AsmJSSimdType"); +} + +static bool +ValidateSimdType(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal, + MutableHandleValue out) +{ + RootedValue v(cx); + if (!GetDataProperty(cx, globalVal, cx->names().SIMD, &v)) + return false; + + AsmJSSimdType type; + if (global.which() == AsmJSModule::Global::SimdCtor) + type = global.simdCtorType(); + else + type = global.simdOperationType(); + + RootedPropertyName simdTypeName(cx, SimdTypeToName(cx, type)); + if (!GetDataProperty(cx, v, simdTypeName, &v)) + return false; + + if (!v.isObject()) + return LinkFail(cx, "bad SIMD type"); + + RootedObject x4desc(cx, &v.toObject()); + if (!x4desc->is<X4TypeDescr>()) + return LinkFail(cx, "bad SIMD type"); + + if (AsmJSSimdTypeToTypeDescrType(type) != x4desc->as<X4TypeDescr>().type()) + return LinkFail(cx, "bad SIMD type"); + + out.set(v); + return true; +} + +static bool +ValidateSimdType(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) +{ + RootedValue _(cx); + return ValidateSimdType(cx, global, globalVal, &_); +} + +static bool +ValidateSimdOperation(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) +{ + // SIMD operations are loaded from the SIMD type, so the type must have been + // validated before the operation. + RootedValue v(cx); + JS_ALWAYS_TRUE(ValidateSimdType(cx, global, globalVal, &v)); + + RootedPropertyName opName(cx, global.simdOperationName()); + if (!GetDataProperty(cx, v, opName, &v)) + return false; + + Native native = nullptr; + switch (global.simdOperationType()) { + case AsmJSSimdType_int32x4: + switch (global.simdOperation()) { + case AsmJSSimdOperation_add: native = simd_int32x4_add; break; + case AsmJSSimdOperation_sub: native = simd_int32x4_sub; break; + case AsmJSSimdOperation_mul: + case AsmJSSimdOperation_div: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Mul and div shouldn't have been validated in " + "the first place"); + } + break; + case AsmJSSimdType_float32x4: + switch (global.simdOperation()) { + case AsmJSSimdOperation_add: native = simd_float32x4_add; break; + case AsmJSSimdOperation_sub: native = simd_float32x4_sub; break; + case AsmJSSimdOperation_mul: native = simd_float32x4_mul; break; + case AsmJSSimdOperation_div: native = simd_float32x4_div; break; + } + break; + } + if (!native || !IsNativeFunction(v, native)) + return LinkFail(cx, "bad SIMD.type.* operation"); + return true; +} + static bool ValidateConstant(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) { RootedPropertyName field(cx, global.constantName()); RootedValue v(cx, globalVal); if (global.constantKind() == AsmJSModule::Global::MathConstant) { if (!GetDataProperty(cx, v, cx->names().Math, &v)) @@ -372,16 +496,24 @@ DynamicallyLinkModule(JSContext *cx, Cal case AsmJSModule::Global::MathBuiltinFunction: if (!ValidateMathBuiltinFunction(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateConstant(cx, global, globalVal)) return false; break; + case AsmJSModule::Global::SimdCtor: + if (!ValidateSimdType(cx, global, globalVal)) + return false; + break; + case AsmJSModule::Global::SimdOperation: + if (!ValidateSimdOperation(cx, global, globalVal)) + return false; + break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); module.initGlobalNaN(); @@ -432,24 +564,24 @@ CallAsmJS(JSContext *cx, unsigned argc, 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 8-byte values where each value contains either a coerced int32 - // (in the low word) or double value, with the coercions specified by the - // asm.js signature. The external entry point unpacks this array into the - // system-ABI-specified registers and stack memory and then calls into the - // internal entry point. The return value is stored in the first element of - // the array (which, therefore, must have length >= 1). - - js::Vector<uint64_t, 8> coercedArgs(cx); + // 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). + js::Vector<AsmJSModule::EntryArg, 8> coercedArgs(cx); if (!coercedArgs.resize(Max<size_t>(1, func.numArgs()))) return false; RootedValue v(cx); for (unsigned i = 0; i < func.numArgs(); ++i) { v = i < callArgs.length() ? callArgs[i] : UndefinedValue(); switch (func.argCoercion(i)) { case AsmJS_ToInt32: @@ -459,16 +591,30 @@ CallAsmJS(JSContext *cx, unsigned argc, case AsmJS_ToNumber: if (!ToNumber(cx, v, (double*)&coercedArgs[i])) return false; break; case AsmJS_FRound: if (!RoundFloat32(cx, v, (float *)&coercedArgs[i])) return false; break; + case AsmJS_ToInt32x4: { + SimdConstant simd; + if (!ToSimdConstant<Int32x4>(cx, v, &simd)) + return false; + memcpy(&coercedArgs[i], simd.asInt32x4(), Simd128DataSize); + break; + } + case AsmJS_ToFloat32x4: { + SimdConstant simd; + if (!ToSimdConstant<Float32x4>(cx, v, &simd)) + return false; + memcpy(&coercedArgs[i], simd.asFloat32x4(), Simd128DataSize); + break; + } } } // 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're // about to run with a neutered heap. if (module.maybeHeapBufferObject() && module.maybeHeapBufferObject()->isNeutered()) { @@ -496,26 +642,39 @@ CallAsmJS(JSContext *cx, unsigned argc, // returns a primary type, which is the case for all asm.js exported // functions, the returned value is discarded and an empty object is // returned instead. JSObject *obj = NewBuiltinClassInstance(cx, &JSObject::class_); callArgs.rval().set(ObjectValue(*obj)); return true; } + JSObject *x4obj; switch (func.returnType()) { case AsmJSModule::Return_Void: callArgs.rval().set(UndefinedValue()); break; case AsmJSModule::Return_Int32: callArgs.rval().set(Int32Value(*(int32_t*)&coercedArgs[0])); break; case AsmJSModule::Return_Double: callArgs.rval().set(NumberValue(*(double*)&coercedArgs[0])); break; + case AsmJSModule::Return_Int32x4: + x4obj = CreateSimd<Int32x4>(cx, (int32_t*)&coercedArgs[0]); + if (!x4obj) + return false; + callArgs.rval().set(ObjectValue(*x4obj)); + break; + case AsmJSModule::Return_Float32x4: + x4obj = CreateSimd<Float32x4>(cx, (float*)&coercedArgs[0]); + if (!x4obj) + return false; + callArgs.rval().set(ObjectValue(*x4obj)); + break; } return true; } static JSFunction * NewExportedFunction(JSContext *cx, const AsmJSModule::ExportedFunction &func, HandleObject moduleObj, unsigned exportIndex)
--- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -298,18 +298,18 @@ AsmJSModule::finish(ExclusiveContext *cx uint32_t endAfterCurly = tokenStream.peekTokenPos().end; JS_ASSERT(endBeforeCurly >= srcBodyStart_); JS_ASSERT(endAfterCurly >= srcBodyStart_); pod.srcLength_ = endBeforeCurly - srcStart_; pod.srcLengthWithRightBrace_ = endAfterCurly - srcStart_; // The global data section sits immediately after the executable (and // other) data allocated by the MacroAssembler, so ensure it is - // double-aligned. - pod.codeBytes_ = AlignBytes(masm.bytesNeeded(), sizeof(double)); + // SIMD-aligned. + pod.codeBytes_ = AlignBytes(masm.bytesNeeded(), SimdStackAlignment); // The entire region is allocated via mmap/VirtualAlloc which requires // units of pages. pod.totalBytes_ = AlignBytes(pod.codeBytes_ + globalDataBytes(), AsmJSPageSize); JS_ASSERT(!code_); code_ = AllocateExecutableMemory(cx, pod.totalBytes_); if (!code_) @@ -513,21 +513,21 @@ TryEnablingIon(JSContext *cx, AsmJSModul JSScript *script = fun->nonLazyScript(); if (!script->hasIonScript()) return true; // Currently we can't rectify arguments. Therefore disabling if argc is too low. if (fun->nargs() > size_t(argc)) return true; - // Normally the types should corresond, since we just ran with those types, + // Normally the types should correspond, since we just ran with those types, // but there are reports this is asserting. Therefore doing it as a check, instead of DEBUG only. if (!types::TypeScript::ThisTypes(script)->hasType(types::Type::UndefinedType())) return true; - for(uint32_t i = 0; i < fun->nargs(); i++) { + for (uint32_t i = 0; i < fun->nargs(); i++) { types::StackTypeSet *typeset = types::TypeScript::ArgTypes(script, i); types::Type type = types::Type::DoubleType(); if (!argv[i].isDouble()) type = types::Type::PrimitiveType(argv[i].extractNonDoubleType()); if (!typeset->hasType(type)) return true; }
--- a/js/src/asmjs/AsmJSModule.h +++ b/js/src/asmjs/AsmJSModule.h @@ -22,18 +22,20 @@ #include "mozilla/Maybe.h" #include "mozilla/Move.h" #include "mozilla/PodOperations.h" #include "jsscript.h" #include "asmjs/AsmJSFrameIterator.h" #include "asmjs/AsmJSValidate.h" +#include "builtin/SIMD.h" #include "gc/Marking.h" #include "jit/IonMacroAssembler.h" +#include "jit/IonTypes.h" #ifdef JS_ION_PERF # include "jit/PerfSpewer.h" #endif #include "jit/RegisterSets.h" #include "jit/shared/Assembler-shared.h" #include "vm/TypedArrayObject.h" namespace js { @@ -42,31 +44,49 @@ namespace frontend { class TokenStream; using JS::GenericNaN; // These EcmaScript-defined coercions form the basis of the asm.js type system. enum AsmJSCoercion { AsmJS_ToInt32, AsmJS_ToNumber, - AsmJS_FRound + AsmJS_FRound, + AsmJS_ToInt32x4, + AsmJS_ToFloat32x4 }; // The asm.js spec recognizes this set of builtin Math functions. enum AsmJSMathBuiltinFunction { AsmJSMathBuiltin_sin, AsmJSMathBuiltin_cos, AsmJSMathBuiltin_tan, AsmJSMathBuiltin_asin, AsmJSMathBuiltin_acos, AsmJSMathBuiltin_atan, AsmJSMathBuiltin_ceil, AsmJSMathBuiltin_floor, AsmJSMathBuiltin_exp, AsmJSMathBuiltin_log, AsmJSMathBuiltin_pow, AsmJSMathBuiltin_sqrt, AsmJSMathBuiltin_abs, AsmJSMathBuiltin_atan2, AsmJSMathBuiltin_imul, AsmJSMathBuiltin_fround, AsmJSMathBuiltin_min, AsmJSMathBuiltin_max, AsmJSMathBuiltin_clz32 }; +// Set of known global object SIMD's attributes, i.e. types +enum AsmJSSimdType +{ + AsmJSSimdType_int32x4, + AsmJSSimdType_float32x4 +}; + +// Set of known operations, for a given SIMD type (int32x4, float32x4,...) +enum AsmJSSimdOperation +{ + AsmJSSimdOperation_add, + AsmJSSimdOperation_sub, + AsmJSSimdOperation_mul, + AsmJSSimdOperation_div +}; + // These labels describe positions in the prologue/epilogue of functions while // compiling an AsmJSModule. struct AsmJSFunctionLabels { AsmJSFunctionLabels(jit::Label &entry, jit::Label &overflowExit) : entry(entry), overflowExit(overflowExit) {} jit::Label begin; @@ -93,53 +113,76 @@ class AsmJSNumLit { public: enum Which { Fixnum, NegativeInt, BigUnsigned, Double, Float, + Int32x4, + Float32x4, OutOfRangeInt = -1 }; private: Which which_; - Value value_; + union { + Value scalar_; + jit::SimdConstant simd_; + } value; public: static AsmJSNumLit Create(Which w, Value v) { AsmJSNumLit lit; lit.which_ = w; - lit.value_ = v; + lit.value.scalar_ = v; + JS_ASSERT(!lit.isSimd()); + return lit; + } + + static AsmJSNumLit Create(Which w, jit::SimdConstant c) { + AsmJSNumLit lit; + lit.which_ = w; + lit.value.simd_ = c; + JS_ASSERT(lit.isSimd()); return lit; } Which which() const { return which_; } int32_t toInt32() const { JS_ASSERT(which_ == Fixnum || which_ == NegativeInt || which_ == BigUnsigned); - return value_.toInt32(); + return value.scalar_.toInt32(); } double toDouble() const { JS_ASSERT(which_ == Double); - return value_.toDouble(); + return value.scalar_.toDouble(); } float toFloat() const { JS_ASSERT(which_ == Float); - return float(value_.toDouble()); + return float(value.scalar_.toDouble()); + } + + Value scalarValue() const { + JS_ASSERT(which_ != OutOfRangeInt); + return value.scalar_; } - Value value() const { - JS_ASSERT(which_ != OutOfRangeInt); - return value_; + bool isSimd() const { + return which_ == Int32x4 || which_ == Float32x4; + } + + const jit::SimdConstant &simdValue() const { + JS_ASSERT(isSimd()); + return value.simd_; } bool hasType() const { return which_ != OutOfRangeInt; } }; // An asm.js module represents the collection of functions nested inside a @@ -153,17 +196,18 @@ class AsmJSNumLit // // NB: this means that AsmJSModule must be GC-safe. class AsmJSModule { public: class Global { public: - enum Which { Variable, FFI, ArrayView, MathBuiltinFunction, Constant }; + enum Which { Variable, FFI, ArrayView, MathBuiltinFunction, Constant, + SimdCtor, SimdOperation}; enum VarInitKind { InitConstant, InitImport }; enum ConstantKind { GlobalConstant, MathConstant }; private: struct Pod { Which which_; union { struct { @@ -172,16 +216,21 @@ class AsmJSModule union { AsmJSCoercion coercion_; AsmJSNumLit numLit_; } u; } var; uint32_t ffiIndex_; Scalar::Type viewType_; AsmJSMathBuiltinFunction mathBuiltinFunc_; + AsmJSSimdType simdCtorType_; + struct { + AsmJSSimdType type_; + AsmJSSimdOperation which_; + } simdOp; struct { ConstantKind kind_; double value_; } constant; } u; } pod; PropertyName *name_; @@ -192,17 +241,17 @@ class AsmJSModule name_ = name; JS_ASSERT_IF(name_, name_->isTenured()); } void trace(JSTracer *trc) { if (name_) MarkStringUnbarriered(trc, &name_, "asm.js global name"); JS_ASSERT_IF(pod.which_ == Variable && pod.u.var.initKind_ == InitConstant, - !pod.u.var.u.numLit_.value().isMarkable()); + !pod.u.var.u.numLit_.scalarValue().isMarkable()); } public: Global() {} Which which() const { return pod.which_; } uint32_t varIndex() const { @@ -247,16 +296,36 @@ class AsmJSModule PropertyName *mathName() const { JS_ASSERT(pod.which_ == MathBuiltinFunction); return name_; } AsmJSMathBuiltinFunction mathBuiltinFunction() const { JS_ASSERT(pod.which_ == MathBuiltinFunction); return pod.u.mathBuiltinFunc_; } + AsmJSSimdType simdCtorType() const { + JS_ASSERT(pod.which_ == SimdCtor); + return pod.u.simdCtorType_; + } + PropertyName *simdCtorName() const { + JS_ASSERT(pod.which_ == SimdCtor); + return name_; + } + PropertyName *simdOperationName() const { + JS_ASSERT(pod.which_ == SimdOperation); + return name_; + } + AsmJSSimdOperation simdOperation() const { + JS_ASSERT(pod.which_ == SimdOperation); + return pod.u.simdOp.which_; + } + AsmJSSimdType simdOperationType() const { + JS_ASSERT(pod.which_ == SimdOperation); + return pod.u.simdOp.type_; + } PropertyName *constantName() const { JS_ASSERT(pod.which_ == Constant); return name_; } ConstantKind constantKind() const { JS_ASSERT(pod.which_ == Constant); return pod.u.constant.kind_; } @@ -305,30 +374,36 @@ class AsmJSModule ionCodeOffset_ = masm.actualOffset(ionCodeOffset_); } 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, Exit *out) const; }; - typedef int32_t (*CodePtr)(uint64_t *args, uint8_t *global); + + struct EntryArg { + uint64_t lo; + uint64_t hi; + }; + JS_STATIC_ASSERT(sizeof(EntryArg) >= jit::Simd128DataSize); + typedef int32_t (*CodePtr)(EntryArg *args, uint8_t *global); // An Exit holds bookkeeping information about an exit; the ExitDatum // struct overlays the actual runtime data stored in the global data // section. struct ExitDatum { uint8_t *exit; HeapPtrFunction fun; }; typedef Vector<AsmJSCoercion, 0, SystemAllocPolicy> ArgCoercionVector; - enum ReturnType { Return_Int32, Return_Double, Return_Void }; + enum ReturnType { Return_Int32, Return_Double, Return_Int32x4, Return_Float32x4, Return_Void }; class ExportedFunction { PropertyName *name_; PropertyName *maybeFieldName_; ArgCoercionVector argCoercions_; struct Pod { ReturnType returnType_; @@ -668,17 +743,18 @@ 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 numGlobalVars_; + uint32_t numGlobalScalarVars_; + uint32_t numGlobalSimdVars_; uint32_t numFFIs_; uint32_t srcLength_; uint32_t srcLengthWithRightBrace_; bool strict_; bool hasArrayView_; bool usesSignalHandlers_; } pod; @@ -815,30 +891,53 @@ class AsmJSModule PropertyName *importArgumentName() const { return importArgumentName_; } PropertyName *bufferArgumentName() const { return bufferArgumentName_; } bool addGlobalVarInit(const AsmJSNumLit &lit, uint32_t *globalIndex) { JS_ASSERT(!isFinishedWithModulePrologue()); - if (pod.numGlobalVars_ == UINT32_MAX) - return false; Global g(Global::Variable, nullptr); g.pod.u.var.initKind_ = Global::InitConstant; g.pod.u.var.u.numLit_ = lit; - g.pod.u.var.index_ = *globalIndex = pod.numGlobalVars_++; + + if (lit.isSimd()) { + if (pod.numGlobalSimdVars_ == UINT32_MAX) + return false; + *globalIndex = pod.numGlobalSimdVars_++; + } else { + if (pod.numGlobalScalarVars_ == UINT32_MAX) + return false; + *globalIndex = pod.numGlobalScalarVars_++; + } + + g.pod.u.var.index_ = *globalIndex; return globals_.append(g); } + static bool IsSimdCoercion(AsmJSCoercion c) { + switch (c) { + case AsmJS_ToInt32: + case AsmJS_ToNumber: + case AsmJS_FRound: + return false; + case AsmJS_ToInt32x4: + case AsmJS_ToFloat32x4: + return true; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected AsmJSCoercion"); + } bool addGlobalVarImport(PropertyName *name, AsmJSCoercion coercion, uint32_t *globalIndex) { JS_ASSERT(!isFinishedWithModulePrologue()); Global g(Global::Variable, name); g.pod.u.var.initKind_ = Global::InitImport; g.pod.u.var.u.coercion_ = coercion; - g.pod.u.var.index_ = *globalIndex = pod.numGlobalVars_++; + *globalIndex = IsSimdCoercion(coercion) ? pod.numGlobalSimdVars_++ + : pod.numGlobalScalarVars_++; + g.pod.u.var.index_ = *globalIndex; return globals_.append(g); } bool addFFI(PropertyName *field, uint32_t *ffiIndex) { JS_ASSERT(!isFinishedWithModulePrologue()); if (pod.numFFIs_ == UINT32_MAX) return false; Global g(Global::FFI, field); g.pod.u.ffiIndex_ = *ffiIndex = pod.numFFIs_++; @@ -859,16 +958,27 @@ class AsmJSModule } bool addMathBuiltinConstant(double value, PropertyName *field) { JS_ASSERT(!isFinishedWithModulePrologue()); Global g(Global::Constant, field); g.pod.u.constant.value_ = value; g.pod.u.constant.kind_ = Global::MathConstant; return globals_.append(g); } + bool addSimdCtor(AsmJSSimdType type, PropertyName *field) { + Global g(Global::SimdCtor, field); + g.pod.u.simdCtorType_ = type; + return globals_.append(g); + } + bool addSimdOperation(AsmJSSimdType type, AsmJSSimdOperation op, PropertyName *field) { + Global g(Global::SimdOperation, field); + g.pod.u.simdOp.type_ = type; + g.pod.u.simdOp.which_ = op; + return globals_.append(g); + } bool addGlobalConstant(double value, PropertyName *name) { JS_ASSERT(!isFinishedWithModulePrologue()); Global g(Global::Constant, name); g.pod.u.constant.value_ = value; g.pod.u.constant.kind_ = Global::GlobalConstant; return globals_.append(g); } unsigned numGlobals() const { @@ -1105,37 +1215,43 @@ class AsmJSModule // null if no heap access was found. const jit::AsmJSHeapAccess *lookupHeapAccess(void *pc) const; // The global data section is placed after the executable code (i.e., at // offset codeBytes_) in the module's linear allocation. The global data // are laid out in this order: // 0. a pointer to the current AsmJSActivation // 1. a pointer to the heap that was linked to the module - // 2. the double float constant NaN. - // 3. the float32 constant NaN, padded to sizeof(double). - // 4. global variable state (elements are sizeof(uint64_t)) - // 5. interleaved function-pointer tables and exits. These are allocated + // 2. the double float constant NaN + // 3. the float32 constant NaN, padded to Simd128DataSize + // 4. global SIMD variable state (elements are Simd128DataSize) + // 5. global variable state (elements are sizeof(uint64_t)) + // 6. interleaved function-pointer tables and exits. These are allocated // while type checking function bodies (as exits and uses of // function-pointer tables are encountered). size_t offsetOfGlobalData() const { JS_ASSERT(isFinished()); return pod.codeBytes_; } uint8_t *globalData() const { JS_ASSERT(isFinished()); return code_ + offsetOfGlobalData(); } + size_t globalSimdVarsOffset() const { + return AlignBytes(/* 0 */ sizeof(void*) + + /* 1 */ sizeof(void*) + + /* 2 */ sizeof(double) + + /* 3 */ sizeof(float), + jit::Simd128DataSize); + } size_t globalDataBytes() const { - return sizeof(void*) + - sizeof(void*) + - sizeof(double) + - sizeof(double) + - pod.numGlobalVars_ * sizeof(uint64_t) + - pod.funcPtrTableAndExitBytes_; + return globalSimdVarsOffset() + + /* 4 */ pod.numGlobalSimdVars_ * jit::Simd128DataSize + + /* 5 */ pod.numGlobalScalarVars_ * sizeof(uint64_t) + + /* 6 */ pod.funcPtrTableAndExitBytes_; } static unsigned activationGlobalDataOffset() { JS_STATIC_ASSERT(jit::AsmJSActivationGlobalDataOffset == 0); return 0; } AsmJSActivation *&activation() const { return *(AsmJSActivation**)(globalData() + activationGlobalDataOffset()); } @@ -1160,30 +1276,49 @@ class AsmJSModule return nan64GlobalDataOffset() + sizeof(double); } void initGlobalNaN() { MOZ_ASSERT(jit::AsmJSNaN64GlobalDataOffset == nan64GlobalDataOffset()); MOZ_ASSERT(jit::AsmJSNaN32GlobalDataOffset == nan32GlobalDataOffset()); *(double *)(globalData() + nan64GlobalDataOffset()) = GenericNaN(); *(float *)(globalData() + nan32GlobalDataOffset()) = GenericNaN(); } - unsigned globalVariableOffset() const { - static_assert((2 * sizeof(void*) + 2 * sizeof(double)) % sizeof(double) == 0, - "Global data should be aligned"); - return 2 * sizeof(void*) + 2 * sizeof(double); + unsigned globalSimdVarIndexToGlobalDataOffset(unsigned i) const { + JS_ASSERT(isFinishedWithModulePrologue()); + JS_ASSERT(i < pod.numGlobalSimdVars_); + return globalSimdVarsOffset() + + i * jit::Simd128DataSize; } - unsigned globalVarIndexToGlobalDataOffset(unsigned i) const { + unsigned globalScalarVarIndexToGlobalDataOffset(unsigned i) const { JS_ASSERT(isFinishedWithModulePrologue()); - JS_ASSERT(i < pod.numGlobalVars_); - return globalVariableOffset() + + JS_ASSERT(i < pod.numGlobalScalarVars_); + return globalSimdVarsOffset() + + pod.numGlobalSimdVars_ * jit::Simd128DataSize + i * sizeof(uint64_t); } - void *globalVarIndexToGlobalDatum(unsigned i) const { + void *globalScalarVarIndexToGlobalDatum(unsigned i) const { + JS_ASSERT(isFinished()); + return (void *)(globalData() + globalScalarVarIndexToGlobalDataOffset(i)); + } + void *globalSimdVarIndexToGlobalDatum(unsigned i) const { JS_ASSERT(isFinished()); - return (void *)(globalData() + globalVarIndexToGlobalDataOffset(i)); + return (void *)(globalData() + globalSimdVarIndexToGlobalDataOffset(i)); + } + void *globalVarToGlobalDatum(const Global &g) const { + unsigned index = g.varIndex(); + if (g.varInitKind() == Global::VarInitKind::InitConstant) { + return g.varInitNumLit().isSimd() + ? globalSimdVarIndexToGlobalDatum(index) + : globalScalarVarIndexToGlobalDatum(index); + } + + JS_ASSERT(g.varInitKind() == Global::VarInitKind::InitImport); + return IsSimdCoercion(g.varInitCoercion()) + ? globalSimdVarIndexToGlobalDatum(index) + : globalScalarVarIndexToGlobalDatum(index); } uint8_t **globalDataOffsetToFuncPtrTable(unsigned globalDataOffset) const { JS_ASSERT(isFinished()); JS_ASSERT(globalDataOffset < globalDataBytes()); return (uint8_t **)(globalData() + globalDataOffset); } unsigned exitIndexToGlobalDataOffset(unsigned exitIndex) const { JS_ASSERT(isFinishedWithModulePrologue());
--- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -27,16 +27,17 @@ #include "jsmath.h" #include "jsprf.h" #include "prmjtime.h" #include "asmjs/AsmJSLink.h" #include "asmjs/AsmJSModule.h" #include "asmjs/AsmJSSignalHandlers.h" +#include "builtin/SIMD.h" #include "frontend/Parser.h" #include "jit/CodeGenerator.h" #include "jit/CompileWrappers.h" #include "jit/MIR.h" #include "jit/MIRGraph.h" #ifdef JS_ION_PERF # include "jit/PerfSpewer.h" #endif @@ -382,37 +383,50 @@ class Type { public: enum Which { Fixnum = AsmJSNumLit::Fixnum, Signed = AsmJSNumLit::NegativeInt, Unsigned = AsmJSNumLit::BigUnsigned, Double = AsmJSNumLit::Double, Float = AsmJSNumLit::Float, + Int32x4 = AsmJSNumLit::Int32x4, + Float32x4 = AsmJSNumLit::Float32x4, MaybeDouble, MaybeFloat, Floatish, Int, Intish, Void }; private: Which which_; public: Type() : which_(Which(-1)) {} static Type Of(const AsmJSNumLit &lit) { JS_ASSERT(lit.hasType()); - JS_ASSERT(Type::Which(lit.which()) >= Fixnum && Type::Which(lit.which()) <= Float); + JS_ASSERT(Type::Which(lit.which()) >= Fixnum && Type::Which(lit.which()) <= Float32x4); Type t; t.which_ = Type::Which(lit.which()); return t; } MOZ_IMPLICIT Type(Which w) : which_(w) {} + MOZ_IMPLICIT Type(AsmJSSimdType type) { + switch (type) { + case AsmJSSimdType_int32x4: + which_ = Int32x4; + return; + case AsmJSSimdType_float32x4: + which_ = Float32x4; + return; + } + MOZ_CRASH("unexpected AsmJSSimdType"); + } bool operator==(Type rhs) const { return which_ == rhs.which_; } bool operator!=(Type rhs) const { return which_ != rhs.which_; } bool isSigned() const { return which_ == Signed || which_ == Fixnum; } @@ -451,18 +465,30 @@ class Type bool isVoid() const { return which_ == Void; } bool isExtern() const { return isDouble() || isSigned(); } + bool isInt32x4() const { + return which_ == Int32x4; + } + + bool isFloat32x4() const { + return which_ == Float32x4; + } + + bool isSimd() const { + return isInt32x4() || isFloat32x4(); + } + bool isVarType() const { - return isInt() || isDouble() || isFloat(); + return isInt() || isDouble() || isFloat() || isSimd(); } MIRType toMIRType() const { switch (which_) { case Double: case MaybeDouble: return MIRType_Double; case Float: @@ -470,34 +496,88 @@ class Type case MaybeFloat: return MIRType_Float32; case Fixnum: case Int: case Signed: case Unsigned: case Intish: return MIRType_Int32; + case Int32x4: + return MIRType_Int32x4; + case Float32x4: + return MIRType_Float32x4; case Void: return MIRType_None; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid Type"); } + Type simdToScalarType() const { + JS_ASSERT(isSimd()); + switch (which_) { + case Int32x4: + return Int; + case Float32x4: + return Float; + // Scalar types + case Double: + case MaybeDouble: + case Float: + case MaybeFloat: + case Floatish: + case Fixnum: + case Int: + case Signed: + case Unsigned: + case Intish: + case Void: + break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid SIMD Type"); + } + + AsmJSSimdType simdToSimdType() const { + JS_ASSERT(isSimd()); + switch (which_) { + case Int32x4: + return AsmJSSimdType_int32x4; + case Float32x4: + return AsmJSSimdType_float32x4; + // Scalar types + case Double: + case MaybeDouble: + case Float: + case MaybeFloat: + case Floatish: + case Fixnum: + case Int: + case Signed: + case Unsigned: + case Intish: + case Void: + break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid SIMD Type"); + } + const char *toChars() const { switch (which_) { case Double: return "double"; case MaybeDouble: return "double?"; case Float: return "float"; case Floatish: return "floatish"; case MaybeFloat: return "float?"; case Fixnum: return "fixnum"; case Int: return "int"; case Signed: return "signed"; case Unsigned: return "unsigned"; case Intish: return "intish"; + case Int32x4: return "int32x4"; + case Float32x4: return "float32x4"; case Void: return "void"; } MOZ_CRASH("Invalid Type"); } }; } /* anonymous namespace */ @@ -505,53 +585,61 @@ class Type // function. class RetType { public: enum Which { Void = Type::Void, Signed = Type::Signed, Double = Type::Double, - Float = Type::Float + Float = Type::Float, + Int32x4 = Type::Int32x4, + Float32x4 = Type::Float32x4 }; private: Which which_; public: RetType() : which_(Which(-1)) {} MOZ_IMPLICIT RetType(Which w) : which_(w) {} MOZ_IMPLICIT RetType(AsmJSCoercion coercion) { switch (coercion) { case AsmJS_ToInt32: which_ = Signed; break; case AsmJS_ToNumber: which_ = Double; break; case AsmJS_FRound: which_ = Float; break; + case AsmJS_ToInt32x4: which_ = Int32x4; break; + case AsmJS_ToFloat32x4: which_ = Float32x4; break; } } Which which() const { return which_; } Type toType() const { return Type::Which(which_); } AsmJSModule::ReturnType toModuleReturnType() const { switch (which_) { case Void: return AsmJSModule::Return_Void; case Signed: return AsmJSModule::Return_Int32; case Float: // will be converted to a Double case Double: return AsmJSModule::Return_Double; + case Int32x4: return AsmJSModule::Return_Int32x4; + case Float32x4: return AsmJSModule::Return_Float32x4; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected return type"); } MIRType toMIRType() const { switch (which_) { case Void: return MIRType_None; case Signed: return MIRType_Int32; case Double: return MIRType_Double; case Float: return MIRType_Float32; + case Int32x4: return MIRType_Int32x4; + case Float32x4: return MIRType_Float32x4; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected return type"); } bool operator==(RetType rhs) const { return which_ == rhs.which_; } bool operator!=(RetType rhs) const { return which_ != rhs.which_; } }; // Represents the subset of Type that can be used as a return type of a builtin @@ -607,32 +695,36 @@ namespace { // RetType, the result is Signed since callers (asm.js and non-asm.js) can // rely on the return value being Signed. class VarType { public: enum Which { Int = Type::Int, Double = Type::Double, - Float = Type::Float + Float = Type::Float, + Int32x4 = Type::Int32x4, + Float32x4 = Type::Float32x4 }; private: Which which_; public: VarType() : which_(Which(-1)) {} MOZ_IMPLICIT VarType(Which w) : which_(w) {} MOZ_IMPLICIT VarType(AsmJSCoercion coercion) { switch (coercion) { case AsmJS_ToInt32: which_ = Int; break; case AsmJS_ToNumber: which_ = Double; break; case AsmJS_FRound: which_ = Float; break; + case AsmJS_ToInt32x4: which_ = Int32x4; break; + case AsmJS_ToFloat32x4: which_ = Float32x4; break; } } static VarType Of(const AsmJSNumLit &lit) { JS_ASSERT(lit.hasType()); VarType v; switch (lit.which()) { case AsmJSNumLit::Fixnum: case AsmJSNumLit::NegativeInt: @@ -640,67 +732,84 @@ class VarType v.which_ = Int; return v; case AsmJSNumLit::Double: v.which_ = Double; return v; case AsmJSNumLit::Float: v.which_ = Float; return v; + case AsmJSNumLit::Int32x4: + v.which_ = Int32x4; + return v; + case AsmJSNumLit::Float32x4: + v.which_ = Float32x4; + return v; case AsmJSNumLit::OutOfRangeInt: MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("can't be out of range int"); } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected literal type"); } Which which() const { return which_; } Type toType() const { return Type::Which(which_); } MIRType toMIRType() const { switch(which_) { - case Int: return MIRType_Int32; - case Double: return MIRType_Double; - case Float: return MIRType_Float32; + case Int: return MIRType_Int32; + case Double: return MIRType_Double; + case Float: return MIRType_Float32; + case Int32x4: return MIRType_Int32x4; + case Float32x4: return MIRType_Float32x4; } - MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, Double or Float"); + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, SIMD, Double or Float"); } AsmJSCoercion toCoercion() const { switch(which_) { - case Int: return AsmJS_ToInt32; - case Double: return AsmJS_ToNumber; - case Float: return AsmJS_FRound; + case Int: return AsmJS_ToInt32; + case Double: return AsmJS_ToNumber; + case Float: return AsmJS_FRound; + case Int32x4: return AsmJS_ToInt32x4; + case Float32x4: return AsmJS_ToFloat32x4; } - MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, Double or Float"); + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, SIMD, Double or Float"); } static VarType FromCheckedType(Type type) { - JS_ASSERT(type.isInt() || type.isMaybeDouble() || type.isFloatish()); + JS_ASSERT(type.isInt() || type.isMaybeDouble() || type.isFloatish() || type.isSimd()); if (type.isMaybeDouble()) return Double; else if (type.isFloatish()) return Float; - else + else if (type.isInt()) return Int; + else if (type.isInt32x4()) + return Int32x4; + else if (type.isFloat32x4()) + return Float32x4; + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unknown type in FromCheckedType"); } bool operator==(VarType rhs) const { return which_ == rhs.which_; } bool operator!=(VarType rhs) const { return which_ != rhs.which_; } }; } /* anonymous namespace */ // Implements <: (subtype) operator when the rhs is a VarType static inline bool operator<=(Type lhs, VarType rhs) { switch (rhs.which()) { - case VarType::Int: return lhs.isInt(); - case VarType::Double: return lhs.isDouble(); - case VarType::Float: return lhs.isFloat(); + case VarType::Int: return lhs.isInt(); + case VarType::Double: return lhs.isDouble(); + case VarType::Float: return lhs.isFloat(); + case VarType::Int32x4: return lhs.isInt32x4(); + case VarType::Float32x4: return lhs.isFloat32x4(); } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected rhs type"); } /*****************************************************************************/ static inline MIRType ToMIRType(MIRType t) { return t; } static inline MIRType ToMIRType(VarType t) { return t.toMIRType(); } @@ -930,32 +1039,39 @@ class MOZ_STACK_CLASS ModuleCompiler enum Which { Variable, ConstantLiteral, ConstantImport, Function, FuncPtrTable, FFI, ArrayView, - MathBuiltinFunction + MathBuiltinFunction, + SimdCtor, + SimdOperation }; private: Which which_; union { struct { VarType::Which type_; uint32_t index_; AsmJSNumLit literalValue_; } varOrConst; uint32_t funcIndex_; uint32_t funcPtrTableIndex_; uint32_t ffiIndex_; Scalar::Type viewType_; AsmJSMathBuiltinFunction mathBuiltinFunc_; + AsmJSSimdType simdCtorType_; + struct { + AsmJSSimdType type_; + AsmJSSimdOperation which_; + } simdOp; } u; friend class ModuleCompiler; friend class js::LifoAlloc; explicit Global(Which which) : which_(which) {} public: @@ -995,16 +1111,34 @@ class MOZ_STACK_CLASS ModuleCompiler } bool isMathFunction() const { return which_ == MathBuiltinFunction; } AsmJSMathBuiltinFunction mathBuiltinFunction() const { JS_ASSERT(which_ == MathBuiltinFunction); return u.mathBuiltinFunc_; } + bool isSimdCtor() const { + return which_ == SimdCtor; + } + AsmJSSimdType simdCtorType() const { + JS_ASSERT(which_ == SimdCtor); + return u.simdCtorType_; + } + bool isSimdOperation() const { + return which_ == SimdOperation; + } + AsmJSSimdOperation simdOperation() const { + JS_ASSERT(which_ == SimdOperation); + return u.simdOp.which_; + } + AsmJSSimdType simdOperationType() const { + JS_ASSERT(which_ == SimdOperation); + return u.simdOp.type_; + } }; typedef Vector<const Func*> FuncPtrVector; class FuncPtrTable { Signature sig_; uint32_t mask_; @@ -1096,16 +1230,17 @@ class MOZ_STACK_CLASS ModuleCompiler PropertyName *name; unsigned ms; unsigned line; unsigned column; }; 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; ExclusiveContext * cx_; AsmJSParser & parser_; @@ -1116,63 +1251,73 @@ class MOZ_STACK_CLASS ModuleCompiler ParseNode * moduleFunctionNode_; PropertyName * moduleFunctionName_; GlobalMap globals_; FuncVector functions_; FuncPtrTableVector funcPtrTables_; 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 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); } bool addStandardLibraryMathName(const char *name, double cst) { JSAtom *atom = Atomize(cx_, name, strlen(name)); if (!atom) return false; MathBuiltin builtin(cst); return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin); } + bool addStandardLibrarySimdOpName(const char *name, AsmJSSimdOperation op) { + JSAtom *atom = Atomize(cx_, name, strlen(name)); + if (!atom) + return false; + return standardLibrarySimdOpNames_.putNew(atom->asPropertyName(), op); + } public: ModuleCompiler(ExclusiveContext *cx, AsmJSParser &parser) : cx_(cx), parser_(parser), masm_(MacroAssembler::AsmJSToken()), moduleLifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE), moduleFunctionNode_(parser.pc->maybeFunction), moduleFunctionName_(nullptr), globals_(cx), functions_(cx), funcPtrTables_(cx), exits_(cx), standardLibraryMathNames_(cx), + standardLibrarySimdOpNames_(cx), errorString_(nullptr), errorOffset_(UINT32_MAX), errorOverRecursed_(false), usecBefore_(PRMJ_Now()), slowFunctions_(cx), - finishedFunctionBodies_(false) + finishedFunctionBodies_(false), + supportsSimd_(cx->jitSupportsSimd()) { JS_ASSERT(moduleFunctionNode_->pn_funbox == parser.pc->sc->asFunctionBox()); } ~ModuleCompiler() { if (errorString_) { JS_ASSERT(errorOffset_ != UINT32_MAX); tokenStream().reportAsmJSError(errorOffset_, @@ -1214,16 +1359,25 @@ class MOZ_STACK_CLASS ModuleCompiler !addStandardLibraryMathName("LOG10E", M_LOG10E) || !addStandardLibraryMathName("PI", M_PI) || !addStandardLibraryMathName("SQRT1_2", M_SQRT1_2) || !addStandardLibraryMathName("SQRT2", M_SQRT2)) { return false; } + if (!standardLibrarySimdOpNames_.init() || + !addStandardLibrarySimdOpName("add", AsmJSSimdOperation_add) || + !addStandardLibrarySimdOpName("sub", AsmJSSimdOperation_sub) || + !addStandardLibrarySimdOpName("mul", AsmJSSimdOperation_mul) || + !addStandardLibrarySimdOpName("div", AsmJSSimdOperation_div)) + { + return false; + } + uint32_t srcStart = parser_.pc->maybeFunction->pn_body->pn_pos.begin; uint32_t srcBodyStart = tokenStream().currentToken().pos.end; // "use strict" should be added to the source if we are in an implicit // strict context, see also comment above addUseStrict in // js::FunctionToString. bool strict = parser_.pc->sc->strict && !parser_.pc->sc->hasExplicitUseStrict(); module_ = cx_->new_<AsmJSModule>(parser_.ss, srcStart, srcBodyStart, strict, @@ -1295,16 +1449,17 @@ class MOZ_STACK_CLASS ModuleCompiler Label &stackOverflowLabel() { return stackOverflowLabel_; } Label &asyncInterruptLabel() { return asyncInterruptLabel_; } Label &syncInterruptLabel() { return syncInterruptLabel_; } bool hasError() const { return errorString_ != nullptr; } const AsmJSModule &module() const { return *module_.get(); } uint32_t srcStart() const { return module_->srcStart(); } bool usesSignalHandlersForInterrupt() const { return module_->usesSignalHandlersForInterrupt(); } bool usesSignalHandlersForOOB() const { return module_->usesSignalHandlersForOOB(); } + bool supportsSimd() const { return supportsSimd_; } ParseNode *moduleFunctionNode() const { return moduleFunctionNode_; } PropertyName *moduleFunctionName() const { return moduleFunctionName_; } const Global *lookupGlobal(PropertyName *name) const { if (GlobalMap::Ptr p = globals_.lookup(name)) return p->value(); return nullptr; @@ -1331,16 +1486,23 @@ class MOZ_STACK_CLASS ModuleCompiler } bool lookupStandardLibraryMathName(PropertyName *name, MathBuiltin *mathBuiltin) const { if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) { *mathBuiltin = p->value(); return true; } return false; } + bool lookupStandardSimdOpName(PropertyName *name, AsmJSSimdOperation *op) const { + if (SimdOperationNameMap::Ptr p = standardLibrarySimdOpNames_.lookup(name)) { + *op = p->value(); + return true; + } + return false; + } ExitMap::Range allExits() const { return exits_.all(); } /***************************************************** Mutable interface */ void initModuleFunctionName(PropertyName *name) { moduleFunctionName_ = name; } @@ -1434,16 +1596,37 @@ class MOZ_STACK_CLASS ModuleCompiler if (!module_->addMathBuiltinFunction(func, fieldName)) return false; Global *global = moduleLifo_.new_<Global>(Global::MathBuiltinFunction); if (!global) return false; global->u.mathBuiltinFunc_ = func; return globals_.putNew(varName, global); } + bool addSimdCtor(PropertyName *varName, AsmJSSimdType type, PropertyName *fieldName) { + if (!module_->addSimdCtor(type, fieldName)) + return false; + Global *global = moduleLifo_.new_<Global>(Global::SimdCtor); + if (!global) + return false; + global->u.simdCtorType_ = type; + return globals_.putNew(varName, global); + } + bool addSimdOperation(PropertyName *varName, AsmJSSimdType type, AsmJSSimdOperation op, + PropertyName *typeVarName, PropertyName *opName) + { + if (!module_->addSimdOperation(type, op, opName)) + return false; + Global *global = moduleLifo_.new_<Global>(Global::SimdOperation); + if (!global) + return false; + global->u.simdOp.type_ = type; + global->u.simdOp.which_ = op; + return globals_.putNew(varName, global); + } private: bool addGlobalDoubleConstant(PropertyName *varName, double constant) { Global *global = moduleLifo_.new_<Global>(Global::ConstantLiteral); if (!global) return false; global->u.varOrConst.type_ = VarType::Double; global->u.varOrConst.literalValue_ = AsmJSNumLit::Create(AsmJSNumLit::Double, DoubleValue(constant)); @@ -1667,82 +1850,203 @@ IsCallToGlobal(ModuleCompiler &m, ParseN if (!callee->isKind(PNK_NAME)) return false; *global = m.lookupGlobal(callee->name()); return !!*global; } static bool -IsFloatCoercion(ModuleCompiler &m, ParseNode *pn, ParseNode **coercedExpr) +IsCoercionCall(ModuleCompiler &m, ParseNode *pn, AsmJSCoercion *coercion, ParseNode **coercedExpr) { const ModuleCompiler::Global *global; if (!IsCallToGlobal(m, pn, &global)) return false; - if (!global->isMathFunction() || global->mathBuiltinFunction() != AsmJSMathBuiltin_fround) - return false; - if (CallArgListLength(pn) != 1) return false; if (coercedExpr) *coercedExpr = CallArgList(pn); - return true; -} - -static bool -IsNumericFloatLiteral(ModuleCompiler &m, ParseNode *pn) + if (global->isMathFunction() && global->mathBuiltinFunction() == AsmJSMathBuiltin_fround) { + *coercion = AsmJS_FRound; + return true; + } + + if (global->isSimdCtor()) { + switch (global->simdCtorType()) { + case AsmJSSimdType_int32x4: + *coercion = AsmJS_ToInt32x4; + return true; + case AsmJSSimdType_float32x4: + *coercion = AsmJS_ToFloat32x4; + return true; + } + } + + return false; +} + +static bool +IsFloatLiteral(ModuleCompiler &m, ParseNode *pn) { ParseNode *coercedExpr; - if (!IsFloatCoercion(m, pn, &coercedExpr)) - return false; - + AsmJSCoercion coercion; + if (!IsCoercionCall(m, pn, &coercion, &coercedExpr) || coercion != AsmJS_FRound) + return false; return IsNumericNonFloatLiteral(coercedExpr); } +static unsigned +SimdTypeToLength(AsmJSSimdType type) +{ + switch (type) { + case AsmJSSimdType_float32x4: + case AsmJSSimdType_int32x4: + return 4; + } + MOZ_CRASH("unexpected SIMD type"); +} + +static bool +IsSimdTuple(ModuleCompiler &m, ParseNode *pn, AsmJSSimdType *type) +{ + const ModuleCompiler::Global *global; + if (!IsCallToGlobal(m, pn, &global)) + return false; + + if (!global->isSimdCtor()) + return false; + + if (CallArgListLength(pn) != SimdTypeToLength(global->simdCtorType())) + 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)) + return false; + + ParseNode *arg = CallArgList(pn); + unsigned length = SimdTypeToLength(type); + for (unsigned i = 0; i < length; i++) { + if (!IsNumericLiteral(m, arg)) + return false; + + uint32_t _; + switch (type) { + case AsmJSSimdType_int32x4: + if (!IsLiteralInt(m, arg, &_)) + return false; + case AsmJSSimdType_float32x4: + if (!IsNumericNonFloatLiteral(arg)) + return false; + } + + arg = NextNode(arg); + } + + JS_ASSERT(arg == nullptr); + return true; +} + static bool IsNumericLiteral(ModuleCompiler &m, ParseNode *pn) { return IsNumericNonFloatLiteral(pn) || - IsNumericFloatLiteral(m, pn); + IsFloatLiteral(m, pn) || + IsSimdLiteral(m, pn); } // The JS grammar treats -42 as -(42) (i.e., with separate grammar // productions) for the unary - and literal 42). However, the asm.js spec // recognizes -42 (modulo parens, so -(42) and -((42))) as a single literal // so fold the two potential parse nodes into a single double value. static double -ExtractNumericNonFloatValue(ParseNode **pn) -{ - JS_ASSERT(IsNumericNonFloatLiteral(*pn)); - - if ((*pn)->isKind(PNK_NEG)) { - *pn = UnaryKid(*pn); - return -NumberNodeValue(*pn); - } - - return NumberNodeValue(*pn); +ExtractNumericNonFloatValue(ParseNode *pn, ParseNode **out = nullptr) +{ + JS_ASSERT(IsNumericNonFloatLiteral(pn)); + + if (pn->isKind(PNK_NEG)) { + pn = UnaryKid(pn); + if (out) + *out = pn; + return -NumberNodeValue(pn); + } + + return NumberNodeValue(pn); +} + +static AsmJSNumLit +ExtractSimdValue(ModuleCompiler &m, ParseNode *pn) +{ + JS_ASSERT(IsSimdLiteral(m, pn)); + + AsmJSSimdType type; + JS_ALWAYS_TRUE(IsSimdTuple(m, pn, &type)); + + ParseNode *arg = CallArgList(pn); + unsigned length = SimdTypeToLength(type); + switch (type) { + case AsmJSSimdType_int32x4: { + JS_ASSERT(length == 4); + int32_t val[4]; + for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) { + uint32_t u32; + JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = int32_t(u32); + } + JS_ASSERT(arg== nullptr); + return AsmJSNumLit::Create(AsmJSNumLit::Int32x4, SimdConstant::CreateX4(val)); + } + case AsmJSSimdType_float32x4: { + JS_ASSERT(length == 4); + float val[4]; + for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) + val[i] = float(ExtractNumericNonFloatValue(arg)); + JS_ASSERT(arg == nullptr); + return AsmJSNumLit::Create(AsmJSNumLit::Float32x4, SimdConstant::CreateX4(val)); + } + } + + MOZ_CRASH("Unexpected SIMD type."); } static AsmJSNumLit ExtractNumericLiteral(ModuleCompiler &m, ParseNode *pn) { JS_ASSERT(IsNumericLiteral(m, pn)); - // Float literals are explicitly coerced and thus the coerced literal may be - // any valid (non-float) numeric literal. if (pn->isKind(PNK_CALL)) { - pn = CallArgList(pn); - double d = ExtractNumericNonFloatValue(&pn); - return AsmJSNumLit::Create(AsmJSNumLit::Float, DoubleValue(d)); - } - - double d = ExtractNumericNonFloatValue(&pn); + // Float literals are explicitly coerced and thus the coerced literal may be + // any valid (non-float) numeric literal. + if (CallArgListLength(pn) == 1) { + pn = CallArgList(pn); + double d = ExtractNumericNonFloatValue(pn); + return AsmJSNumLit::Create(AsmJSNumLit::Float, DoubleValue(d)); + } + + JS_ASSERT(CallArgListLength(pn) == 4); + return ExtractSimdValue(m, pn); + } + + double d = ExtractNumericNonFloatValue(pn, &pn); // The asm.js spec syntactically distinguishes any literal containing a // decimal point or the literal -0 as having double type. if (NumberNodeHasFrac(pn) || IsNegativeZero(d)) return AsmJSNumLit::Create(AsmJSNumLit::Double, DoubleValue(d)); // The syntactic checks above rule out these double values. JS_ASSERT(!IsNegativeZero(d)); @@ -1779,16 +2083,18 @@ IsLiteralInt(ModuleCompiler &m, ParseNod case AsmJSNumLit::Fixnum: case AsmJSNumLit::BigUnsigned: case AsmJSNumLit::NegativeInt: *u32 = uint32_t(literal.toInt32()); return true; case AsmJSNumLit::Double: case AsmJSNumLit::Float: case AsmJSNumLit::OutOfRangeInt: + case AsmJSNumLit::Int32x4: + case AsmJSNumLit::Float32x4: return false; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal type"); } /*****************************************************************************/ @@ -1949,17 +2255,24 @@ class FunctionCompiler curBlock_->add(ins); curBlock_->initSlot(info().localSlot(i.index()), ins); if (!mirGen_->ensureBallast()) return false; } unsigned firstLocalSlot = argTypes.length(); for (unsigned i = 0; i < varInitializers_.length(); i++) { AsmJSNumLit &lit = varInitializers_[i]; - MConstant *ins = MConstant::NewAsmJS(alloc(), lit.value(), Type::Of(lit).toMIRType()); + MIRType type = Type::Of(lit).toMIRType(); + + MInstruction *ins; + if (lit.isSimd()) + ins = MSimdConstant::New(alloc(), lit.simdValue(), type); + else + ins = MConstant::NewAsmJS(alloc(), lit.scalarValue(), type); + curBlock_->add(ins); curBlock_->initSlot(info().localSlot(firstLocalSlot + i), ins); if (!mirGen_->ensureBallast()) return false; } maybeAddInterruptCheck(fn_); return true; } @@ -2000,23 +2313,33 @@ class FunctionCompiler const ModuleCompiler::Global *lookupGlobal(PropertyName *name) const { if (locals_.has(name)) return nullptr; return m_.lookupGlobal(name); } + bool supportsSimd() const { + return m_.supportsSimd(); + } + /***************************** Code generation (after local scope setup) */ MDefinition *constant(const AsmJSNumLit &lit) { if (inDeadCode()) return nullptr; - MConstant *constant = MConstant::NewAsmJS(alloc(), lit.value(), Type::Of(lit).toMIRType()); + + MInstruction *constant; + if (lit.isSimd()) + constant = MSimdConstant::New(alloc(), lit.simdValue(), Type::Of(lit).toMIRType()); + else + constant = MConstant::NewAsmJS(alloc(), lit.scalarValue(), Type::Of(lit).toMIRType()); + curBlock_->add(constant); return constant; } MDefinition *constant(Value v, Type t) { if (inDeadCode()) return nullptr; @@ -2060,16 +2383,29 @@ class FunctionCompiler { if (inDeadCode()) return nullptr; T *ins = T::NewAsmJS(alloc(), lhs, rhs, type); curBlock_->add(ins); return ins; } + MDefinition *binarySimd(MDefinition *lhs, MDefinition *rhs, MSimdBinaryArith::Operation op, + MIRType type) + { + if (inDeadCode()) + return nullptr; + + JS_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); + JS_ASSERT(lhs->type() == type); + MSimdBinaryArith *ins = MSimdBinaryArith::NewAsmJS(alloc(), lhs, rhs, op, type); + curBlock_->add(ins); + return ins; + } + MDefinition *minMax(MDefinition *lhs, MDefinition *rhs, MIRType type, bool isMax) { if (inDeadCode()) return nullptr; MMinMax *ins = MMinMax::New(alloc(), lhs, rhs, type, isMax); curBlock_->add(ins); return ins; } @@ -2157,31 +2493,42 @@ class FunctionCompiler store->setSkipBoundsCheck(true); } MDefinition *loadGlobalVar(const ModuleCompiler::Global &global) { if (inDeadCode()) return nullptr; - uint32_t index = global.varOrConstIndex(); - unsigned globalDataOffset = module().globalVarIndexToGlobalDataOffset(index); MIRType type = global.varOrConstType().toMIRType(); + + unsigned globalDataOffset; + if (IsSimdType(type)) + globalDataOffset = module().globalSimdVarIndexToGlobalDataOffset(global.varOrConstIndex()); + else + globalDataOffset = module().globalScalarVarIndexToGlobalDataOffset(global.varOrConstIndex()); + MAsmJSLoadGlobalVar *load = MAsmJSLoadGlobalVar::New(alloc(), type, globalDataOffset, global.isConst()); curBlock_->add(load); return load; } void storeGlobalVar(const ModuleCompiler::Global &global, MDefinition *v) { if (inDeadCode()) return; JS_ASSERT(!global.isConst()); - unsigned globalDataOffset = module().globalVarIndexToGlobalDataOffset(global.varOrConstIndex()); + + unsigned globalDataOffset; + if (IsSimdType(v->type())) + globalDataOffset = module().globalSimdVarIndexToGlobalDataOffset(global.varOrConstIndex()); + else + globalDataOffset = module().globalScalarVarIndexToGlobalDataOffset(global.varOrConstIndex()); + curBlock_->add(MAsmJSStoreGlobalVar::New(alloc(), globalDataOffset, v)); } void maybeAddInterruptCheck(ParseNode *pn) { if (inDeadCode()) return; @@ -2189,16 +2536,41 @@ class FunctionCompiler return; unsigned lineno = 0, column = 0; m().tokenStream().srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &lineno, &column); CallSiteDesc callDesc(lineno, column, CallSiteDesc::Relative); curBlock_->add(MAsmJSInterruptCheck::New(alloc(), &m().syncInterruptLabel(), callDesc)); } + MDefinition *extractSimdElement(SimdLane lane, MDefinition *base, MIRType type) + { + if (inDeadCode()) + return nullptr; + + JS_ASSERT(IsSimdType(base->type())); + JS_ASSERT(!IsSimdType(type)); + MSimdExtractElement *ins = MSimdExtractElement::NewAsmJS(alloc(), base, type, lane); + curBlock_->add(ins); + return ins; + } + + template<typename T> + MDefinition *constructSimd(MDefinition *x, MDefinition *y, MDefinition *z, MDefinition *w, + MIRType type) + { + if (inDeadCode()) + return nullptr; + + JS_ASSERT(IsSimdType(type)); + T *ins = T::New(alloc(), type, x, y, z, w); + curBlock_->add(ins); + return ins; + } + /***************************************************************** Calls */ // The IonMonkey backend maintains a single stack offset (from the stack // pointer to the base of the frame) by adding the total amount of spill // space required plus the maximum stack required for argument passing. // Since we do not use IonMonkey's MPrepareCall/MPassArg/MCall, we must // manually accumulate, for the entire function, the maximum required stack // space for argument passing. (This is passed to the CodeGenerator via @@ -2278,17 +2650,17 @@ class FunctionCompiler void finishCallArgs(Call *call) { if (inDeadCode()) return; uint32_t parentStackBytes = call->abi_.stackBytesConsumedSoFar(); uint32_t newStackBytes; if (call->childClobbers_) { - call->spIncrement_ = AlignBytes(call->maxChildStackBytes_, StackAlignment); + call->spIncrement_ = AlignBytes(call->maxChildStackBytes_, AsmJSStackAlignment); for (unsigned i = 0; i < call->stackArgs_.length(); i++) call->stackArgs_[i]->incrementOffset(call->spIncrement_); newStackBytes = Max(call->prevMaxStackBytes_, call->spIncrement_ + parentStackBytes); } else { call->spIncrement_ = 0; newStackBytes = Max(call->prevMaxStackBytes_, Max(call->maxChildStackBytes_, parentStackBytes)); @@ -2959,25 +3331,23 @@ CheckTypeAnnotation(ModuleCompiler &m, P } case PNK_POS: { *coercion = AsmJS_ToNumber; if (coercedExpr) *coercedExpr = UnaryKid(coercionNode); return true; } case PNK_CALL: { - *coercion = AsmJS_FRound; - if (!IsFloatCoercion(m, coercionNode, coercedExpr)) - return m.fail(coercionNode, "call must be to fround coercion"); - return true; + if (IsCoercionCall(m, coercionNode, coercion, coercedExpr)) + return true; } default:; } - return m.fail(coercionNode, "must be of the form +x, fround(x) or x|0"); + return m.fail(coercionNode, "must be of the form +x, fround(x), simdType(x) or x|0"); } static bool CheckGlobalVariableImportExpr(ModuleCompiler &m, PropertyName *varName, AsmJSCoercion coercion, ParseNode *coercedExpr, bool isConst) { if (!coercedExpr->isKind(PNK_DOT)) return m.failName(coercedExpr, "invalid import expression for global '%s'", varName); @@ -3051,54 +3421,133 @@ CheckNewArrayView(ModuleCompiler &m, Pro type = Scalar::Float64; else return m.fail(ctorExpr, "could not match typed array name"); return m.addArrayView(varName, type, field); } static bool +IsSimdTypeName(ModuleCompiler &m, PropertyName *name, AsmJSSimdType *type) +{ + if (name == m.cx()->names().int32x4) { + *type = AsmJSSimdType_int32x4; + return true; + } + if (name == m.cx()->names().float32x4) { + *type = AsmJSSimdType_float32x4; + return true; + } + return false; +} + +static bool +IsSimdValidOperationType(AsmJSSimdType type, AsmJSSimdOperation op) +{ + switch (op) { + case AsmJSSimdOperation_add: + case AsmJSSimdOperation_sub: + return true; + case AsmJSSimdOperation_mul: + case AsmJSSimdOperation_div: + return type == AsmJSSimdType_float32x4; + } + return false; +} + +static bool +CheckGlobalMathImport(ModuleCompiler &m, ParseNode *initNode, PropertyName *varName, + PropertyName *field) +{ + // Math builtin, with the form glob.Math.[[builtin]] + ModuleCompiler::MathBuiltin mathBuiltin; + if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) + return m.failName(initNode, "'%s' is not a standard Math builtin", field); + + switch (mathBuiltin.kind) { + case ModuleCompiler::MathBuiltin::Function: + return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field); + case ModuleCompiler::MathBuiltin::Constant: + return m.addMathBuiltinConstant(varName, mathBuiltin.u.cst, field); + default: + break; + } + MOZ_CRASH("unexpected or uninitialized math builtin type"); +} + +static bool +CheckGlobalSimdImport(ModuleCompiler &m, ParseNode *initNode, PropertyName *varName, + PropertyName *field) +{ + if (!m.supportsSimd()) + return m.fail(initNode, "SIMD is not supported on this platform"); + + // SIMD constructor, with the form glob.SIMD.[[type]] + AsmJSSimdType simdType; + if (!IsSimdTypeName(m, field, &simdType)) + return m.failName(initNode, "'%s' is not a standard SIMD type", field); + return m.addSimdCtor(varName, simdType, field); +} + +static bool +CheckGlobalSimdOperationImport(ModuleCompiler &m, const ModuleCompiler::Global *global, + ParseNode *initNode, PropertyName *varName, PropertyName *ctorVarName, + PropertyName *opName) +{ + AsmJSSimdType simdType = global->simdCtorType(); + AsmJSSimdOperation simdOp; + if (!m.lookupStandardSimdOpName(opName, &simdOp)) + return m.failName(initNode, "'%s' is not a standard SIMD operation", opName); + if (!IsSimdValidOperationType(simdType, simdOp)) + return m.failName(initNode, "'%s' is not an operation supported by the SIMD type", opName); + return m.addSimdOperation(varName, simdType, simdOp, ctorVarName, opName); +} + +static bool CheckGlobalDotImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode) { ParseNode *base = DotBase(initNode); PropertyName *field = DotMember(initNode); if (base->isKind(PNK_DOT)) { ParseNode *global = DotBase(base); - PropertyName *math = DotMember(base); - if (!IsUseOfName(global, m.module().globalArgumentName()) || math != m.cx()->names().Math) - return m.fail(base, "expecting global.Math"); - - ModuleCompiler::MathBuiltin mathBuiltin; - if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) - return m.failName(initNode, "'%s' is not a standard Math builtin", field); - - switch (mathBuiltin.kind) { - case ModuleCompiler::MathBuiltin::Function: - return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field); - case ModuleCompiler::MathBuiltin::Constant: - return m.addMathBuiltinConstant(varName, mathBuiltin.u.cst, field); - default: - break; - } - MOZ_CRASH("unexpected or uninitialized math builtin type"); - } - - if (IsUseOfName(base, m.module().globalArgumentName())) { + PropertyName *mathOrSimd = DotMember(base); + + if (!IsUseOfName(global, m.module().globalArgumentName())) + return m.failf(base, "expecting %s.*", m.module().globalArgumentName()); + + if (mathOrSimd == m.cx()->names().Math) + return CheckGlobalMathImport(m, initNode, varName, field); + if (mathOrSimd == m.cx()->names().SIMD) + return CheckGlobalSimdImport(m, initNode, varName, field); + return m.failf(base, "expecting %s.{Math|SIMD}", m.module().globalArgumentName()); + } + + 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); return m.failName(initNode, "'%s' is not a standard global constant", field); } - if (IsUseOfName(base, m.module().importArgumentName())) + if (base->name() == m.module().importArgumentName()) return m.addFFI(varName, field); - return m.fail(initNode, "expecting c.y where c is either the global or foreign parameter"); + const ModuleCompiler::Global *global = m.lookupGlobal(base->name()); + if (!global) + return m.failName(initNode, "%s not found in module global scope", base->name()); + + if (!global->isSimdCtor()) + return m.failName(base, "expecting SIMD constructor name, got %s", field); + + return CheckGlobalSimdOperationImport(m, global, initNode, varName, base->name(), field); } static bool CheckModuleGlobal(ModuleCompiler &m, ParseNode *var, bool isConst) { if (!IsDefinition(var)) return m.fail(var, "import variable names must be unique"); @@ -3220,16 +3669,22 @@ CheckFinalReturn(FunctionCompiler &f, Pa *retType = RetType::Signed; break; case AsmJSNumLit::Double: *retType = RetType::Double; break; case AsmJSNumLit::Float: *retType = RetType::Float; break; + case AsmJSNumLit::Int32x4: + *retType = RetType::Int32x4; + break; + case AsmJSNumLit::Float32x4: + *retType = RetType::Float32x4; + break; } return true; } AsmJSCoercion coercion; if (!CheckTypeAnnotation(f.m(), coercionNode, &coercion)) return false; @@ -3335,16 +3790,18 @@ CheckVarRef(FunctionCompiler &f, ParseNo *def = f.loadGlobalVar(*global); *type = global->varOrConstType().toType(); break; case ModuleCompiler::Global::Function: case ModuleCompiler::Global::FFI: case ModuleCompiler::Global::MathBuiltinFunction: case ModuleCompiler::Global::FuncPtrTable: case ModuleCompiler::Global::ArrayView: + case ModuleCompiler::Global::SimdCtor: + case ModuleCompiler::Global::SimdOperation: 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); } @@ -3357,17 +3814,17 @@ IsLiteralOrConstInt(FunctionCompiler &f, if (pn->getKind() != PNK_NAME) return false; PropertyName *name = pn->name(); const ModuleCompiler::Global *global = f.lookupGlobal(name); if (!global || global->which() != ModuleCompiler::Global::ConstantLiteral) return false; - const Value &v = global->constLiteralValue().value(); + const Value &v = global->constLiteralValue().scalarValue(); if (!v.isInt32()) return false; *u32 = (uint32_t) v.toInt32(); return true; } static bool @@ -3494,16 +3951,50 @@ CheckLoadArray(FunctionCompiler &f, Pars return false; *def = f.loadHeap(viewType, pointerDef, needsBoundsCheck); *type = TypedArrayLoadType(viewType); return true; } static bool +CheckDotAccess(FunctionCompiler &f, ParseNode *elem, MDefinition **def, Type *type) +{ + JS_ASSERT(elem->isKind(PNK_DOT)); + + ParseNode *base = DotBase(elem); + MDefinition *baseDef; + Type baseType; + if (!CheckExpr(f, base, &baseDef, &baseType)) + return false; + if (!baseType.isSimd()) + return f.failf(base, "expected SIMD type, got %s", baseType.toChars()); + + ModuleCompiler &m = f.m(); + PropertyName *field = DotMember(elem); + + SimdLane lane; + JSAtomState &names = m.cx()->names(); + if (field == names.x) + lane = LaneX; + else if (field == names.y) + lane = LaneY; + else if (field == names.z) + lane = LaneZ; + else if (field == names.w) + lane = LaneW; + else + return f.fail(base, "dot access field must be a lane name (x, y, z, w)"); + + *type = baseType.simdToScalarType(); + *def = f.extractSimdElement(lane, baseDef, type->toMIRType()); + return true; +} + +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; @@ -3931,16 +4422,18 @@ CheckIsExternType(FunctionCompiler &f, P static bool CheckFFICall(FunctionCompiler &f, ParseNode *callNode, unsigned ffiIndex, RetType retType, MDefinition **def, Type *type) { PropertyName *calleeName = CallCallee(callNode)->name(); if (retType == RetType::Float) return f.fail(callNode, "FFI calls can't return float"); + if (retType.toType().isSimd()) + return f.fail(callNode, "FFI calls can't return SIMD values"); FunctionCompiler::Call call(f, callNode, retType); if (!CheckCallArgs(f, callNode, CheckIsExternType, &call)) return false; unsigned exitIndex; if (!f.m().addExit(ffiIndex, calleeName, Move(call.sig()), &exitIndex)) return false; @@ -3948,46 +4441,91 @@ CheckFFICall(FunctionCompiler &f, ParseN if (!f.ffiCall(exitIndex, call, retType.toMIRType(), def)) return false; *type = retType.toType(); return true; } static bool +CheckFloatCoercionArg(FunctionCompiler &f, ParseNode *inputNode, Type inputType, + MDefinition *inputDef, MDefinition **def) +{ + if (inputType.isMaybeDouble() || inputType.isSigned()) { + *def = f.unary<MToFloat32>(inputDef); + return true; + } + if (inputType.isUnsigned()) { + *def = f.unary<MAsmJSUnsignedToFloat32>(inputDef); + return true; + } + if (inputType.isFloatish()) { + *def = inputDef; + return true; + } + + return f.failf(inputNode, "%s is not a subtype of signed, unsigned, double? or floatish", + inputType.toChars()); +} + +static bool CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinition **def, Type *type); static bool +CheckCoercionArg(FunctionCompiler &f, ParseNode *arg, AsmJSCoercion expected, MDefinition **def, + Type *type) +{ + RetType retType(expected); + if (arg->isKind(PNK_CALL)) + return CheckCoercedCall(f, arg, retType, def, type); + + MDefinition *argDef; + Type argType; + if (!CheckExpr(f, arg, &argDef, &argType)) + return false; + + switch (expected) { + case AsmJS_FRound: + if (!CheckFloatCoercionArg(f, arg, argType, argDef, def)) + return false; + break; + case AsmJS_ToInt32x4: + if (!argType.isInt32x4()) + return f.fail(arg, "argument to SIMD int32x4 coercion isn't int32x4"); + *def = argDef; + break; + case AsmJS_ToFloat32x4: + if (!argType.isFloat32x4()) + return f.fail(arg, "argument to SIMD float32x4 coercion isn't float32x4"); + *def = argDef; + break; + case AsmJS_ToInt32: + case AsmJS_ToNumber: + MOZ_CRASH("not call coercions"); + } + + *type = retType.toType(); + return true; +} + +static bool CheckMathFRound(FunctionCompiler &f, ParseNode *callNode, MDefinition **def, MathRetType *type) { - ParseNode *argNode = nullptr; - if (!IsFloatCoercion(f.m(), callNode, &argNode)) - return f.fail(callNode, "invalid call to fround"); - - // Make sure to do this before calling CheckCoercedCall - *type = MathRetType::Float; - - Type _; - if (argNode->isKind(PNK_CALL)) - return CheckCoercedCall(f, argNode, RetType::Float, def, &_); - + if (CallArgListLength(callNode) != 1) + return f.fail(callNode, "Math.fround must be passed 1 argument"); + + ParseNode *argNode = CallArgList(callNode); MDefinition *argDef; Type argType; - if (!CheckExpr(f, argNode, &argDef, &argType)) - return false; - - if (argType.isMaybeDouble() || argType.isSigned()) - *def = f.unary<MToFloat32>(argDef); - else if (argType.isUnsigned()) - *def = f.unary<MAsmJSUnsignedToFloat32>(argDef); - else if (argType.isFloatish()) - *def = argDef; - else - return f.failf(argNode, "%s is not a subtype of signed, unsigned, double? or floatish", argType.toChars()); - + if (!CheckCoercionArg(f, argNode, AsmJS_FRound, &argDef, &argType)) + return false; + + JS_ASSERT(argType == Type::Float); + *def = argDef; + *type = MathRetType::Float; return true; } static bool CheckMathBuiltinCall(FunctionCompiler &f, ParseNode *callNode, AsmJSMathBuiltinFunction func, MDefinition **def, MathRetType *type) { unsigned arity = 0; @@ -4061,28 +4599,129 @@ CheckMathBuiltinCall(FunctionCompiler &f if (!f.builtinCall(callee, call, varType.toMIRType(), def)) return false; *type = MathRetType(opIsDouble ? MathRetType::Double : MathRetType::Floatish); return true; } static bool +CheckBinarySimd(FunctionCompiler &f, ParseNode *call, AsmJSSimdType simdType, + MSimdBinaryArith::Operation op, MDefinition **def, Type *type) +{ + unsigned numArgs = CallArgListLength(call); + if (numArgs != 2) + return f.failf(call, "expected 2 arguments to binary arithmetic SIMD operation, got %u", numArgs); + + ParseNode *lhs = CallArgList(call); + ParseNode *rhs = NextNode(lhs); + + MDefinition *lhsDef, *rhsDef; + Type lhsType, rhsType; + if (!CheckExpr(f, lhs, &lhsDef, &lhsType)) + return false; + if (!CheckExpr(f, rhs, &rhsDef, &rhsType)) + return false; + + Type retType = simdType; + JS_ASSERT_IF(retType.isInt32x4(), op != MSimdBinaryArith::Mul && op != MSimdBinaryArith::Div); + if (lhsType != retType || rhsType != retType) + return f.failf(lhs, "arguments to SIMD binary op should both be %s", retType.toChars()); + + *type = retType; + *def = f.binarySimd(lhsDef, rhsDef, op, retType.toMIRType()); + return true; +} + +static bool +CheckSimdOperationCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global, + MDefinition **def, Type *type) +{ + JS_ASSERT(global->isSimdOperation()); + switch (global->simdOperation()) { + case AsmJSSimdOperation_add: + return CheckBinarySimd(f, call, global->simdOperationType(), MSimdBinaryArith::Add, def, type); + case AsmJSSimdOperation_sub: + return CheckBinarySimd(f, call, global->simdOperationType(), MSimdBinaryArith::Sub, def, type); + case AsmJSSimdOperation_mul: + return CheckBinarySimd(f, call, global->simdOperationType(), MSimdBinaryArith::Mul, def, type); + case AsmJSSimdOperation_div: + return CheckBinarySimd(f, call, global->simdOperationType(), MSimdBinaryArith::Div, def, type); + } + MOZ_CRASH("unexpected simd operation in CheckSimdOperationCall"); +} + +static bool +CheckSimdCtorCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global, + MDefinition **def, Type *type) +{ + JS_ASSERT(call->isKind(PNK_CALL)); + + AsmJSCoercion coercion; + ParseNode *argNode; + if (IsCoercionCall(f.m(), call, &coercion, &argNode)) + return CheckCoercionArg(f, argNode, coercion, def, type); + + AsmJSSimdType simdType = global->simdCtorType(); + unsigned numArgs = CallArgListLength(call); + unsigned length = SimdTypeToLength(simdType); + if (numArgs != length) + return f.failName(call, "invalid number of arguments in call to '%s'", CallCallee(call)->name()); + + Vector<MDefinition*, 4, SystemAllocPolicy> defs; + if (!defs.resize(length)) + return false; + + argNode = CallArgList(call); + size_t i = 0; + for (; argNode; argNode = NextNode(argNode), ++i) + { + JS_ASSERT(i < length); + + Type argType; + if (!CheckExpr(f, argNode, &defs[i], &argType)) + return false; + + switch (simdType) { + case AsmJSSimdType_int32x4: + if (!argType.isIntish()) + return f.failf(argNode, "argument %d of Int32x4 ctor isn't a subtype of intish", i); + break; + case AsmJSSimdType_float32x4: + if (!CheckFloatCoercionArg(f, argNode, argType, defs[i], &defs[i])) + return false; + break; + } + } + JS_ASSERT(i == length); + + *type = simdType; + *def = f.constructSimd<MSimdValueX4>(defs[0], defs[1], defs[2], defs[3], type->toMIRType()); + return true; +} + +static bool CheckUncoercedCall(FunctionCompiler &f, ParseNode *expr, MDefinition **def, Type *type) { JS_ASSERT(expr->isKind(PNK_CALL)); const ModuleCompiler::Global *global; - if (IsCallToGlobal(f.m(), expr, &global) && global->isMathFunction()) - { - MathRetType mathRetType; - if (!CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), def, &mathRetType)) - return false; - *type = mathRetType.toType(); - return true; + if (IsCallToGlobal(f.m(), expr, &global)) { + if (global->isMathFunction()) { + MathRetType mathRetType; + if (!CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), def, &mathRetType)) + return false; + *type = mathRetType.toType(); + return true; + } + + if (global->isSimdCtor()) + return CheckSimdCtorCall(f, expr, global, def, type); + if (global->isSimdOperation()) + return CheckSimdOperationCall(f, expr, global, def, type); } return f.fail(expr, "all function calls must either be calls to standard lib math functions, " "ignored (via f(); or comma-expression), coerced to signed (via f()|0), " "coerced to float (via fround(f())) or coerced to double (via +f())"); } static bool @@ -4090,16 +4729,20 @@ CheckCoercedMathBuiltinCall(FunctionComp RetType retType, MDefinition **def, Type *type) { MDefinition *operand; MathRetType actualRetType; if (!CheckMathBuiltinCall(f, callNode, func, &operand, &actualRetType)) return false; switch (retType.which()) { + case RetType::Int32x4: + case RetType::Float32x4: + return f.failf(callNode, "%s is not a vector type", actualRetType.toType().toChars()); + case RetType::Double: switch (actualRetType.which()) { case MathRetType::Double: *def = operand; break; case MathRetType::Float: case MathRetType::Signed: *def = f.unary<MToDouble>(operand); @@ -4151,16 +4794,56 @@ CheckCoercedMathBuiltinCall(FunctionComp } JS_ASSERT_IF(retType == RetType::Void || f.inDeadCode(), !*def); JS_ASSERT_IF(retType != RetType::Void && !f.inDeadCode(), !!*def); return true; } static bool +CheckCoercedSimdCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global, + RetType retType, MDefinition **def, Type *type) +{ + if (global->isSimdCtor()) { + if (!CheckSimdCtorCall(f, call, global, def, type)) + return false; + } else { + JS_ASSERT(global->isSimdOperation()); + if (!CheckSimdOperationCall(f, call, global, def, type)) + return false; + } + + JS_ASSERT(type->isSimd()); + switch (retType.which()) { + case RetType::Signed: + case RetType::Double: + case RetType::Float: + return f.failf(call, "SIMD call returns %s, used as scalar", type->toChars()); + + case RetType::Int32x4: + if (!type->isInt32x4()) + return f.failf(call, "SIMD call returns %s, used as int32x4", type->toChars()); + break; + + case RetType::Float32x4: + if (!type->isFloat32x4()) + return f.failf(call, "SIMD call returns %s, used as float32x4", type->toChars()); + break; + + case RetType::Void: + *def = nullptr; + break; + } + + JS_ASSERT_IF(retType == RetType::Void || f.inDeadCode(), !*def); + JS_ASSERT_IF(retType != RetType::Void && !f.inDeadCode(), !!*def); + return true; +} + +static bool CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinition **def, Type *type) { JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); ParseNode *callee = CallCallee(call); if (callee->isKind(PNK_ELEM)) return CheckFuncPtrCall(f, call, retType, def, type); @@ -4177,16 +4860,19 @@ 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: 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; } } return CheckInternalCall(f, call, calleeName, retType, def, type); } @@ -4409,16 +5095,18 @@ IsValidIntMultiplyConstant(ModuleCompile case AsmJSNumLit::NegativeInt: if (abs(literal.toInt32()) < (1<<20)) return true; return false; case AsmJSNumLit::BigUnsigned: case AsmJSNumLit::Double: case AsmJSNumLit::Float: case AsmJSNumLit::OutOfRangeInt: + case AsmJSNumLit::Int32x4: + case AsmJSNumLit::Float32x4: return false; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal"); } static bool CheckMultiply(FunctionCompiler &f, ParseNode *star, MDefinition **def, Type *type) @@ -4695,16 +5383,17 @@ CheckExpr(FunctionCompiler &f, ParseNode return false; if (IsNumericLiteral(f.m(), expr)) return CheckNumericLiteral(f, expr, def, type); switch (expr->getKind()) { case PNK_NAME: return CheckVarRef(f, expr, def, type); case PNK_ELEM: return CheckLoadArray(f, expr, def, type); + case PNK_DOT: return CheckDotAccess(f, expr, def, type); case PNK_ASSIGN: return CheckAssign(f, expr, def, type); case PNK_POS: return CheckPos(f, expr, def, type); case PNK_NOT: return CheckNot(f, expr, def, type); case PNK_NEG: return CheckNeg(f, expr, def, type); case PNK_BITNOT: return CheckBitNot(f, expr, def, type); case PNK_COMMA: return CheckComma(f, expr, def, type); case PNK_CONDITIONAL: return CheckConditional(f, expr, def, type); case PNK_STAR: return CheckMultiply(f, expr, def, type); @@ -5104,16 +5793,18 @@ CheckCaseExpr(FunctionCompiler &f, Parse case AsmJSNumLit::NegativeInt: *value = literal.toInt32(); break; case AsmJSNumLit::OutOfRangeInt: case AsmJSNumLit::BigUnsigned: return f.fail(caseExpr, "switch case expression out of integer range"); case AsmJSNumLit::Double: case AsmJSNumLit::Float: + case AsmJSNumLit::Int32x4: + case AsmJSNumLit::Float32x4: return f.fail(caseExpr, "switch case expression must be an integer literal"); } return true; } static bool CheckDefaultAtEnd(FunctionCompiler &f, ParseNode *stmt) @@ -5262,16 +5953,20 @@ CheckReturn(FunctionCompiler &f, ParseNo RetType retType; if (type.isSigned()) retType = RetType::Signed; else if (type.isDouble()) retType = RetType::Double; else if (type.isFloat()) retType = RetType::Float; + else if (type.isInt32x4()) + retType = RetType::Int32x4; + else if (type.isFloat32x4()) + retType = RetType::Float32x4; else if (type.isVoid()) retType = RetType::Void; else return f.failf(expr, "%s is not a valid return type", type.toChars()); if (!CheckReturnType(f, expr, retType)) return false; @@ -5931,194 +6626,246 @@ CheckModuleReturn(ModuleCompiler &m) // (since cx->tempLifoAlloc is marked/released after each function // statement) and thus all the identifiers in the return statement will be // mistaken as free variables and added to lexdeps. Clear these now. m.parser().pc->lexdeps->clear(); return true; } static void -AssertStackAlignment(MacroAssembler &masm) -{ - JS_ASSERT((sizeof(AsmJSFrame) + masm.framePushed()) % StackAlignment == 0); - masm.assertStackAlignment(); +AssertStackAlignment(MacroAssembler &masm, uint32_t alignment) +{ + JS_ASSERT((sizeof(AsmJSFrame) + masm.framePushed()) % alignment == 0); + masm.assertStackAlignment(alignment); } static unsigned -StackDecrementForCall(MacroAssembler &masm, unsigned bytesToPush) -{ - return StackDecrementForCall(sizeof(AsmJSFrame) + masm.framePushed(), bytesToPush); +StackDecrementForCall(MacroAssembler &masm, uint32_t alignment, unsigned bytesToPush) +{ + return StackDecrementForCall(alignment, sizeof(AsmJSFrame) + masm.framePushed(), bytesToPush); } template <class VectorT> static unsigned StackArgBytes(const VectorT &argTypes) { ABIArgIter<VectorT> iter(argTypes); while (!iter.done()) iter++; return iter.stackBytesConsumedSoFar(); } template <class VectorT> static unsigned -StackDecrementForCall(MacroAssembler &masm, const VectorT &argTypes, unsigned extraBytes = 0) -{ - return StackDecrementForCall(masm, StackArgBytes(argTypes) + extraBytes); +StackDecrementForCall(MacroAssembler &masm, uint32_t alignment, const VectorT &argTypes, + unsigned extraBytes = 0) +{ + return StackDecrementForCall(masm, alignment, StackArgBytes(argTypes) + extraBytes); } #if defined(JS_CODEGEN_ARM) // The ARM system ABI also includes d15 in the non volatile float registers. // Also exclude lr (a.k.a. r14) as we preserve it manually) static const RegisterSet NonVolatileRegs = RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask & ~(uint32_t(1) << Registers::lr)), FloatRegisterSet(FloatRegisters::NonVolatileMask | (1ULL << FloatRegisters::d15))); #else static const RegisterSet NonVolatileRegs = RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask), FloatRegisterSet(FloatRegisters::NonVolatileMask)); #endif +static const FloatRegisterSet NonVolatileSimdRegs = SupportsSimd ? NonVolatileRegs.fpus() + : FloatRegisterSet(); #if defined(JS_CODEGEN_MIPS) // Mips is using one more double slot due to stack alignment for double values. // Look at MacroAssembler::PushRegsInMask(RegisterSet set) static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) + NonVolatileRegs.fpus().getPushSizeInBytes() + sizeof(double); #else -static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) + - NonVolatileRegs.fpus().getPushSizeInBytes(); +static const unsigned FramePushedAfterSave = + SupportsSimd ? NonVolatileRegs.gprs().size() * sizeof(intptr_t) + + NonVolatileRegs.fpus().size() * Simd128DataSize + : NonVolatileRegs.gprs().size() * sizeof(intptr_t) + + NonVolatileRegs.fpus().getPushSizeInBytes(); #endif +static const unsigned FramePushedForEntrySP = FramePushedAfterSave + sizeof(void*); static bool GenerateEntry(ModuleCompiler &m, unsigned exportIndex) { MacroAssembler &masm = m.masm(); Label begin; masm.align(CodeAlignment); masm.bind(&begin); + // Save the return address if it wasn't already saved by the call insn. #if defined(JS_CODEGEN_ARM) masm.push(lr); #elif defined(JS_CODEGEN_MIPS) masm.push(ra); +#elif defined(JS_CODEGEN_X86) + static const unsigned EntryFrameSize = sizeof(void*); #endif - masm.subPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); + + // Save all caller non-volatile registers before we clobber them here and in + // the asm.js callee (which does not preserve non-volatile registers). masm.setFramePushed(0); - - // In constrast to the system ABI, the Ion convention is that all registers - // are clobbered by calls. Thus, we must save the caller's non-volatile - // registers. - masm.PushRegsInMask(NonVolatileRegs); + masm.PushRegsInMask(NonVolatileRegs, NonVolatileSimdRegs); JS_ASSERT(masm.framePushed() == FramePushedAfterSave); // ARM and MIPS have a globally-pinned GlobalReg (x64 uses RIP-relative // 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 - // Remember the stack pointer in the current AsmJSActivation. This will be - // used by error exit paths to set the stack pointer back to what it was - // right after the (C++) caller's non-volatile registers were saved so that - // they can be restored. - Register activation = ABIArgGenerator::NonArgReturnReg0; - masm.loadAsmJSActivation(activation); - masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfErrorRejoinSP())); - - // Get 'argv' into a non-arg register and save it on the stack. + // 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) - masm.loadPtr(Address(StackPointer, sizeof(AsmJSFrame) + masm.framePushed()), argv); + masm.loadPtr(Address(StackPointer, EntryFrameSize + masm.framePushed()), argv); #else masm.movePtr(IntArgReg0, argv); #endif masm.Push(argv); + // Save the stack pointer to the saved non-volatile registers. We will use + // this on two paths: normal return and exceptional return. Since + // loadAsmJSActivation uses GlobalReg, we must do this after loading + // GlobalReg. + JS_ASSERT(masm.framePushed() == FramePushedForEntrySP); + masm.loadAsmJSActivation(scratch); + masm.storePtr(StackPointer, Address(scratch, AsmJSActivation::offsetOfEntrySP())); + + // Dynamically align the stack since ABIStackAlignment is not necessarily + // AsmJSStackAlignment. We'll use entrySP to recover the original stack + // pointer on return. + masm.andPtr(Imm32(~(AsmJSStackAlignment - 1)), StackPointer); + // Bump the stack for the call. PropertyName *funcName = m.module().exportedFunction(exportIndex).name(); const ModuleCompiler::Func &func = *m.lookupFunction(funcName); - unsigned stackDec = StackDecrementForCall(masm, func.sig().args()); - masm.reserveStack(stackDec); + masm.reserveStack(AlignBytes(StackArgBytes(func.sig().args()), AsmJSStackAlignment)); // Copy parameters out of argv and into the registers/stack-slots specified by // the system ABI. for (ABIArgTypeIter iter(func.sig().args()); !iter.done(); iter++) { - unsigned argOffset = iter.index() * sizeof(uint64_t); + unsigned argOffset = iter.index() * sizeof(AsmJSModule::EntryArg); Address src(argv, argOffset); + MIRType type = iter.mirType(); switch (iter->kind()) { case ABIArg::GPR: masm.load32(src, iter->gpr()); break; case ABIArg::FPU: - if (iter.mirType() == MIRType_Double) { + switch (type) { + case MIRType_Int32x4: + masm.loadUnalignedInt32x4(src, iter->fpu()); + break; + case MIRType_Float32x4: + masm.loadUnalignedFloat32x4(src, iter->fpu()); + break; + case MIRType_Double: masm.loadDouble(src, iter->fpu()); - } else { - JS_ASSERT(iter.mirType() == MIRType_Float32); + break; + case MIRType_Float32: masm.loadFloat32(src, iter->fpu()); + break; + default: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected FPU type"); + break; } break; case ABIArg::Stack: - if (iter.mirType() == MIRType_Int32) { + switch (type) { + case MIRType_Int32: masm.load32(src, scratch); masm.storePtr(scratch, Address(StackPointer, iter->offsetFromArgBase())); - } else if (iter.mirType() == MIRType_Double) { + break; + case MIRType_Double: masm.loadDouble(src, ScratchDoubleReg); masm.storeDouble(ScratchDoubleReg, Address(StackPointer, iter->offsetFromArgBase())); - } else { - JS_ASSERT(iter.mirType() == MIRType_Float32); + break; + case MIRType_Float32: masm.loadFloat32(src, ScratchFloat32Reg); masm.storeFloat32(ScratchFloat32Reg, Address(StackPointer, iter->offsetFromArgBase())); + break; + case MIRType_Int32x4: + masm.loadUnalignedInt32x4(src, ScratchSimdReg); + masm.storeAlignedInt32x4(ScratchSimdReg, + Address(StackPointer, iter->offsetFromArgBase())); + break; + case MIRType_Float32x4: + masm.loadUnalignedFloat32x4(src, ScratchSimdReg); + masm.storeAlignedFloat32x4(ScratchSimdReg, + Address(StackPointer, iter->offsetFromArgBase())); + break; + default: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected stack arg type"); } break; } } // Call into the real function. - AssertStackAlignment(masm); + masm.assertStackAlignment(AsmJSStackAlignment); masm.call(CallSiteDesc(CallSiteDesc::Relative), &func.entry()); - // Pop the stack and recover the original 'argv' argument passed to the - // trampoline (which was pushed on the stack). - masm.freeStack(stackDec); + // Recover the stack pointer value before dynamic alignment. + masm.loadAsmJSActivation(scratch); + masm.loadPtr(Address(scratch, AsmJSActivation::offsetOfEntrySP()), StackPointer); + masm.setFramePushed(FramePushedForEntrySP); + + // Recover the 'argv' pointer which was saved before aligning the stack. masm.Pop(argv); // Store the return value in argv[0] switch (func.sig().retType().which()) { case RetType::Void: break; case RetType::Signed: masm.storeValue(JSVAL_TYPE_INT32, ReturnReg, Address(argv, 0)); break; case RetType::Float: masm.convertFloat32ToDouble(ReturnFloat32Reg, ReturnDoubleReg); // Fall through as ReturnDoubleReg now contains a Double case RetType::Double: masm.canonicalizeDouble(ReturnDoubleReg); masm.storeDouble(ReturnDoubleReg, Address(argv, 0)); break; + case RetType::Int32x4: + // We don't have control on argv alignment, do an unaligned access. + masm.storeUnalignedInt32x4(ReturnSimdReg, Address(argv, 0)); + break; + case RetType::Float32x4: + // We don't have control on argv alignment, do an unaligned access. + masm.storeUnalignedFloat32x4(ReturnSimdReg, Address(argv, 0)); + break; } // Restore clobbered non-volatile registers of the caller. - masm.PopRegsInMask(NonVolatileRegs); + masm.PopRegsInMask(NonVolatileRegs, NonVolatileSimdRegs); JS_ASSERT(masm.framePushed() == 0); masm.move32(Imm32(true), ReturnReg); - masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); masm.ret(); return m.finishGeneratingEntry(exportIndex, &begin) && !masm.oom(); } static void FillArgumentArray(ModuleCompiler &m, const VarTypeVector &argTypes, unsigned offsetToArgs, unsigned offsetToCallerStackArgs, @@ -6172,17 +6919,17 @@ GenerateFFIInterpExit(ModuleCompiler &m, invokeArgTypes.infallibleAppend(typeArray, ArrayLength(typeArray)); // At the point of the call, the stack layout shall be (sp grows to the left): // | stack args | padding | Value argv[] | padding | retaddr | caller stack args | // The padding between stack args and argv ensures that argv is aligned. The // padding between argv and retaddr ensures that sp is aligned. unsigned offsetToArgv = AlignBytes(StackArgBytes(invokeArgTypes), sizeof(double)); unsigned argvBytes = Max<size_t>(1, exit.sig().args().length()) * sizeof(Value); - unsigned framePushed = StackDecrementForCall(masm, offsetToArgv + argvBytes); + unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, offsetToArgv + argvBytes); Label begin; GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::SlowFFI, &begin); // Fill the argument array. unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed(); Register scratch = ABIArgGenerator::NonArgReturnReg0; FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch); @@ -6212,17 +6959,17 @@ GenerateFFIInterpExit(ModuleCompiler &m, } else { masm.computeEffectiveAddress(argv, scratch); masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase())); } i++; JS_ASSERT(i.done()); // Make the call, test whether it succeeded, and extract the return value. - AssertStackAlignment(masm); + AssertStackAlignment(masm, ABIStackAlignment); switch (exit.sig().retType().which()) { case RetType::Void: masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_Ignore)); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); break; case RetType::Signed: masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToInt32)); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); @@ -6230,16 +6977,19 @@ GenerateFFIInterpExit(ModuleCompiler &m, break; case RetType::Double: masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToNumber)); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); masm.loadDouble(argv, ReturnDoubleReg); 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"); } 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 @@ -6274,26 +7024,26 @@ GenerateFFIIonExit(ModuleCompiler &m, co // space required for both calls and take the maximum. In both cases, // include space for savedRegBytes, since these go 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 ionFrameSize = StackDecrementForCall(masm, totalIonBytes); + 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 coerceFrameSize = StackDecrementForCall(masm, totalCoerceBytes); + unsigned coerceFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalCoerceBytes); unsigned framePushed = Max(ionFrameSize, coerceFrameSize); Label begin; GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::IonFFI, &begin); // 1. Descriptor size_t argOffset = offsetToIonArgs; @@ -6384,19 +7134,19 @@ GenerateFFIIonExit(ModuleCompiler &m, co masm.loadPtr(Address(reg0, offsetOfJitTop), reg2); masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitTop())); masm.loadPtr(Address(reg0, offsetOfJitJSContext), reg2); masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitJSContext())); masm.storePtr(reg3, Address(reg0, offsetOfJitJSContext)); } // 2. Call - AssertStackAlignment(masm); + AssertStackAlignment(masm, AsmJSStackAlignment); masm.callIonFromAsmJS(callee); - AssertStackAlignment(masm); + AssertStackAlignment(masm, AsmJSStackAlignment); { // Disable Activation. // // This sequence needs three registers, and must preserve the JSReturnReg_Data and // JSReturnReg_Type, so there are five live registers. JS_ASSERT(JSReturnReg_Data == AsmJSIonExitRegReturnData); JS_ASSERT(JSReturnReg_Type == AsmJSIonExitRegReturnType); @@ -6441,16 +7191,19 @@ GenerateFFIIonExit(ModuleCompiler &m, co masm.convertValueToInt32(JSReturnOperand, ReturnDoubleReg, ReturnReg, &oolConvert, /* -0 check */ false); break; case RetType::Double: masm.convertValueToDouble(JSReturnOperand, ReturnDoubleReg, &oolConvert); break; case RetType::Float: MOZ_CRASH("Float shouldn't be returned from a FFI"); + case RetType::Int32x4: + case RetType::Float32x4: + MOZ_CRASH("SIMD types shouldn't be returned from a FFI"); } Label done; masm.bind(&done); Label profilingReturn; GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::IonFFI, &profilingReturn); @@ -6469,17 +7222,17 @@ GenerateFFIIonExit(ModuleCompiler &m, co } else { masm.computeEffectiveAddress(argv, scratch); masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase())); } i++; JS_ASSERT(i.done()); // Call coercion function - AssertStackAlignment(masm); + AssertStackAlignment(masm, ABIStackAlignment); switch (exit.sig().retType().which()) { case RetType::Signed: masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToInt32)); masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); masm.unboxInt32(Address(StackPointer, offsetToCoerceArgv), ReturnReg); break; case RetType::Double: masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToNumber)); @@ -6564,17 +7317,17 @@ GenerateBuiltinThunk(ModuleCompiler &m, case AsmJSExit::Builtin_CeilF: case AsmJSExit::Builtin_FloorF: argTypes.infallibleAppend(MIRType_Float32); break; case AsmJSExit::Builtin_Limit: MOZ_CRASH("Bad builtin"); } - uint32_t framePushed = StackDecrementForCall(masm, argTypes); + uint32_t framePushed = StackDecrementForCall(masm, ABIStackAlignment, argTypes); Label begin; GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Builtin(builtin), &begin); for (ABIArgMIRTypeIter i(argTypes); !i.done(); i++) { if (i->kind() != ABIArg::Stack) continue; #if !defined(JS_CODEGEN_ARM) @@ -6589,17 +7342,17 @@ GenerateBuiltinThunk(ModuleCompiler &m, masm.loadDouble(srcAddr, ScratchDoubleReg); masm.storeDouble(ScratchDoubleReg, dstAddr); } #else MOZ_CRASH("Architecture should have enough registers for all builtin calls"); #endif } - AssertStackAlignment(masm); + AssertStackAlignment(masm, ABIStackAlignment); masm.call(BuiltinToImmKind(builtin)); Label profilingReturn; GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Builtin(builtin), &profilingReturn); return m.finishGeneratingBuiltinThunk(builtin, &begin, &profilingReturn) && !masm.oom(); } static bool @@ -6632,66 +7385,69 @@ GenerateAsyncInterruptExit(ModuleCompile #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) // Be very careful here not to perturb the machine state before saving it // to the stack. In particular, add/sub instructions may set conditions in // the flags register. masm.push(Imm32(0)); // space for resumePC masm.pushFlags(); // after this we are safe to use sub masm.setFramePushed(0); // set to zero so we can use masm.framePushed() below - masm.PushRegsInMask(AllRegsExceptSP); // save all GP/FP registers (except SP) + masm.PushRegsInMask(AllRegsExceptSP, AllRegsExceptSP.fpus()); // save all GP/FP registers (except SP) Register scratch = ABIArgGenerator::NonArgReturnReg0; // Store resumePC into the reserved space. masm.loadAsmJSActivation(scratch); masm.loadPtr(Address(scratch, AsmJSActivation::offsetOfResumePC()), scratch); masm.storePtr(scratch, Address(StackPointer, masm.framePushed() + sizeof(void*))); // We know that StackPointer is word-aligned, but not necessarily // stack-aligned, so we need to align it dynamically. masm.mov(StackPointer, ABIArgGenerator::NonVolatileReg); - masm.andPtr(Imm32(~(StackAlignment - 1)), StackPointer); + masm.andPtr(Imm32(~(ABIStackAlignment - 1)), StackPointer); if (ShadowStackSpace) masm.subPtr(Imm32(ShadowStackSpace), StackPointer); - masm.assertStackAlignment(); + masm.assertStackAlignment(ABIStackAlignment); masm.call(AsmJSImmPtr(AsmJSImm_HandleExecutionInterrupt)); 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); // restore all GP/FP registers (except SP) + masm.PopRegsInMask(AllRegsExceptSP, AllRegsExceptSP.fpus()); // restore all GP/FP registers (except SP) 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 + // of SIMD registers as well. + JS_STATIC_ASSERT(!SupportsSimd); // save all registers,except sp. After this stack is alligned. masm.PushRegsInMask(AllRegsExceptSP); // Save the stack pointer in a non-volatile register. masm.movePtr(StackPointer, s0); // Align the stack. - masm.ma_and(StackPointer, StackPointer, Imm32(~(StackAlignment - 1))); + masm.ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1))); // Store resumePC into the reserved space. masm.loadAsmJSActivation(IntArgReg0); masm.loadPtr(Address(IntArgReg0, AsmJSActivation::offsetOfResumePC()), IntArgReg1); masm.storePtr(IntArgReg1, Address(s0, masm.framePushed())); // MIPS ABI requires rewserving stack for registes $a0 to $a3. masm.subPtr(Imm32(4 * sizeof(intptr_t)), StackPointer); - masm.assertStackAlignment(); + masm.assertStackAlignment(ABIStackAlignment); masm.call(AsmJSImm_HandleExecutionInterrupt); masm.addPtr(Imm32(4 * sizeof(intptr_t)), StackPointer); masm.branchIfFalseBool(ReturnReg, throwLabel); // This will restore stack to the address before the call. masm.movePtr(s0, StackPointer); @@ -6716,19 +7472,22 @@ GenerateAsyncInterruptExit(ModuleCompile // Align the stack. masm.ma_and(Imm32(~7), sp, sp); // Store resumePC into the return PC stack slot. masm.loadAsmJSActivation(IntArgReg0); masm.loadPtr(Address(IntArgReg0, AsmJSActivation::offsetOfResumePC()), IntArgReg1); masm.storePtr(IntArgReg1, Address(r6, 14 * sizeof(uint32_t*))); + // When this platform supports SIMD extensions, we'll need to push and pop + // high lanes of SIMD registers as well. + JS_STATIC_ASSERT(!SupportsSimd); masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllDoubleMask))); // save all FP registers - masm.assertStackAlignment(); + masm.assertStackAlignment(ABIStackAlignment); masm.call(AsmJSImm_HandleExecutionInterrupt); masm.branchIfFalseBool(ReturnReg, throwLabel); // Restore the machine state to before the interrupt. this will set the pc! masm.PopRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllDoubleMask))); // restore all FP registers masm.mov(r6,sp); masm.as_vmsr(r5); @@ -6762,21 +7521,21 @@ GenerateAsyncInterruptExit(ModuleCompile } static bool GenerateSyncInterruptExit(ModuleCompiler &m, Label *throwLabel) { MacroAssembler &masm = m.masm(); masm.setFramePushed(0); - unsigned framePushed = StackDecrementForCall(masm, ShadowStackSpace); + unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, ShadowStackSpace); GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::Interrupt, &m.syncInterruptLabel()); - AssertStackAlignment(masm); + AssertStackAlignment(masm, ABIStackAlignment); masm.call(AsmJSImmPtr(AsmJSImm_HandleExecutionInterrupt)); masm.branchIfFalseBool(ReturnReg, throwLabel); Label profilingReturn; GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::Interrupt, &profilingReturn); return m.finishGeneratingInterrupt(&m.syncInterruptLabel(), &profilingReturn) && !masm.oom(); } @@ -6790,27 +7549,27 @@ GenerateThrowStub(ModuleCompiler &m, Lab { MacroAssembler &masm = m.masm(); masm.align(CodeAlignment); masm.bind(throwLabel); // We are about to pop all frames in this AsmJSActivation. Set fp to null to // maintain the invariant that fp is either null or pointing to a valid // frame. - Register activation = ABIArgGenerator::NonArgReturnReg0; - masm.loadAsmJSActivation(activation); - masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfFP())); - - masm.setFramePushed(FramePushedAfterSave); - masm.loadPtr(Address(activation, AsmJSActivation::offsetOfErrorRejoinSP()), StackPointer); - masm.PopRegsInMask(NonVolatileRegs); + Register scratch = ABIArgGenerator::NonArgReturnReg0; + masm.loadAsmJSActivation(scratch); + masm.storePtr(ImmWord(0), Address(scratch, AsmJSActivation::offsetOfFP())); + + masm.setFramePushed(FramePushedForEntrySP); + masm.loadPtr(Address(scratch, AsmJSActivation::offsetOfEntrySP()), StackPointer); + masm.Pop(scratch); + masm.PopRegsInMask(NonVolatileRegs, NonVolatileSimdRegs); JS_ASSERT(masm.framePushed() == 0); masm.mov(ImmWord(0), ReturnReg); - masm.addPtr(Imm32(AsmJSFrameBytesAfterReturnAddress), StackPointer); masm.ret(); return m.finishGeneratingInlineStub(throwLabel) && !masm.oom(); } static bool GenerateStubs(ModuleCompiler &m) {
--- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -34,33 +34,54 @@ extern const JSFunctionSpec Int32x4Metho } /////////////////////////////////////////////////////////////////////////// // X4 static const char *laneNames[] = {"lane 0", "lane 1", "lane 2", "lane3"}; template<typename V> -static bool -IsVectorObject(HandleValue v) +bool +js::IsVectorObject(HandleValue v) { if (!v.isObject()) return false; JSObject &obj = v.toObject(); if (!obj.is<TypedObject>()) return false; TypeDescr &typeRepr = obj.as<TypedObject>().typeDescr(); if (typeRepr.kind() != type::X4) return false; return typeRepr.as<X4TypeDescr>().type() == V::type; } +template bool js::IsVectorObject<Int32x4>(HandleValue v); +template bool js::IsVectorObject<Float32x4>(HandleValue v); + +template<typename V> +bool +js::ToSimdConstant(JSContext *cx, HandleValue v, jit::SimdConstant *out) +{ + typedef typename V::Elem Elem; + if (!IsVectorObject<V>(v)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SIMD_NOT_A_VECTOR); + return false; + } + + Elem *mem = reinterpret_cast<Elem *>(v.toObject().as<TypedObject>().typedMem()); + *out = jit::SimdConstant::CreateX4(mem); + return true; +} + +template bool js::ToSimdConstant<Int32x4>(JSContext *cx, HandleValue v, jit::SimdConstant *out); +template bool js::ToSimdConstant<Float32x4>(JSContext *cx, HandleValue v, jit::SimdConstant *out); + template<typename Elem> static Elem TypedObjectMemory(HandleValue v) { TypedObject &obj = v.toObject().as<TypedObject>(); MOZ_ASSERT(!obj.owner().isNeutered()); return reinterpret_cast<Elem>(obj.typedMem()); }
--- a/js/src/builtin/SIMD.h +++ b/js/src/builtin/SIMD.h @@ -161,16 +161,22 @@ struct Int32x4 { static void setReturn(CallArgs &args, Elem value) { args.rval().setInt32(value); } }; template<typename V> JSObject *CreateSimd(JSContext *cx, typename V::Elem *data); +template<typename V> +bool IsVectorObject(HandleValue v); + +template<typename V> +bool ToSimdConstant(JSContext *cx, HandleValue v, jit::SimdConstant *out); + #define DECLARE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands, Flags) \ extern bool \ simd_float32x4_##Name(JSContext *cx, unsigned argc, Value *vp); FLOAT32X4_FUNCTION_LIST(DECLARE_SIMD_FLOAT32X4_FUNCTION) #undef DECLARE_SIMD_FLOAT32X4_FUNCTION #define DECLARE_SIMD_INT32x4_FUNCTION(Name, Func, Operands, Flags) \ extern bool \
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1969,16 +1969,29 @@ EvalReturningScope(JSContext *cx, unsign RootedObject scope(cx); if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope)) return false; args.rval().setObject(*scope); return true; } +static bool +IsSimdAvailable(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); +#ifdef JS_CODEGEN_NONE + bool available = false; +#else + bool available = cx->jitSupportsSimd(); +#endif + args.rval().set(BooleanValue(available)); + return true; +} + static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gc", ::GC, 0, 0, "gc([obj] | 'compartment')", " Run the garbage collector. When obj is given, GC only its compartment.\n" " If 'compartment' is given, GC any compartments that were scheduled for\n" " GC via schedulegc."), JS_FN_HELP("minorgc", ::MinorGC, 0, 0, @@ -2152,16 +2165,20 @@ static const JSFunctionSpecWithHelp Test " inferred name based on where the function was defined. This can be\n" " different from the 'name' property on the function."), JS_FN_HELP("isAsmJSCompilationAvailable", IsAsmJSCompilationAvailable, 0, 0, "isAsmJSCompilationAvailable", " Returns whether asm.js compilation is currently available or whether it is disabled\n" " (e.g., by the debugger)."), + JS_FN_HELP("isSimdAvailable", IsSimdAvailable, 0, 0, +"isSimdAvailable", +" Returns true if SIMD extensions are supported on this platform."), + JS_FN_HELP("getJitCompilerOptions", GetJitCompilerOptions, 0, 0, "getCompilerOptions()", "Return an object describing some of the JIT compiler options.\n"), JS_FN_HELP("isAsmJSModule", IsAsmJSModule, 1, 0, "isAsmJSModule(fn)", " Returns whether the given value is a function containing \"use asm\" that has been\n" " validated according to the asm.js spec."),
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp +++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp @@ -139,17 +139,17 @@ NativeRegExpMacroAssembler::GenerateCode #ifndef JS_CODEGEN_X86 // The InputOutputData* is stored as an argument, save it on the stack // above the frame. masm.Push(IntArgReg0); #endif size_t frameSize = sizeof(FrameData) + num_registers_ * sizeof(void *); - frameSize = JS_ROUNDUP(frameSize + masm.framePushed(), StackAlignment) - masm.framePushed(); + frameSize = JS_ROUNDUP(frameSize + masm.framePushed(), ABIStackAlignment) - masm.framePushed(); // Actually emit code to start a new stack frame. masm.reserveStack(frameSize); masm.checkStackAlignment(); // Check if we have space on the stack. Label stack_ok; void *stack_limit = &runtime->mainThread.jitStackLimit;
--- a/js/src/jit-test/lib/asm.js +++ b/js/src/jit-test/lib/asm.js @@ -103,18 +103,18 @@ function assertAsmLinkFail(f) assertEq(isAsmJSModule(f), true); // Verify no error is thrown with warnings off var ret = f.apply(null, Array.slice(arguments, 1)); assertEq(isAsmJSFunction(ret), false); if (typeof ret === 'object') - for (f of ret) - assertEq(isAsmJSFunction(f), false); + for (var i in ret) + assertEq(isAsmJSFunction(ret[i]), false); // Turn on warnings-as-errors var oldOpts = options("werror"); assertEq(oldOpts.indexOf("werror"), -1); // Verify an error is thrown var caught = false; try {
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testSIMD.js @@ -0,0 +1,657 @@ +load(libdir + "asm.js"); +var heap = new ArrayBuffer(4096); + +// Set to true to see more JS debugging spew +const DEBUG = false; + +if (!isSimdAvailable() || typeof SIMD === 'undefined') { + DEBUG && print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +const I32 = 'var i4 = glob.SIMD.int32x4;' +const I32A = 'var i4a = i4.add;' +const I32S = 'var i4s = i4.sub;' +const F32 = 'var f4 = glob.SIMD.float32x4;' +const F32A = 'var f4a = f4.add;' +const F32S = 'var f4s = f4.sub;' +const F32M = 'var f4m = f4.mul;' +const F32D = 'var f4d = f4.div;' +const FROUND = 'var f32=glob.Math.fround;' + +const INT32_MAX = Math.pow(2, 31) - 1; +const INT32_MIN = INT32_MAX + 1 | 0; + +const assertEqFFI = {assertEq:assertEq}; + +function assertEqX4(real, expected) { + assertEq(real.x, expected[0]); + assertEq(real.y, expected[1]); + assertEq(real.z, expected[2]); + assertEq(real.w, expected[3]); +} + +function CheckI4(header, code, expected) { + // code needs to contain a local called x + header = USE_ASM + I32 + header; + var lanes = ['x', 'y', 'z', 'w']; + for (var i = 0; i < 4; ++i) { + var lane = lanes[i]; + assertEq(asmLink(asmCompile('glob', header + ';function f() {' + code + ';return x.' + lane + '|0} return f'), this)(), expected[i]); + } +} + +function CheckF4(header, code, expected) { + // code needs to contain a local called x + var lanes = ['x', 'y', 'z', 'w']; + header = USE_ASM + F32 + header; + for (var i = 0; i < 4; ++i) { + var lane = lanes[i]; + assertEq(asmLink(asmCompile('glob', header + ';function f() {' + code + ';return +x.' + lane + '} return f'), this)(), Math.fround(expected[i])); + } +} + +try { + +// 1. Constructors + +// 1.1 Compilation +assertAsmTypeFail('glob', USE_ASM + "var i4 = int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var i4 = glob.int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var i4 = glob.globglob.int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var i4 = glob.Math.int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var herd = glob.SIMD.ponyX4 ; return {}") ; + +// 1.2 Linking +assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: 42}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: Math.fround}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {int32x4: 42}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {int32x4: Math.fround}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {int32x4: new Array}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {int32x4: SIMD.float32x4}}); + +[Type, int32] = [TypedObject.StructType, TypedObject.int32]; +var MyStruct = new Type({'x': int32, 'y': int32, 'z': int32, 'w': int32}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {int32x4: MyStruct}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {int32x4: new MyStruct}}); + +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {} return f"), {SIMD:{int32x4: SIMD.int32x4}})(), undefined); + +assertAsmLinkFail(asmCompile('glob', USE_ASM + F32 + "return {}"), {SIMD: {float32x4: 42}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + F32 + "return {}"), {SIMD: {float32x4: Math.fround}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + F32 + "return {}"), {SIMD: {float32x4: new Array}}); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {} return f"), {SIMD:{float32x4: SIMD.float32x4}})(), undefined); + +// 1.3 Correctness +// 1.3.1 Local variables declarations +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Int32x4(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2, 3);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2, 3, 4.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2.0, 3, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4a(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,2+2|0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3," + (INT32_MIN - 1) + ");} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(i4(1,2,3,4));} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,4);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3," + (INT32_MAX + 1) + ");} return f"), this)(), undefined); + +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4();} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1.,2.,3.);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1.,2.,f32(3.),4.);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1.,2.,3.,4.);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3," + (INT32_MIN - 1) + ");} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3," + (INT32_MAX + 1) + ");} return f"), this)(), undefined); + +// Places where NumLit can creep in +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(i) {i=i|0; var z=0; switch(i|0) {case i4(1,2,3,4): z=1; break; default: z=2; break;}} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(i) {i=i|0; var z=0; return i * i4(1,2,3,4) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(i) {var x=i4(1,2,3,i4(4,5,6,7))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "function f(i) {var x=i4(1,2,3,f4(4,5,6,7))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "function f(i) {var x=f4(1,2,3,i4(4,5,6,7))} return f"); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {return i4(1,2,3,4);} return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {return i4(i4(1,2,3,4));} return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {return f4(1,2,3,4);} return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {return f4(f4(1,2,3,4));} return f"), this)(), [1, 2, 3, 4]); + +// Int32x4 ctor should accept int? +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + I32 + "var i32=new glob.Int32Array(heap); function f(i) {i=i|0; return i4(i4(i32[i>>2], 2, 3, 4))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [0, 2, 3, 4]); +// Float32x4 ctor should accept floatish, i.e. float || float? || floatish +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + F32 + "var f32=new glob.Float32Array(heap); function f(i) {i=i|0; return f4(f4(f32[i>>2], 2, 3, 4))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [NaN, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + "var f32=glob.Math.fround; function f(i) {i=i|0; return f4(f4(f32(1) + f32(2), 2, 3, 4))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [3, 2, 3, 4]); + +// 1.3.2 Reading values out of lanes +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=1; return x.y | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=1; return (x + x).y | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=1.; return x.y | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + "var f32=glob.Math.fround;" + I32 + "function f() {var x=f32(1); return x.y | 0;} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,4); return x.length|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,4).y; return x|0;} return f"); + +CheckI4('', 'var x=i4(0,0,0,0)', [0,0,0,0]); +CheckI4('', 'var x=i4(1,2,3,4)', [1,2,3,4]); +CheckI4('', 'var x=i4(' + INT32_MIN + ',2,3,' + INT32_MAX + ')', [INT32_MIN,2,3,INT32_MAX]); +CheckI4('', 'var x=i4(1,2,3,4); var y=i4(5,6,7,8)', [1,2,3,4]); +CheckI4('', 'var a=1; var b=i4(9,8,7,6); var c=13.37; var x=i4(1,2,3,4); var y=i4(5,6,7,8)', [1,2,3,4]); +CheckI4('', 'var y=i4(5,6,7,8); var x=i4(1,2,3,4)', [1,2,3,4]); + +CheckF4('', 'var x=f4(' + INT32_MAX + ', 2, 3, ' + INT32_MIN + ')', [INT32_MAX, 2, 3, INT32_MIN]); +CheckF4('', 'var x=f4(' + (INT32_MAX + 1) + ', 2, 3, 4)', [INT32_MAX + 1, 2, 3, 4]); +CheckF4('', 'var x=f4(1.3, 2.4, 3.5, 98.76)', [1.3, 2.4, 3.5, 98.76]); +CheckF4('', 'var x=f4(13.37, 2., 3., -0)', [13.37, 2, 3, -0]); + +// 1.3.3. Variable assignments +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1.0, 2, 3, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2.0, 3, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3.0, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3, 4.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); var c=4.0; x=i4(1, 2, 3, +c);} return f"); + +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + I32 + "var i32=new glob.Int32Array(heap); function f() {var x=i4(1,2,3,4); i32[0] = x;} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + I32 + "var i32=new glob.Int32Array(heap); function f() {var x=i4(1,2,3,4); x = i32[0];} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + F32 + "var f32=new glob.Float32Array(heap); function f() {var x=f4(1,2,3,4); f32[0] = x;} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + F32 + "var f32=new glob.Int32Array(heap); function f() {var x=f4(1,2,3,4); x = f32[0];} return f"); + +CheckI4('', 'var x=i4(1,2,3,4); x=i4(5,6,7,8)', [5, 6, 7, 8]); +CheckI4('', 'var x=i4(1,2,3,4); var c=6; x=i4(5,c|0,7,8)', [5, 6, 7, 8]); +CheckI4('', 'var x=i4(8,7,6,5); x=i4(x.w|0,x.z|0,x.y|0,x.x|0)', [5, 6, 7, 8]); + +CheckF4(FROUND, 'var x=f4(1,2,3,4); var y=f32(7.); x=f4(5,6,y,8)', [5, 6, 7, 8]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); x=f4(f32(5.),6.,7.,8.)', [5, 6, 7, 8]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); x=f4(f32(5),6,7,8)', [5, 6, 7, 8]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); x=f4(f32(5.),f32(6.),f32(7.),f32(8.))', [5, 6, 7, 8]); +CheckF4('', 'var x=f4(1.,2.,3.,4.); x=f4(5.,6.,7.,8.)', [5, 6, 7, 8]); +CheckF4('', 'var x=f4(1.,2.,3.,4.); x=f4(1,2,3,4)', [1, 2, 3, 4]); +CheckF4(FROUND, 'var x=f4(1.,2.,3.,4.); var y=f32(7.); x=f4(9, 4, 2, 1)', [9, 4, 2, 1]); +CheckF4('', 'var x=f4(8.,7.,6.,5.); x=f4(x.w, x.z, x.y, x.x)', [5, 6, 7, 8]); + +// 1.3.4 Return values +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=1; return i4(x)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=1; return i4(x + x)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=1.; return i4(x)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + FROUND + "function f() {var x=f32(1.); return i4(x)} return f"); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,4); return i4(x)} return f"), this)(), [1,2,3,4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); return f4(x)} return f"), this)(), [1,2,3,4]); + +// 1.3.5 Coerce and pass arguments +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {x=i4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {x=i4(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x,y) {x=i4(y);y=+y} return f"); + +var i32x4 = SIMD.int32x4(1, 3, 3, 7); +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f(x) {x=i4(x)} return f"), this)(i32x4), undefined); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + "function f(x) {x=i4(x); return i4(x);} return f"), this)(i32x4), [1,3,3,7]); + +var f32x4 = SIMD.float32x4(13.37, 42.42, -0, NaN); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f(x) {x=f4(x)} return f"), this)(f32x4), undefined); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + "function f(x) {x=f4(x); return f4(x);} return f"), this)(f32x4), + [Math.fround(13.37), Math.fround(42.42), -0, NaN]); + +function assertCaught(f) { + var caught = false; + try { + f.apply(null, Array.prototype.slice.call(arguments, 1)); + } catch (e) { + DEBUG && print('Assert caught: ', e, '\n', e.stack); + assertEq(e instanceof TypeError, true); + caught = true; + } + assertEq(caught, true); +} + +var f = asmLink(asmCompile('glob', USE_ASM + F32 + "function f(x) {x=f4(x); return f4(x);} return f"), this); +assertCaught(f); +assertCaught(f, 1); +assertCaught(f, {}); +assertCaught(f, "I sincerely am a SIMD typed object."); +assertCaught(f, SIMD.int32x4(1,2,3,4)); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + "function f(x) {x=i4(x); return i4(x);} return f"), this); +assertCaught(f); +assertCaught(f, 1); +assertCaught(f, {}); +assertCaught(f, "I sincerely am a SIMD typed object."); +assertCaught(f, SIMD.float32x4(4,3,2,1)); + +// 1.3.6 Globals +// 1.3.6.1 Local globals +// Read +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4; x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4.; x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); var f32=glob.Math.fround; function f() {var x=f32(4.); x=f32(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4; x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4.; x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); var f32=glob.Math.fround; function f() {var x=f32(4.); x=f32(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + "var g=f4(1., 2., 3., 4.); function f() {var x=i4(1,2,3,4); x=i4(g);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "var g=i4(1,2,3,4); function f() {var x=f4(1.,2.,3.,4.); x=f4(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=0; function f() {var x=i4(1,2,3,4); x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=0.; function f() {var x=i4(1,2,3,4); x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var f32=glob.Math.fround; var g=f32(0.); function f() {var x=i4(1,2,3,4); x=f32(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=0; function f() {var x=f4(0.,0.,0.,0.); x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=0.; function f() {var x=f4(0.,0.,0.,0.); x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var f32=glob.Math.fround; var g=f32(0.); function f() {var x=f4(0.,0.,0.,0.); x=f32(g);} return f"); + +CheckI4('var x=i4(1,2,3,4)', '', [1, 2, 3, 4]); +CheckI4('var _=42; var h=i4(5,5,5,5); var __=13.37; var x=i4(4,7,9,2);', '', [4,7,9,2]); + +CheckF4('var x=f4(1.,2.,3.,4.)', '', [1, 2, 3, 4]); +CheckF4('var _=42; var h=f4(5.,5.,5.,5.); var __=13.37; var x=f4(4.,13.37,9.,-0.);', '', [4, 13.37, 9, -0]); +CheckF4('var x=f4(1,2,3,4)', '', [1, 2, 3, 4]); + +// Write +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4; g=x|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4.; g=+x;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); var f32=glob.Math.fround; function f() {var x=f32(4.); g=f32(x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4; g=x|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4.; g=+x;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); var f32=glob.Math.fround; function f() {var x=f32(4.); g=f32(x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + "var g=f4(1., 2., 3., 4.); function f() {var x=i4(1,2,3,4); g=i4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + "var g=f4(1., 2., 3., 4.); function f() {var x=i4(1,2,3,4); g=f4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "var g=i4(1,2,3,4); function f() {var x=f4(1.,2.,3.,4.); g=f4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "var g=i4(1,2,3,4); function f() {var x=f4(1.,2.,3.,4.); g=i4(x);} return f"); + +CheckI4('var x=i4(0,0,0,0);', 'x=i4(1,2,3,4)', [1,2,3,4]); +CheckF4('var x=f4(0.,0.,0.,0.);', 'x=f4(5.,3.,4.,2.)', [5,3,4,2]); + +CheckI4('var x=i4(0,0,0,0); var y=42; var z=3.9; var w=13.37', 'x=i4(1,2,3,4); y=24; z=4.9; w=23.10;', [1,2,3,4]); +CheckF4('var x=f4(0,0,0,0); var y=42; var z=3.9; var w=13.37', 'x=f4(1,2,3,4); y=24; z=4.9; w=23.10;', [1,2,3,4]); + +// 1.3.6.2 Imported globals +// Read +var int32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + I32 + "var g=i4(ffi.g); function f() {return i4(g)} return f"), this, {g: SIMD.int32x4(1,2,3,4)})(); +assertEq(int32x4.x, 1); +assertEq(int32x4.y, 2); +assertEq(int32x4.z, 3); +assertEq(int32x4.w, 4); + +for (var v of [1, {}, "totally legit SIMD variable", SIMD.float32x4(1,2,3,4)]) + assertCaught(asmCompile('glob', 'ffi', USE_ASM + I32 + "var g=i4(ffi.g); function f() {return i4(g)} return f"), this, {g: v}); + +var float32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + F32 + "var g=f4(ffi.g); function f() {return f4(g)} return f"), this, {g: SIMD.float32x4(1,2,3,4)})(); +assertEq(float32x4.x, 1); +assertEq(float32x4.y, 2); +assertEq(float32x4.z, 3); +assertEq(float32x4.w, 4); + +for (var v of [1, {}, "totally legit SIMD variable", SIMD.int32x4(1,2,3,4)]) + assertCaught(asmCompile('glob', 'ffi', USE_ASM + F32 + "var g=f4(ffi.g); function f() {return f4(g)} return f"), this, {g: v}); + +// Write +var int32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + I32 + "var g=i4(ffi.g); function f() {g=i4(4,5,6,7); return i4(g)} return f"), this, {g: SIMD.int32x4(1,2,3,4)})(); +assertEq(int32x4.x, 4); +assertEq(int32x4.y, 5); +assertEq(int32x4.z, 6); +assertEq(int32x4.w, 7); + +var float32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + F32 + "var g=f4(ffi.g); function f() {g=f4(4.,5.,6.,7.); return f4(g)} return f"), this, {g: SIMD.float32x4(1,2,3,4)})(); +assertEq(float32x4.x, 4); +assertEq(float32x4.y, 5); +assertEq(float32x4.z, 6); +assertEq(float32x4.w, 7); + +// 2. SIMD operations +// 2.1 Compilation +assertAsmTypeFail('glob', USE_ASM + "var add = int32x4.add; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32A + I32 + "return {}"); +assertAsmTypeFail('glob', USE_ASM + "var g = 3; var add = g.add; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var func = i4.doTheHarlemShake; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var div = i4.div; return {}"); +assertAsmTypeFail('glob', USE_ASM + "var f32 = glob.Math.fround; var i4a = f32.add; return {}"); + +// 2.2 Linking +assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + I32 + I32A + "function f() {} return f"), {}); +assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + I32 + I32A + "function f() {} return f"), {SIMD: Math.fround}); + +var oldInt32x4Add = SIMD.int32x4.add; +var code = asmCompile('glob', USE_ASM + I32 + I32A + "return {}"); +for (var v of [42, Math.fround, SIMD.float32x4.add, function(){}, SIMD.int32x4.mul]) { + SIMD.int32x4.add = v; + assertAsmLinkFail(code, {SIMD: {int32x4: SIMD.int32x4}}); +} +SIMD.int32x4.add = oldInt32x4Add; // finally replace the add function with the original one +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + I32A + "function f() {} return f"), {SIMD: {int32x4: SIMD.int32x4}})(), undefined); + +// 2.3. Binary arithmetic operations +// 2.3.1 Additions +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x, x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(13, 37);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(23.10, 19.89);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x, 42);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x, 13.37);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); var y=4; x=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(0,0,0,0); var y=4; x=i4a(y, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(0,0,0,0); var y=4; y=i4a(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); x=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=i4a(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + F32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=f4a(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + F32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=f4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + F32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); x=f4a(y, y);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + 'function f() {var x=i4(1,2,3,4); var y=0; y=i4(x,x)|0} return f'); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + 'function f() {var x=i4(1,2,3,4); var y=0.; y=+i4(x,x)} return f'); + +CheckI4(I32A, 'var z=i4(1,2,3,4); var y=i4(0,1,0,3); var x=i4(0,0,0,0); x=i4a(z,y)', [1,3,3,7]); +CheckI4(I32A, 'var x=i4(2,3,4,5); var y=i4(0,1,0,3); x=i4a(x,y)', [2,4,4,8]); +CheckI4(I32A, 'var x=i4(1,2,3,4); x=i4a(x,x)', [2,4,6,8]); +CheckI4(I32A, 'var x=i4(' + INT32_MAX + ',2,3,4); var y=i4(1,1,0,3); x=i4a(x,y)', [INT32_MIN,3,3,7]); +CheckI4(I32A, 'var x=i4(' + INT32_MAX + ',2,3,4); var y=i4(1,1,0,3); x=i4(i4a(x,y))', [INT32_MIN,3,3,7]); + +CheckF4(F32A, 'var x=f4(1,2,3,4); x=f4a(x,x)', [2,4,6,8]); +CheckF4(F32A, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4a(x,y)', [5,5,8,6]); +CheckF4(F32A, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4a(x,y)', [Math.fround(13.37) + 4,5,8,6]); +CheckF4(F32A, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4(f4a(x,y))', [Math.fround(13.37) + 4,5,8,6]); + +// 2.3.2. Subtracts +CheckI4(I32S, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=i4s(x,y)', [2,1,3,2]); +CheckI4(I32S, 'var x=i4(5,4,3,2); var y=i4(1,2,3,4); x=i4s(x,y)', [4,2,0,-2]); +CheckI4(I32S, 'var x=i4(1,2,3,4); x=i4s(x,x)', [0,0,0,0]); +CheckI4(I32S, 'var x=i4(' + INT32_MIN + ',2,3,4); var y=i4(1,1,0,3); x=i4s(x,y)', [INT32_MAX,1,3,1]); +CheckI4(I32S, 'var x=i4(' + INT32_MIN + ',2,3,4); var y=i4(1,1,0,3); x=i4(i4s(x,y))', [INT32_MAX,1,3,1]); + +CheckF4(F32S, 'var x=f4(1,2,3,4); x=f4s(x,x)', [0,0,0,0]); +CheckF4(F32S, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4s(x,y)', [-3,-1,-2,2]); +CheckF4(F32S, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4s(x,y)', [Math.fround(13.37) - 4,-1,-2,2]); +CheckF4(F32S, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4(f4s(x,y))', [Math.fround(13.37) - 4,-1,-2,2]); + +// 2.3.3. Multiplications / Divisions +assertAsmTypeFail('glob', USE_ASM + I32 + "var f4m=i4.mul; function f() {} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var f4d=i4.div; function f() {} return f"); + +CheckF4(F32M, 'var x=f4(1,2,3,4); x=f4m(x,x)', [1,4,9,16]); +CheckF4(F32M, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4m(x,y)', [4,6,15,8]); +CheckF4(F32M, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4m(x,y)', [Math.fround(13.37) * 4,6,15,8]); +CheckF4(F32M, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4(f4m(x,y))', [Math.fround(13.37) * 4,6,15,8]); + +// Test NaN +var f32x4 = SIMD.float32x4(0, NaN, -0, NaN); +var another = SIMD.float32x4(NaN, -1, -0, NaN); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32M + "function f(x, y) {x=f4(x); y=f4(y); x=f4m(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, 0, NaN]); + +CheckF4(F32D, 'var x=f4(1,2,3,4); x=f4d(x,x)', [1,1,1,1]); +CheckF4(F32D, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4d(x,y)', [1/4,2/3,3/5,2]); +CheckF4(F32D, 'var x=f4(13.37,1,1,4); var y=f4(4,0,-0.,2); x=f4d(x,y)', [Math.fround(13.37) / 4,+Infinity,-Infinity,2]); + +// Test NaN +var f32x4 = SIMD.float32x4(0, 0, -0, NaN); +var another = SIMD.float32x4(0, -0, 0, 0); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32D + "function f(x,y) {x=f4(x); y=f4(y); x=f4d(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, NaN, NaN]); + +// Dead code +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + 'function f(){var x=i4(1,2,3,4); return i4(x); x=i4(5,6,7,8); return i4(x);} return f'), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + 'function f(){var x=i4(1,2,3,4); var c=0; return i4(x); c=x.x|0; return i4(x);} return f'), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + I32A + 'function f(){var x=i4(1,2,3,4); var c=0; return i4(x); x=i4a(x,x); return i4(x);} return f'), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + I32S + 'function f(){var x=i4(1,2,3,4); var c=0; return i4(x); x=i4s(x,x); return i4(x);} return f'), this)(), [1, 2, 3, 4]); + +// 3. Function calls +// 3.1. No math builtins +assertAsmTypeFail('glob', USE_ASM + I32 + "var fround=glob.Math.fround; function f() {var x=i4(1,2,3,4); return +fround(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var sin=glob.Math.sin; function f() {var x=i4(1,2,3,4); return +sin(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var ceil=glob.Math.ceil; function f() {var x=i4(1,2,3,4); return +ceil(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var pow=glob.Math.pow; function f() {var x=i4(1,2,3,4); return +pow(1.0, x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "var fround=glob.Math.fround; function f() {var x=i4(1,2,3,4); x=i4(fround(3));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var sin=glob.Math.sin; function f() {var x=i4(1,2,3,4); x=i4(sin(3.0));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var ceil=glob.Math.sin; function f() {var x=i4(1,2,3,4); x=i4(ceil(3.0));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var pow=glob.Math.pow; function f() {var x=i4(1,2,3,4); x=i4(pow(1.0, 2.0));} return f"); + +// 3.2. FFI calls +// Can't pass SIMD arguments to FFI +assertAsmTypeFail('glob', 'ffi', USE_ASM + I32 + "var func=ffi.func; function f() {var x=i4(1,2,3,4); func(x);} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + F32 + "var func=ffi.func; function f() {var x=f4(1,2,3,4); func(x);} return f"); + +// Can't have FFI return SIMD values +assertAsmTypeFail('glob', 'ffi', USE_ASM + I32 + "var func=ffi.func; function f() {var x=i4(1,2,3,4); x=i4(func());} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + F32 + "var func=ffi.func; function f() {var x=f4(1,2,3,4); x=f4(func());} return f"); + +// 3.3 Internal calls +// asm.js -> asm.js +// Retrieving values from asm.js +var code = USE_ASM + I32 + I32A + ` + var check = ffi.check; + + function g() { + var i = 0; + var y = i4(0,0,0,0); + var tmp = i4(0,0,0,0); var z = i4(1,1,1,1); + var w = i4(5,5,5,5); + for (; (i|0) < 30; i = i + 1 |0) + y = i4a(z, y); + y = i4a(w, y); + check(y.x | 0, y.y | 0, y.z | 0, y.w | 0); + return i4(y); + } + + function f(x) { + x = i4(x); + var y = i4(0,0,0,0); + y = i4(g()); + check(y.x | 0, y.y | 0, y.z | 0, y.w | 0); + return i4(x); + } + return f; +`; + +var v4 = SIMD.int32x4(1,2,3,4); +function check(x, y, z, w) { + assertEq(x, 35); + assertEq(y, 35); + assertEq(z, 35); + assertEq(w, 35); +} +var ffi = {check}; +assertEqX4(asmLink(asmCompile('glob', 'ffi', code), this, ffi)(v4), [1,2,3,4]); + +// Passing arguments from asm.js to asm.js +// TODO make this code look better with templatized strings +var code = USE_ASM + I32 + I32A + ` + var assertEq = ffi.assertEq; + + function internal([args]) { + [coerc] + assertEq([last].x | 0, [i] | 0); + assertEq([last].y | 0, [i] + 1 |0); + assertEq([last].z | 0, [i] + 2 |0); + assertEq([last].w | 0, [i] + 3 |0); + } + + function external() { + [decls] + internal([args]); + } + return external; +`; + +var ffi = {assertEq}; +var args = ''; +var decls = ''; +var coerc = ''; +for (var i = 1; i < 10; ++i) { + var j = i; + args += ((i > 1) ? ', ':'') + 'x' + i; + decls += 'var x' + i + ' = i4(' + j++ + ', ' + j++ + ', ' + j++ + ', ' + j++ + ');\n'; + coerc += 'x' + i + ' = i4(x' + i + ');\n'; + last = 'x' + i; + var c = code.replace(/\[args\]/g, args) + .replace(/\[last\]/g, last) + .replace(/\[decls\]/i, decls) + .replace(/\[coerc\]/i, coerc) + .replace(/\[i\]/g, i); + asmLink(asmCompile('glob', 'ffi', c), this, ffi)(); +} + +// Stress-test for register spilling code and stack depth checks +var code = ` + "use asm"; + var i4 = glob.SIMD.int32x4; + var i4a = i4.add; + var assertEq = ffi.assertEq; + function g() { + var x = i4(1,2,3,4); + var y = i4(2,3,4,5); + var z = i4(0,0,0,0); + z = i4a(x, y); + assertEq(z.x | 0, 3); + assertEq(z.y | 0, 5); + assertEq(z.z | 0, 7); + assertEq(z.w | 0, 9); + } + return g +` +asmLink(asmCompile('glob', 'ffi', code), this, assertEqFFI)(); + +(function() { + var code = ` + "use asm"; + var i4 = glob.SIMD.int32x4; + var i4a = i4.add; + var assertEq = ffi.assertEq; + var one = ffi.one; + + // Function call with arguments on the stack (1 on x64, 3 on x86) + function h(x1, x2, x3, x4, x5, x6, x7) { + x1=x1|0 + x2=x2|0 + x3=x3|0 + x4=x4|0 + x5=x5|0 + x6=x6|0 + x7=x7|0 + return x1 + x2 |0 + } + + function g() { + var x = i4(1,2,3,4); + var y = i4(2,3,4,5); + var z = i4(0,0,0,0); + var w = 1; + z = i4a(x, y); + w = w + (one() | 0) | 0; + assertEq(z.x | 0, 3); + assertEq(z.y | 0, 5); + assertEq(z.z | 0, 7); + assertEq(z.w | 0, 9); + h(1, 2, 3, 4, 42, 42, 42)|0 + return w | 0; + } + return g + `; + + asmLink(asmCompile('glob', 'ffi', code), this, {assertEq: assertEq, one: () => 1})(); +})(); + +// Function calls with mixed arguments on the stack (SIMD and scalar). In the +// worst case (x64), we have 6 int arg registers and 8 float registers. +(function() { + var code = ` + "use asm"; + var i4 = glob.SIMD.int32x4; + function h( + // In registers: + gpr1, gpr2, gpr3, gpr4, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, + // On the stack: + sint1, ssimd1, sdouble1, ssimd2, sint2, sint3, sint4, ssimd3, sdouble2 + ) + { + gpr1=gpr1|0; + gpr2=gpr2|0; + gpr3=gpr3|0; + gpr4=gpr4|0; + + xmm1=+xmm1; + xmm2=+xmm2; + xmm3=+xmm3; + xmm4=+xmm4; + xmm5=+xmm5; + xmm6=+xmm6; + xmm7=+xmm7; + xmm8=+xmm8; + + sint1=sint1|0; + ssimd1=i4(ssimd1); + sdouble1=+sdouble1; + ssimd2=i4(ssimd2); + sint2=sint2|0; + sint3=sint3|0; + sint4=sint4|0; + ssimd3=i4(ssimd3); + sdouble2=+sdouble2; + + return (ssimd1.x|0) + (ssimd2.y|0) + (ssimd3.z|0) + sint2 + gpr3 | 0; + } + + function g() { + var simd1 = i4(1,2,3,4); + var simd2 = i4(5,6,7,8); + var simd3 = i4(9,10,11,12); + return h(1, 2, 3, 4, + 1., 2., 3., 4., 5., 6., 7., 8., + 5, simd1, 9., simd2, 6, 7, 8, simd3, 10.) | 0; + } + return g + `; + + assertEq(asmLink(asmCompile('glob', 'ffi', code), this)(), 1 + 6 + 11 + 6 + 3); +})(); + +// Check that the interrupt callback doesn't erase high components of simd +// registers: + +// WARNING: must be the last test in this file +(function() { + var iters = 2000000; + var code = ` + "use asm"; + var i4 = glob.SIMD.int32x4; + var i4a = i4.add; + function _() { + var i = 0; + var n = i4(0,0,0,0); + var one = i4(1,1,1,1); + for (; (i>>>0) < ` + iters + `; i=(i+1)>>>0) { + n = i4a(n, one); + } + return i4(n); + } + return _;`; + // This test relies on the fact that setting the timeout will call the + // interrupt callback at fixed intervals, even before the timeout. + timeout(1000); + var x4 = asmLink(asmCompile('glob', code), this)(); + assertEq(x4.x, iters); + assertEq(x4.y, iters); + assertEq(x4.z, iters); + assertEq(x4.w, iters); +})(); + +} catch(e) { + print('Stack:', e.stack) + print('Error:', e) + throw e; +} +
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -8817,22 +8817,25 @@ CodeGenerator::visitAsmJSCall(LAsmJSCall } } } #endif if (mir->spIncrement()) masm.freeStack(mir->spIncrement()); - JS_ASSERT((sizeof(AsmJSFrame) + masm.framePushed()) % StackAlignment == 0); + JS_ASSERT((sizeof(AsmJSFrame) + masm.framePushed()) % AsmJSStackAlignment == 0); #ifdef DEBUG + static_assert(AsmJSStackAlignment >= ABIStackAlignment, + "The asm.js stack alignment should subsume the ABI-required alignment"); + static_assert(AsmJSStackAlignment % ABIStackAlignment == 0, + "The asm.js stack alignment should subsume the ABI-required alignment"); Label ok; - JS_ASSERT(IsPowerOfTwo(StackAlignment)); - masm.branchTestPtr(Assembler::Zero, StackPointer, Imm32(StackAlignment - 1), &ok); + masm.branchTestPtr(Assembler::Zero, StackPointer, Imm32(AsmJSStackAlignment - 1), &ok); masm.breakpoint(); masm.bind(&ok); #endif MAsmJSCall::Callee callee = mir->callee(); switch (callee.which()) { case MAsmJSCall::Callee::Internal: masm.call(mir->desc(), callee.internal()); @@ -9061,17 +9064,17 @@ CodeGenerator::visitAsmJSInterruptCheck( { Register scratch = ToRegister(lir->scratch()); masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterrupt), scratch); masm.load8ZeroExtend(Address(scratch, 0), scratch); Label rejoin; masm.branch32(Assembler::Equal, scratch, Imm32(0), &rejoin); { uint32_t stackFixup = ComputeByteAlignment(masm.framePushed() + sizeof(AsmJSFrame), - StackAlignment); + ABIStackAlignment); masm.reserveStack(stackFixup); masm.call(lir->funcDesc(), lir->interruptExit()); masm.freeStack(stackFixup); } masm.bind(&rejoin); return true; }
--- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -3209,8 +3209,14 @@ AutoDebugModeInvalidation::~AutoDebugMod } } bool jit::JitSupportsFloatingPoint() { return js::jit::MacroAssembler::SupportsFloatingPoint(); } + +bool +jit::JitSupportsSimd() +{ + return js::jit::MacroAssembler::SupportsSimd(); +}
--- a/js/src/jit/Ion.h +++ b/js/src/jit/Ion.h @@ -202,13 +202,14 @@ void TraceIonScripts(JSTracer* trc, JSSc void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode); bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp); bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp, AutoDebugModeInvalidation &invalidate); bool JitSupportsFloatingPoint(); +bool JitSupportsSimd(); } // namespace jit } // namespace js #endif /* jit_Ion_h */
--- a/js/src/jit/IonFrames.cpp +++ b/js/src/jit/IonFrames.cpp @@ -1070,17 +1070,17 @@ JitActivationIterator::jitStackRange(uin end = reinterpret_cast<uintptr_t *>(frames.prevFp()); } #ifdef JS_CODEGEN_MIPS uint8_t * alignDoubleSpillWithOffset(uint8_t *pointer, int32_t offset) { uint32_t address = reinterpret_cast<uint32_t>(pointer); - address = (address - offset) & ~(StackAlignment - 1); + address = (address - offset) & ~(ABIStackAlignment - 1); return reinterpret_cast<uint8_t *>(address); } static void MarkJitExitFrameCopiedArguments(JSTracer *trc, const VMFunction *f, IonExitFooterFrame *footer) { uint8_t *doubleArgs = reinterpret_cast<uint8_t *>(footer); doubleArgs = alignDoubleSpillWithOffset(doubleArgs, sizeof(intptr_t));
--- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -1421,21 +1421,21 @@ class MacroAssembler : public MacroAssem } void icRestoreLive(RegisterSet &liveRegs, AfterICSaveLive &aic) { restoreFrameAlignmentForICArguments(aic); JS_ASSERT(framePushed() == aic.initialStack); PopRegsInMask(liveRegs); } - void assertStackAlignment() { + void assertStackAlignment(uint32_t alignment) { #ifdef DEBUG Label ok; - JS_ASSERT(IsPowerOfTwo(StackAlignment)); - branchTestPtr(Assembler::Zero, StackPointer, Imm32(StackAlignment - 1), &ok); + JS_ASSERT(IsPowerOfTwo(alignment)); + branchTestPtr(Assembler::Zero, StackPointer, Imm32(alignment - 1), &ok); breakpoint(); bind(&ok); #endif } }; static inline Assembler::DoubleCondition JSOpToDoubleCondition(JSOp op) @@ -1503,18 +1503,18 @@ JSOpToCondition(JSOp op, bool isSigned) return Assembler::AboveOrEqual; default: MOZ_CRASH("Unrecognized comparison operation"); } } } static inline size_t -StackDecrementForCall(size_t bytesAlreadyPushed, size_t bytesToPush) +StackDecrementForCall(uint32_t alignment, size_t bytesAlreadyPushed, size_t bytesToPush) { return bytesToPush + - ComputeByteAlignment(bytesAlreadyPushed + bytesToPush, StackAlignment); + ComputeByteAlignment(bytesAlreadyPushed + bytesToPush, alignment); } } // namespace jit } // namespace js #endif /* jit_IonMacroAssembler_h */
--- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -1581,20 +1581,20 @@ class LIRGraph } uint32_t localSlotCount() const { return localSlotCount_; } // Return the localSlotCount() value rounded up so that it satisfies the // platform stack alignment requirement, and so that it's a multiple of // the number of slots per Value. uint32_t paddedLocalSlotCount() const { - // Round to StackAlignment, but also round to at least sizeof(Value) in - // case that's greater, because StackOffsetOfPassedArg rounds argument - // slots to 8-byte boundaries. - size_t Alignment = Max(size_t(StackAlignment), sizeof(Value)); + // Round to ABIStackAlignment, but also round to at least sizeof(Value) + // in case that's greater, because StackOffsetOfPassedArg rounds + // argument slots to 8-byte boundaries. + size_t Alignment = Max(size_t(ABIStackAlignment), sizeof(Value)); return AlignBytes(localSlotCount(), Alignment); } size_t paddedLocalSlotsSize() const { return paddedLocalSlotCount(); } void setArgumentSlotCount(uint32_t argumentSlotCount) { argumentSlotCount_ = argumentSlotCount; }
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -3548,46 +3548,48 @@ LIRGenerator::visitAsmJSLoadFFIFunc(MAsm bool LIRGenerator::visitAsmJSParameter(MAsmJSParameter *ins) { ABIArg abi = ins->abi(); if (abi.argInRegister()) return defineFixed(new(alloc()) LAsmJSParameter, ins, LAllocation(abi.reg())); - JS_ASSERT(IsNumberType(ins->type())); + JS_ASSERT(IsNumberType(ins->type()) || IsSimdType(ins->type())); return defineFixed(new(alloc()) LAsmJSParameter, ins, LArgument(abi.offsetFromArgBase())); } bool LIRGenerator::visitAsmJSReturn(MAsmJSReturn *ins) { MDefinition *rval = ins->getOperand(0); LAsmJSReturn *lir = new(alloc()) LAsmJSReturn; if (rval->type() == MIRType_Float32) lir->setOperand(0, useFixed(rval, ReturnFloat32Reg)); else if (rval->type() == MIRType_Double) lir->setOperand(0, useFixed(rval, ReturnDoubleReg)); + else if (IsSimdType(rval->type())) + lir->setOperand(0, useFixed(rval, ReturnSimdReg)); else if (rval->type() == MIRType_Int32) lir->setOperand(0, useFixed(rval, ReturnReg)); else MOZ_ASSUME_UNREACHABLE("Unexpected asm.js return type"); return add(lir); } bool LIRGenerator::visitAsmJSVoidReturn(MAsmJSVoidReturn *ins) { return add(new(alloc()) LAsmJSVoidReturn); } bool LIRGenerator::visitAsmJSPassStackArg(MAsmJSPassStackArg *ins) { - if (IsFloatingPointType(ins->arg()->type())) { + if (IsFloatingPointType(ins->arg()->type()) || IsSimdType(ins->arg()->type())) { JS_ASSERT(!ins->arg()->isEmittedAtUses()); return add(new(alloc()) LAsmJSPassStackArg(useRegisterAtStart(ins->arg())), ins); } return add(new(alloc()) LAsmJSPassStackArg(useRegisterOrConstantAtStart(ins->arg())), ins); } bool
--- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -488,16 +488,17 @@ MConstant * MConstant::New(TempAllocator &alloc, const Value &v, types::CompilerConstraintList *constraints) { return new(alloc) MConstant(v, constraints); } MConstant * MConstant::NewAsmJS(TempAllocator &alloc, const Value &v, MIRType type) { + JS_ASSERT(!IsSimdType(type)); MConstant *constant = new(alloc) MConstant(v, nullptr); constant->setResultType(type); return constant; } MConstant * MConstant::NewConstraintlessObject(TempAllocator &alloc, JSObject *v) {
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -11182,17 +11182,17 @@ class MAsmJSStoreHeap : public MBinaryIn } }; class MAsmJSLoadGlobalVar : public MNullaryInstruction { MAsmJSLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant) : globalDataOffset_(globalDataOffset), isConstant_(isConstant) { - JS_ASSERT(IsNumberType(type)); + JS_ASSERT(IsNumberType(type) || IsSimdType(type)); setResultType(type); setMovable(); } unsigned globalDataOffset_; bool isConstant_; public:
--- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -140,27 +140,28 @@ static MOZ_CONSTEXPR_VAR FloatRegister d static MOZ_CONSTEXPR_VAR FloatRegister d14 = {FloatRegisters::d14, VFPRegister::Double}; static MOZ_CONSTEXPR_VAR FloatRegister d15 = {FloatRegisters::d15, VFPRegister::Double}; // For maximal awesomeness, 8 should be sufficent. ldrd/strd (dual-register // load/store) operate in a single cycle when the address they are dealing with // is 8 byte aligned. Also, the ARM abi wants the stack to be 8 byte aligned at // function boundaries. I'm trying to make sure this is always true. -static const uint32_t StackAlignment = 8; +static const uint32_t ABIStackAlignment = 8; static const uint32_t CodeAlignment = 8; -static const bool StackKeptAligned = true; // This boolean indicates whether we support SIMD instructions flavoured for // this architecture or not. Rather than a method in the LIRGenerator, it is // here such that it is accessible from the entire codebase. Once full support // for SIMD is reached on all tier-1 platforms, this constant can be deleted. static const bool SupportsSimd = false; static const uint32_t SimdStackAlignment = 8; +static const uint32_t AsmJSStackAlignment = SimdStackAlignment; + static const Scale ScalePointer = TimesFour; class Instruction; class InstBranchImm; uint32_t RM(Register r); uint32_t RS(Register r); uint32_t RD(Register r); uint32_t RT(Register r); @@ -1547,16 +1548,19 @@ class Assembler : public AssemblerShared public: static void TraceJumpRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader); static void TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader); static bool SupportsFloatingPoint() { return HasVFP(); } + static bool SupportsSimd() { + return js::jit::SupportsSimd; + } protected: void addPendingJump(BufferOffset src, ImmPtr target, Relocation::Kind kind) { enoughMemory_ &= jumps_.append(RelativePatch(target.value, kind)); if (kind == Relocation::JITCODE) writeRelocation(src); }
--- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -1875,17 +1875,17 @@ void MacroAssemblerARMCompat::freeStack(Register amount) { ma_add(amount, sp); } void MacroAssembler::PushRegsInMask(RegisterSet set, FloatRegisterSet simdSet) { - JS_ASSERT(!SupportsSimd && simdSet.size() == 0); + JS_ASSERT(!SupportsSimd() && simdSet.size() == 0); int32_t diffF = set.fpus().getPushSizeInBytes(); int32_t diffG = set.gprs().size() * sizeof(intptr_t); if (set.gprs().size() > 1) { adjustFrame(diffG); startDataTransferM(IsStore, StackPointer, DB, WriteBack); for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); iter++) { diffG -= sizeof(intptr_t); @@ -1904,17 +1904,17 @@ MacroAssembler::PushRegsInMask(RegisterS adjustFrame(diffF); diffF += transferMultipleByRuns(set.fpus(), IsStore, StackPointer, DB); JS_ASSERT(diffF == 0); } void MacroAssembler::PopRegsInMaskIgnore(RegisterSet set, RegisterSet ignore, FloatRegisterSet simdSet) { - JS_ASSERT(!SupportsSimd && simdSet.size() == 0); + JS_ASSERT(!SupportsSimd() && simdSet.size() == 0); int32_t diffG = set.gprs().size() * sizeof(intptr_t); int32_t diffF = set.fpus().getPushSizeInBytes(); const int32_t reservedG = diffG; const int32_t reservedF = diffF; // ARM can load multiple registers at once, but only if we want back all // the registers we previously saved to the stack. if (ignore.empty(true)) { @@ -3773,17 +3773,17 @@ void MacroAssemblerARMCompat::setupUnalignedABICall(uint32_t args, Register scratch) { setupABICall(args); dynamicAlignment_ = true; ma_mov(sp, scratch); // Force sp to be aligned. - ma_and(Imm32(~(StackAlignment - 1)), sp, sp); + ma_and(Imm32(~(ABIStackAlignment - 1)), sp, sp); ma_push(scratch); } #if defined(JS_CODEGEN_ARM_HARDFP) || defined(JS_ARM_SIMULATOR) void MacroAssemblerARMCompat::passHardFpABIArg(const MoveOperand &from, MoveOp::Type type) { MoveOperand to; @@ -3932,17 +3932,17 @@ void MacroAssemblerARMCompat::passABIArg(FloatRegister freg, MoveOp::Type type) { passABIArg(MoveOperand(freg), type); } void MacroAssemblerARMCompat::checkStackAlignment() { #ifdef DEBUG - ma_tst(sp, Imm32(StackAlignment - 1)); + ma_tst(sp, Imm32(ABIStackAlignment - 1)); breakpoint(NonZero); #endif } void MacroAssemblerARMCompat::callWithABIPre(uint32_t *stackAdjust, bool callFromAsmJS) { JS_ASSERT(inCall_); @@ -3951,21 +3951,21 @@ MacroAssemblerARMCompat::callWithABIPre( #if defined(JS_CODEGEN_ARM_HARDFP) || defined(JS_ARM_SIMULATOR) if (UseHardFpABI()) *stackAdjust += 2*((usedFloatSlots_ > NumFloatArgRegs) ? usedFloatSlots_ - NumFloatArgRegs : 0) * sizeof(intptr_t); #endif uint32_t alignmentAtPrologue = callFromAsmJS ? sizeof(AsmJSFrame) : 0; if (!dynamicAlignment_) { *stackAdjust += ComputeByteAlignment(framePushed_ + *stackAdjust + alignmentAtPrologue, - StackAlignment); + ABIStackAlignment); } else { // sizeof(intptr_t) accounts for the saved stack pointer pushed by // setupUnalignedABICall. - *stackAdjust += ComputeByteAlignment(*stackAdjust + sizeof(intptr_t), StackAlignment); + *stackAdjust += ComputeByteAlignment(*stackAdjust + sizeof(intptr_t), ABIStackAlignment); } reserveStack(*stackAdjust); // Position all arguments. { enoughMemory_ = enoughMemory_ && moveResolver_.resolve(); if (!enoughMemory_)
--- a/js/src/jit/arm/Simulator-arm.cpp +++ b/js/src/jit/arm/Simulator-arm.cpp @@ -2112,17 +2112,17 @@ Simulator::softwareInterrupt(SimInstruct int32_t arg3 = get_register(r3); int32_t *stack_pointer = reinterpret_cast<int32_t*>(get_register(sp)); int32_t arg4 = stack_pointer[0]; int32_t arg5 = stack_pointer[1]; int32_t saved_lr = get_register(lr); intptr_t external = reinterpret_cast<intptr_t>(redirection->nativeFunction()); - bool stack_aligned = (get_register(sp) & (StackAlignment - 1)) == 0; + bool stack_aligned = (get_register(sp) & (ABIStackAlignment - 1)) == 0; if (!stack_aligned) { fprintf(stderr, "Runtime call with unaligned stack!\n"); MOZ_CRASH(); } if (single_stepping_) single_step_callback_(single_step_callback_arg_, this, nullptr); @@ -4253,17 +4253,17 @@ Simulator::call(uint8_t* entry, int argu set_register(r3, va_arg(parameters, int32_t)); // Remaining arguments passed on stack. int original_stack = get_register(sp); int entry_stack = original_stack; if (argument_count >= 4) entry_stack -= (argument_count - 4) * sizeof(int32_t); - entry_stack &= ~StackAlignment; + entry_stack &= ~ABIStackAlignment; // Store remaining arguments on stack, from low to high memory. intptr_t *stack_argument = reinterpret_cast<intptr_t*>(entry_stack); for (int i = 4; i < argument_count; i++) stack_argument[i - 4] = va_arg(parameters, int32_t); va_end(parameters); set_register(sp, entry_stack);
--- a/js/src/jit/mips/Assembler-mips.h +++ b/js/src/jit/mips/Assembler-mips.h @@ -153,29 +153,30 @@ static MOZ_CONSTEXPR_VAR FloatRegister f static MOZ_CONSTEXPR_VAR FloatRegister f22 = { FloatRegisters::f22, FloatRegister::Double }; static MOZ_CONSTEXPR_VAR FloatRegister f24 = { FloatRegisters::f24, FloatRegister::Double }; static MOZ_CONSTEXPR_VAR FloatRegister f26 = { FloatRegisters::f26, FloatRegister::Double }; static MOZ_CONSTEXPR_VAR FloatRegister f28 = { FloatRegisters::f28, FloatRegister::Double }; static MOZ_CONSTEXPR_VAR FloatRegister f30 = { FloatRegisters::f30, FloatRegister::Double }; // MIPS CPUs can only load multibyte data that is "naturally" // four-byte-aligned, sp register should be eight-byte-aligned. -static const uint32_t StackAlignment = 8; +static const uint32_t ABIStackAlignment = 8; static const uint32_t CodeAlignment = 4; -static const bool StackKeptAligned = true; // This boolean indicates whether we support SIMD instructions flavoured for // this architecture or not. Rather than a method in the LIRGenerator, it is // here such that it is accessible from the entire codebase. Once full support // for SIMD is reached on all tier-1 platforms, this constant can be deleted. static const bool SupportsSimd = false; // TODO this is just a filler to prevent a build failure. The MIPS SIMD // alignment requirements still need to be explored. static const uint32_t SimdStackAlignment = 8; +static const uint32_t AsmJSStackAlignment = SimdStackAlignment; + static const Scale ScalePointer = TimesFour; // MIPS instruction types // +---------------------------------------------------------------+ // | 6 | 5 | 5 | 5 | 5 | 6 | // +---------------------------------------------------------------+ // Register type | Opcode | Rs | Rt | Rd | Sa | Function | // +---------------------------------------------------------------+ @@ -233,17 +234,16 @@ static const uint32_t Imm16Mask = ((1 << static const uint32_t Imm26Mask = ((1 << Imm26Bits) - 1) << Imm26Shift; static const uint32_t Imm28Mask = ((1 << Imm28Bits) - 1) << Imm28Shift; static const uint32_t RSMask = ((1 << RSBits) - 1) << RSShift; static const uint32_t RTMask = ((1 << RTBits) - 1) << RTShift; static const uint32_t RDMask = ((1 << RDBits) - 1) << RDShift; static const uint32_t SAMask = ((1 << SABits) - 1) << SAShift; static const uint32_t FunctionMask = ((1 << FunctionBits) - 1) << FunctionShift; static const uint32_t RegMask = Registers::Total - 1; -static const uint32_t StackAlignmentMask = StackAlignment - 1; static const uint32_t MAX_BREAK_CODE = 1024 - 1; class Instruction; class InstReg; class InstImm; class InstJump; class BranchInstBlock;
--- a/js/src/jit/mips/MacroAssembler-mips.cpp +++ b/js/src/jit/mips/MacroAssembler-mips.cpp @@ -1569,17 +1569,17 @@ MacroAssembler::PushRegsInMask(RegisterS diffG -= sizeof(intptr_t); storePtr(*iter, Address(StackPointer, diffG)); } MOZ_ASSERT(diffG == 0); // Double values have to be aligned. We reserve extra space so that we can // start writing from the first aligned location. // We reserve a whole extra double so that the buffer has even size. - ma_and(SecondScratchReg, sp, Imm32(~(StackAlignment - 1))); + ma_and(SecondScratchReg, sp, Imm32(~(ABIStackAlignment - 1))); reserveStack(diffF + sizeof(double)); for (FloatRegisterForwardIterator iter(set.fpus().reduceSetForPush()); iter.more(); iter++) { if ((*iter).code() % 2 == 0) as_sd(*iter, SecondScratchReg, -diffF); diffF -= sizeof(double); } MOZ_ASSERT(diffF == 0); @@ -1591,17 +1591,17 @@ MacroAssembler::PopRegsInMaskIgnore(Regi JS_ASSERT(!SupportsSimd && simdSet.size() == 0); int32_t diffG = set.gprs().size() * sizeof(intptr_t); int32_t diffF = set.fpus().getPushSizeInBytes(); const int32_t reservedG = diffG; const int32_t reservedF = diffF; // Read the buffer form the first aligned location. ma_addu(SecondScratchReg, sp, Imm32(reservedF + sizeof(double))); - ma_and(SecondScratchReg, SecondScratchReg, Imm32(~(StackAlignment - 1))); + ma_and(SecondScratchReg, SecondScratchReg, Imm32(~(ABIStackAlignment - 1))); for (FloatRegisterForwardIterator iter(set.fpus().reduceSetForPush()); iter.more(); iter++) { if (!ignore.has(*iter) && ((*iter).code() % 2 == 0)) // Use assembly l.d because we have alligned the stack. as_ld(*iter, SecondScratchReg, -diffF); diffF -= sizeof(double); } freeStack(reservedF + sizeof(double)); @@ -3153,17 +3153,17 @@ MacroAssemblerMIPSCompat::setupUnaligned { setupABICall(args); dynamicAlignment_ = true; ma_move(scratch, StackPointer); // Force sp to be aligned ma_subu(StackPointer, StackPointer, Imm32(sizeof(uint32_t))); - ma_and(StackPointer, StackPointer, Imm32(~(StackAlignment - 1))); + ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1))); as_sw(scratch, StackPointer, 0); } void MacroAssemblerMIPSCompat::passABIArg(const MoveOperand &from, MoveOp::Type type) { ++passedArgs_; if (!enoughMemory_) @@ -3254,48 +3254,48 @@ MacroAssemblerMIPSCompat::passABIArg(Flo passABIArg(MoveOperand(freg), type); } void MacroAssemblerMIPSCompat::checkStackAlignment() { #ifdef DEBUG Label aligned; - as_andi(ScratchRegister, sp, StackAlignment - 1); + as_andi(ScratchRegister, sp, ABIStackAlignment - 1); ma_b(ScratchRegister, zero, &aligned, Equal, ShortJump); as_break(MAX_BREAK_CODE); bind(&aligned); #endif } void MacroAssemblerMIPSCompat::alignStackPointer() { movePtr(StackPointer, SecondScratchReg); subPtr(Imm32(sizeof(uintptr_t)), StackPointer); - andPtr(Imm32(~(StackAlignment - 1)), StackPointer); + andPtr(Imm32(~(ABIStackAlignment - 1)), StackPointer); storePtr(SecondScratchReg, Address(StackPointer, 0)); } void MacroAssemblerMIPSCompat::restoreStackPointer() { loadPtr(Address(StackPointer, 0), StackPointer); } void MacroAssembler::alignFrameForICArguments(AfterICSaveLive &aic) { - if (framePushed() % StackAlignment != 0) { - aic.alignmentPadding = StackAlignment - (framePushed() % StackAlignment); + if (framePushed() % ABIStackAlignment != 0) { + aic.alignmentPadding = ABIStackAlignment - (framePushed() % StackAlignment); reserveStack(aic.alignmentPadding); } else { aic.alignmentPadding = 0; } - MOZ_ASSERT(framePushed() % StackAlignment == 0); + MOZ_ASSERT(framePushed() % ABIStackAlignment == 0); checkStackAlignment(); } void MacroAssembler::restoreFrameAlignmentForICArguments(AfterICSaveLive &aic) { if (aic.alignmentPadding != 0) freeStack(aic.alignmentPadding); @@ -3311,20 +3311,20 @@ MacroAssemblerMIPSCompat::callWithABIPre *stackAdjust += usedArgSlots_ > NumIntArgRegs ? usedArgSlots_ * sizeof(intptr_t) : NumIntArgRegs * sizeof(intptr_t); uint32_t alignmentAtPrologue = callFromAsmJS ? sizeof(AsmJSFrame) : 0; if (dynamicAlignment_) { - *stackAdjust += ComputeByteAlignment(*stackAdjust, StackAlignment); + *stackAdjust += ComputeByteAlignment(*stackAdjust, ABIStackAlignment); } else { *stackAdjust += ComputeByteAlignment(framePushed_ + alignmentAtPrologue + *stackAdjust, - StackAlignment); + ABIStackAlignment); } reserveStack(*stackAdjust); // Save $ra because call is going to clobber it. Restore it in // callWithABIPost. NOTE: This is needed for calls from BaselineIC. // Maybe we can do this differently. ma_sw(ra, Address(StackPointer, *stackAdjust - sizeof(intptr_t))); @@ -3439,17 +3439,17 @@ MacroAssemblerMIPSCompat::callWithABI(co callWithABIPost(stackAdjust, result); } void MacroAssemblerMIPSCompat::handleFailureWithHandler(void *handler) { // Reserve space for exception information. - int size = (sizeof(ResumeFromException) + StackAlignment) & ~(StackAlignment - 1); + int size = (sizeof(ResumeFromException) + ABIStackAlignment) & ~(ABIStackAlignment - 1); ma_subu(StackPointer, StackPointer, Imm32(size)); ma_move(a0, StackPointer); // Use a0 since it is a first function argument // Ask for an exception handler. setupUnalignedABICall(1, a1); passABIArg(a0); callWithABI(handler);
--- a/js/src/jit/mips/Simulator-mips.cpp +++ b/js/src/jit/mips/Simulator-mips.cpp @@ -1866,17 +1866,17 @@ Simulator::softwareInterrupt(SimInstruct int32_t arg5 = stack_pointer[5]; // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_ra = getRegister(ra); intptr_t external = reinterpret_cast<intptr_t>(redirection->nativeFunction()); - bool stack_aligned = (getRegister(sp) & (StackAlignment - 1)) == 0; + bool stack_aligned = (getRegister(sp) & (ABIStackAlignment - 1)) == 0; if (!stack_aligned) { fprintf(stderr, "Runtime call with unaligned stack!\n"); MOZ_CRASH(); } switch (redirection->type()) { case Args_General0: { Prototype_General0 target = reinterpret_cast<Prototype_General0>(external); @@ -3400,17 +3400,17 @@ Simulator::call(uint8_t *entry, int argu int original_stack = getRegister(sp); // Compute position of stack on entry to generated code. int entry_stack = original_stack; if (argument_count > kCArgSlotCount) entry_stack = entry_stack - argument_count * sizeof(int32_t); else entry_stack = entry_stack - kCArgsSlotsSize; - entry_stack &= ~StackAlignment; + entry_stack &= ~ABIStackAlignment; intptr_t *stack_argument = reinterpret_cast<intptr_t*>(entry_stack); // Setup the arguments. for (int i = 0; i < argument_count; i++) { js::jit::Register argReg; if (GetIntArgReg(i, &argReg)) setRegister(argReg.code(), va_arg(parameters, int32_t));
--- a/js/src/jit/none/Architecture-none.h +++ b/js/src/jit/none/Architecture-none.h @@ -11,16 +11,17 @@ // platforms, so include it here to avoid inadvertent build bustage. #include "jit/IonSpewer.h" namespace js { namespace jit { static const bool SupportsSimd = false; static const uint32_t SimdStackAlignment = 0; +static const uint32_t AsmJSStackAlignment = 0; class Registers { public: typedef uint8_t Code; typedef uint8_t SetType; static uint32_t SetSize(SetType) { MOZ_CRASH(); }
--- a/js/src/jit/none/MacroAssembler-none.h +++ b/js/src/jit/none/MacroAssembler-none.h @@ -63,19 +63,18 @@ static MOZ_CONSTEXPR_VAR Register JSRetu #if defined(JS_NUNBOX32) static MOZ_CONSTEXPR_VAR ValueOperand JSReturnOperand(InvalidReg, InvalidReg); #elif defined(JS_PUNBOX64) static MOZ_CONSTEXPR_VAR ValueOperand JSReturnOperand(InvalidReg); #else #error "Bad architecture" #endif -static const uint32_t StackAlignment = 8; +static const uint32_t ABIStackAlignment = 4; static const uint32_t CodeAlignment = 4; -static const bool StackKeptAligned = false; static const Scale ScalePointer = TimesOne; class Assembler : public AssemblerShared { public: enum Condition { Equal,
--- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -635,19 +635,19 @@ class CallSite : public CallSiteDesc // address on all archs (whether or not the call instruction pushes the // return address (x86/x64) or the prologue does (ARM/MIPS)). uint32_t stackDepth() const { return stackDepth_; } }; typedef Vector<CallSite, 0, SystemAllocPolicy> CallSiteVector; // As an invariant across architectures, within asm.js code: -// $sp % StackAlignment = (sizeof(AsmJSFrame) + masm.framePushed) % StackAlignment +// $sp % AsmJSStackAlignment = (sizeof(AsmJSFrame) + masm.framePushed) % AsmJSStackAlignment // Thus, AsmJSFrame represents the bytes pushed after the call (which occurred -// with a StackAlignment-aligned StackPointer) that are not included in +// with a AsmJSStackAlignment-aligned StackPointer) that are not included in // masm.framePushed. struct AsmJSFrame { // The caller's saved frame pointer. In non-profiling mode, internal // asm.js-to-asm.js calls don't update fp and thus don't save the caller's // frame pointer; the space is reserved, however, so that profiling mode can // reuse the same function body without recompiling. uint8_t *callerFP;
--- a/js/src/jit/shared/Assembler-x86-shared.h +++ b/js/src/jit/shared/Assembler-x86-shared.h @@ -921,16 +921,17 @@ class AssemblerX86Shared : public Assemb } #ifdef DEBUG static bool HasSSE2() { return CPUInfo::IsSSE2Present(); } #endif static bool HasSSE3() { return CPUInfo::IsSSE3Present(); } static bool HasSSE41() { return CPUInfo::IsSSE41Present(); } static bool SupportsFloatingPoint() { return CPUInfo::IsSSE2Present(); } + static bool SupportsSimd() { return CPUInfo::IsSSE2Present(); } // The below cmpl 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 cmpl(Register lhs, Register rhs) { masm.cmpl_rr(rhs.code(), lhs.code()); } void cmpl(Register lhs, const Operand &rhs) {
--- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -2973,16 +2973,31 @@ public: void movss_rm(XMMRegisterID src, const void* address) { spew("movss %s, %p", nameFPReg(src), address); m_formatter.prefix(PRE_SSE_F3); m_formatter.twoByteOp(OP2_MOVSD_WsdVsd, (RegisterID)src, address); } + + void movdqa_rm(XMMRegisterID src, const void* address) + { + spew("movdqa %s, %p", + nameFPReg(src), address); + m_formatter.prefix(PRE_SSE_66); + m_formatter.twoByteOp(OP2_MOVDQ_WdqVdq, (RegisterID)src, address); + } + + void movaps_rm(XMMRegisterID src, const void* address) + { + spew("movaps %s, %p", + nameFPReg(src), address); + m_formatter.twoByteOp(OP2_MOVPS_WpsVps, (RegisterID)src, address); + } #else JmpSrc movsd_ripr(XMMRegisterID dst) { spew("movsd ?(%%rip), %s", nameFPReg(dst)); m_formatter.prefix(PRE_SSE_F2); m_formatter.twoByteRipOp(OP2_MOVSD_VsdWsd, (RegisterID)dst, 0); return JmpSrc(m_formatter.size()); @@ -2998,16 +3013,39 @@ public: JmpSrc movsd_rrip(XMMRegisterID src) { spew("movsd %s, ?(%%rip)", nameFPReg(src)); m_formatter.prefix(PRE_SSE_F2); m_formatter.twoByteRipOp(OP2_MOVSD_WsdVsd, (RegisterID)src, 0); return JmpSrc(m_formatter.size()); } + JmpSrc movss_rrip(XMMRegisterID src) + { + spew("movss %s, ?(%%rip)", + nameFPReg(src)); + m_formatter.prefix(PRE_SSE_F3); + m_formatter.twoByteRipOp(OP2_MOVSD_WsdVsd, (RegisterID)src, 0); + return JmpSrc(m_formatter.size()); + } + JmpSrc movdqa_rrip(XMMRegisterID src) + { + spew("movdqa %s, ?(%%rip)", + nameFPReg(src)); + m_formatter.prefix(PRE_SSE_66); + m_formatter.twoByteRipOp(OP2_MOVDQ_WdqVdq, (RegisterID)src, 0); + return JmpSrc(m_formatter.size()); + } + JmpSrc movaps_rrip(XMMRegisterID src) + { + spew("movaps %s, ?(%%rip)", + nameFPReg(src)); + m_formatter.twoByteRipOp(OP2_MOVPS_WpsVps, (RegisterID)src, 0); + return JmpSrc(m_formatter.size()); + } #endif void movaps_rr(XMMRegisterID src, XMMRegisterID dst) { spew("movaps %s, %s", nameFPReg(src), nameFPReg(dst)); m_formatter.twoByteOp(OP2_MOVAPS_VsdWsd, (RegisterID)dst, (RegisterID)src); }
--- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -64,76 +64,44 @@ CodeGeneratorShared::CodeGeneratorShared checkOsiPointRegisters(js_JitOptions.checkOsiPointRegisters), #endif frameDepth_(graph->paddedLocalSlotsSize() + graph->argumentsSize()), frameInitialAdjustment_(0) { if (!gen->compilingAsmJS()) masm.setInstrumentation(&sps_); - // Since asm.js uses the system ABI which does not necessarily use a - // regular array where all slots are sizeof(Value), it maintains the max - // argument stack depth separately. if (gen->compilingAsmJS()) { + // Since asm.js uses the system ABI which does not necessarily use a + // regular array where all slots are sizeof(Value), it maintains the max + // argument stack depth separately. JS_ASSERT(graph->argumentSlotCount() == 0); frameDepth_ += gen->maxAsmJSStackArgBytes(); + // If the function uses any SIMD, we may need to insert padding so that + // local slots are aligned for SIMD. + if (gen->usesSimd()) { + frameInitialAdjustment_ = ComputeByteAlignment(sizeof(AsmJSFrame), AsmJSStackAlignment); + frameDepth_ += frameInitialAdjustment_; + } + // An MAsmJSCall does not align the stack pointer at calls sites but instead - // relies on the a priori stack adjustment (in the prologue) on platforms - // (like x64) which require the stack to be aligned. - if (StackKeptAligned || gen->performsCall() || gen->usesSimd()) { - unsigned alignmentAtCall = sizeof(AsmJSFrame) + frameDepth_; - unsigned firstFixup = 0; - if (unsigned rem = alignmentAtCall % StackAlignment) - frameDepth_ += (firstFixup = StackAlignment - rem); - - if (gen->usesSimd()) - setupSimdAlignment(firstFixup); - } + // relies on the a priori stack adjustment. This must be the last + // adjustment of frameDepth_. + if (gen->performsCall()) + frameDepth_ += ComputeByteAlignment(sizeof(AsmJSFrame) + frameDepth_, AsmJSStackAlignment); // FrameSizeClass is only used for bailing, which cannot happen in // asm.js code. frameClass_ = FrameSizeClass::None(); } else { frameClass_ = FrameSizeClass::FromDepth(frameDepth_); } } -void -CodeGeneratorShared::setupSimdAlignment(unsigned fixup) -{ - JS_STATIC_ASSERT(SimdStackAlignment % StackAlignment == 0); - // At this point, we have: - // (frameDepth_ + sizeof(AsmJSFrame)) % StackAlignment == 0 - // which means we can add as many SimdStackAlignment as needed. - - // The next constraint is to have all stack slots - // aligned for SIMD. That's done by having the first stack slot - // aligned. We need an offset such that: - // (frameDepth_ - offset) % SimdStackAlignment == 0 - frameInitialAdjustment_ = frameDepth_ % SimdStackAlignment; - - // We need to ensure that the first stack slot is actually - // located in this frame and not beforehand, when taking this - // offset into account, i.e.: - // frameDepth_ - initial adjustment >= frameDepth_ - fixup - // <=> fixup >= initial adjustment - // - // For instance, on x86 with gcc, if the initial frameDepth - // % 16 is 8, then the fixup is 0, although the initial - // adjustment is 8. The first stack slot would be located at - // frameDepth - 8 in this case, which is obviously before - // frameDepth. - // - // If that's not the case, we add SimdStackAlignment to the - // fixup, which will keep on satisfying other constraints. - if (frameInitialAdjustment_ > int32_t(fixup)) - frameDepth_ += SimdStackAlignment; -} - bool CodeGeneratorShared::generateOutOfLineCode() { JSScript *topScript = sps_.getPushed(); for (size_t i = 0; i < outOfLineCode_.length(); i++) { // Add native => bytecode mapping entries for OOL sites. // Not enabled on asm.js yet since asm doesn't contain bytecode mappings. if (!gen->compilingAsmJS()) {
--- a/js/src/jit/shared/CodeGenerator-shared.h +++ b/js/src/jit/shared/CodeGenerator-shared.h @@ -491,18 +491,16 @@ class CodeGeneratorShared : public LInst // This function is not used for MIPS. MIPS has branchToBlock. #ifndef JS_CODEGEN_MIPS void jumpToBlock(MBasicBlock *mir, Assembler::Condition cond); #endif private: void generateInvalidateEpilogue(); - void setupSimdAlignment(unsigned fixup); - public: CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm); public: template <class ArgSeq, class StoreOutputTo> bool visitOutOfLineCallVM(OutOfLineCallVM<ArgSeq, StoreOutputTo> *ool); bool visitOutOfLineTruncateSlow(OutOfLineTruncateSlow *ool);
--- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -314,20 +314,36 @@ CodeGeneratorX86Shared::visitCompareFAnd bool CodeGeneratorX86Shared::visitAsmJSPassStackArg(LAsmJSPassStackArg *ins) { const MAsmJSPassStackArg *mir = ins->mir(); Address dst(StackPointer, mir->spOffset()); if (ins->arg()->isConstant()) { masm.storePtr(ImmWord(ToInt32(ins->arg())), dst); } else { - if (ins->arg()->isGeneralReg()) + if (ins->arg()->isGeneralReg()) { masm.storePtr(ToRegister(ins->arg()), dst); - else - masm.storeDouble(ToFloatRegister(ins->arg()), dst); + } else { + switch (mir->input()->type()) { + case MIRType_Double: + case MIRType_Float32: + masm.storeDouble(ToFloatRegister(ins->arg()), dst); + return true; + // StackPointer is SimdStackAlignment-aligned and ABIArgGenerator guarantees stack + // offsets are SimdStackAlignment-aligned. + case MIRType_Int32x4: + masm.storeAlignedInt32x4(ToFloatRegister(ins->arg()), dst); + return true; + case MIRType_Float32x4: + masm.storeAlignedFloat32x4(ToFloatRegister(ins->arg()), dst); + return true; + default: break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected mir type in AsmJSPassStackArg"); + } } return true; } bool CodeGeneratorX86Shared::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds *ool) { if (ool->dest().isFloat()) {
--- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -149,16 +149,22 @@ LIRGeneratorShared::defineReturn(LInstru #endif break; case MIRType_Float32: lir->setDef(0, LDefinition(vreg, LDefinition::FLOAT32, LFloatReg(ReturnFloat32Reg))); break; case MIRType_Double: lir->setDef(0, LDefinition(vreg, LDefinition::DOUBLE, LFloatReg(ReturnDoubleReg))); break; + case MIRType_Int32x4: + lir->setDef(0, LDefinition(vreg, LDefinition::INT32X4, LFloatReg(ReturnSimdReg))); + break; + case MIRType_Float32x4: + lir->setDef(0, LDefinition(vreg, LDefinition::FLOAT32X4, LFloatReg(ReturnSimdReg))); + break; default: LDefinition::Type type = LDefinition::TypeFrom(mir->type()); JS_ASSERT(type != LDefinition::DOUBLE && type != LDefinition::FLOAT32); lir->setDef(0, LDefinition(vreg, type, LGeneralReg(ReturnReg))); break; } mir->setVirtualRegister(vreg);
--- a/js/src/jit/x64/Assembler-x64.cpp +++ b/js/src/jit/x64/Assembler-x64.cpp @@ -25,29 +25,45 @@ ABIArgGenerator::ABIArgGenerator() {} ABIArg ABIArgGenerator::next(MIRType type) { #if defined(XP_WIN) JS_STATIC_ASSERT(NumIntArgRegs == NumFloatArgRegs); if (regIndex_ == NumIntArgRegs) { - current_ = ABIArg(stackOffset_); - stackOffset_ += sizeof(uint64_t); + if (IsSimdType(type)) { + // On Win64, >64 bit args need to be passed by reference, but asm.js + // doesn't allow passing SIMD values to FFIs. The only way to reach + // here is asm to asm calls, so we can break the ABI here. + stackOffset_ = AlignBytes(stackOffset_, SimdStackAlignment); + current_ = ABIArg(stackOffset_); + stackOffset_ += Simd128DataSize; + } else { + stackOffset_ += sizeof(uint64_t); + current_ = ABIArg(stackOffset_); + } return current_; } switch (type) { case MIRType_Int32: case MIRType_Pointer: current_ = ABIArg(IntArgRegs[regIndex_++]); break; case MIRType_Float32: case MIRType_Double: current_ = ABIArg(FloatArgRegs[regIndex_++]); break; + case MIRType_Int32x4: + case MIRType_Float32x4: + // On Win64, >64 bit args need to be passed by reference, but asm.js + // doesn't allow passing SIMD values to FFIs. The only way to reach + // here is asm to asm calls, so we can break the ABI here. + current_ = ABIArg(FloatArgRegs[regIndex_++]); + break; default: MOZ_CRASH("Unexpected argument type"); } return current_; #else switch (type) { case MIRType_Int32: case MIRType_Pointer: @@ -62,16 +78,26 @@ ABIArgGenerator::next(MIRType type) case MIRType_Float32: if (floatRegIndex_ == NumFloatArgRegs) { current_ = ABIArg(stackOffset_); stackOffset_ += sizeof(uint64_t); break; } current_ = ABIArg(FloatArgRegs[floatRegIndex_++]); break; + case MIRType_Int32x4: + case MIRType_Float32x4: + if (floatRegIndex_ == NumFloatArgRegs) { + stackOffset_ = AlignBytes(stackOffset_, SimdStackAlignment); + current_ = ABIArg(stackOffset_); + stackOffset_ += Simd128DataSize; + break; + } + current_ = ABIArg(FloatArgRegs[floatRegIndex_++]); + break; default: MOZ_CRASH("Unexpected argument type"); } return current_; #endif } // Avoid r11, which is the MacroAssembler's ScratchReg.
--- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -179,29 +179,28 @@ class ABIArgGenerator static const Register NonArg_VolatileReg; static const Register NonReturn_VolatileReg0; }; static MOZ_CONSTEXPR_VAR Register OsrFrameReg = IntArgReg3; static MOZ_CONSTEXPR_VAR Register PreBarrierReg = rdx; -// GCC stack is aligned on 16 bytes, but we don't maintain the invariant in -// jitted code. -static const uint32_t StackAlignment = 16; -static const bool StackKeptAligned = false; +static const uint32_t ABIStackAlignment = 16; static const uint32_t CodeAlignment = 8; // This boolean indicates whether we support SIMD instructions flavoured for // this architecture or not. Rather than a method in the LIRGenerator, it is // here such that it is accessible from the entire codebase. Once full support // for SIMD is reached on all tier-1 platforms, this constant can be deleted. static const bool SupportsSimd = true; static const uint32_t SimdStackAlignment = 16; +static const uint32_t AsmJSStackAlignment = SimdStackAlignment; + static const Scale ScalePointer = TimesEight; } // namespace jit } // namespace js #include "jit/shared/Assembler-x86-shared.h" namespace js { @@ -598,22 +597,40 @@ class Assembler : public AssemblerX86Sha return CodeOffsetLabel(masm.movl_ripr(dest.code()).offset()); } CodeOffsetLabel loadRipRelativeInt64(Register dest) { return CodeOffsetLabel(masm.movq_ripr(dest.code()).offset()); } CodeOffsetLabel loadRipRelativeDouble(FloatRegister dest) { return CodeOffsetLabel(masm.movsd_ripr(dest.code()).offset()); } + CodeOffsetLabel loadRipRelativeFloat32(FloatRegister dest) { + return CodeOffsetLabel(masm.movss_ripr(dest.code()).offset()); + } + CodeOffsetLabel loadRipRelativeInt32x4(FloatRegister dest) { + return CodeOffsetLabel(masm.movdqa_ripr(dest.code()).offset()); + } + CodeOffsetLabel loadRipRelativeFloat32x4(FloatRegister dest) { + return CodeOffsetLabel(masm.movaps_ripr(dest.code()).offset()); + } CodeOffsetLabel storeRipRelativeInt32(Register dest) { return CodeOffsetLabel(masm.movl_rrip(dest.code()).offset()); } CodeOffsetLabel storeRipRelativeDouble(FloatRegister dest) { return CodeOffsetLabel(masm.movsd_rrip(dest.code()).offset()); } + CodeOffsetLabel storeRipRelativeFloat32(FloatRegister dest) { + return CodeOffsetLabel(masm.movss_rrip(dest.code()).offset()); + } + CodeOffsetLabel storeRipRelativeInt32x4(FloatRegister dest) { + return CodeOffsetLabel(masm.movdqa_rrip(dest.code()).offset()); + } + CodeOffsetLabel storeRipRelativeFloat32x4(FloatRegister dest) { + return CodeOffsetLabel(masm.movaps_rrip(dest.code()).offset()); + } CodeOffsetLabel leaRipRelative(Register dest) { return CodeOffsetLabel(masm.leaq_rip(dest.code()).offset()); } void loadAsmJSActivation(Register dest) { CodeOffsetLabel label = loadRipRelativeInt64(dest); append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset)); }
--- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -344,38 +344,77 @@ CodeGeneratorX64::visitAsmJSStoreHeap(LA return true; } bool CodeGeneratorX64::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins) { MAsmJSLoadGlobalVar *mir = ins->mir(); + MIRType type = mir->type(); + JS_ASSERT(IsNumberType(type) || IsSimdType(type)); + CodeOffsetLabel label; - if (mir->type() == MIRType_Int32) + switch (type) { + case MIRType_Int32: label = masm.loadRipRelativeInt32(ToRegister(ins->output())); - else + break; + case MIRType_Float32: + label = masm.loadRipRelativeFloat32(ToFloatRegister(ins->output())); + break; + case MIRType_Double: label = masm.loadRipRelativeDouble(ToFloatRegister(ins->output())); + break; + // Aligned access: code is aligned on PageSize + there is padding + // before the global data section. + case MIRType_Int32x4: + label = masm.loadRipRelativeInt32x4(ToFloatRegister(ins->output())); + break; + case MIRType_Float32x4: + label = masm.loadRipRelativeFloat32x4(ToFloatRegister(ins->output())); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected type in visitAsmJSLoadGlobalVar"); + } + masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset())); return true; } bool CodeGeneratorX64::visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar *ins) { MAsmJSStoreGlobalVar *mir = ins->mir(); MIRType type = mir->value()->type(); - JS_ASSERT(IsNumberType(type)); + JS_ASSERT(IsNumberType(type) || IsSimdType(type)); CodeOffsetLabel label; - if (type == MIRType_Int32) + switch (type) { + case MIRType_Int32: label = masm.storeRipRelativeInt32(ToRegister(ins->value())); - else + break; + case MIRType_Float32: + label = masm.storeRipRelativeFloat32(ToFloatRegister(ins->value())); + break; + case MIRType_Double: label = masm.storeRipRelativeDouble(ToFloatRegister(ins->value())); + break; + // Aligned access: code is aligned on PageSize + there is padding + // before the global data section. + case MIRType_Int32x4: + label = masm.storeRipRelativeInt32x4(ToFloatRegister(ins->value())); + break; + case MIRType_Float32x4: + label = masm.storeRipRelativeFloat32x4(ToFloatRegister(ins->value())); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected type in visitAsmJSStoreGlobalVar"); + } + masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset())); return true; } bool CodeGeneratorX64::visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr *ins) { MAsmJSLoadFuncPtr *mir = ins->mir();
--- a/js/src/jit/x64/MacroAssembler-x64.cpp +++ b/js/src/jit/x64/MacroAssembler-x64.cpp @@ -195,17 +195,17 @@ MacroAssemblerX64::setupAlignedABICall(u void MacroAssemblerX64::setupUnalignedABICall(uint32_t args, Register scratch) { setupABICall(args); dynamicAlignment_ = true; movq(rsp, scratch); - andq(Imm32(~(StackAlignment - 1)), rsp); + andq(Imm32(~(ABIStackAlignment - 1)), rsp); push(scratch); } void MacroAssemblerX64::passABIArg(const MoveOperand &from, MoveOp::Type type) { MoveOperand to; switch (type) { @@ -265,21 +265,21 @@ void MacroAssemblerX64::callWithABIPre(uint32_t *stackAdjust) { JS_ASSERT(inCall_); JS_ASSERT(args_ == passedIntArgs_ + passedFloatArgs_); if (dynamicAlignment_) { *stackAdjust = stackForCall_ + ComputeByteAlignment(stackForCall_ + sizeof(intptr_t), - StackAlignment); + ABIStackAlignment); } else { *stackAdjust = stackForCall_ + ComputeByteAlignment(stackForCall_ + framePushed_, - StackAlignment); + ABIStackAlignment); } reserveStack(*stackAdjust); // Position all arguments. { enoughMemory_ &= moveResolver_.resolve(); if (!enoughMemory_) @@ -288,17 +288,17 @@ MacroAssemblerX64::callWithABIPre(uint32 MoveEmitter emitter(*this); emitter.emit(moveResolver_); emitter.finish(); } #ifdef DEBUG { Label good; - testq(rsp, Imm32(StackAlignment - 1)); + testq(rsp, Imm32(ABIStackAlignment - 1)); j(Equal, &good); breakpoint(); bind(&good); } #endif } void
--- a/js/src/jit/x64/Trampoline-x64.cpp +++ b/js/src/jit/x64/Trampoline-x64.cpp @@ -546,17 +546,16 @@ JitRuntime::generateBailoutHandler(JSCon #endif return code; } JitCode * JitRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f) { - JS_ASSERT(!StackKeptAligned); JS_ASSERT(functionWrappers_); JS_ASSERT(functionWrappers_->initialized()); VMWrapperMap::AddPtr p = functionWrappers_->lookupForAdd(&f); if (p) return p->value(); // Generate a separated code for the wrapper. MacroAssembler masm;
--- a/js/src/jit/x86/Assembler-x86.cpp +++ b/js/src/jit/x86/Assembler-x86.cpp @@ -14,26 +14,36 @@ using namespace js::jit; ABIArgGenerator::ABIArgGenerator() : stackOffset_(0), current_() {} ABIArg ABIArgGenerator::next(MIRType type) { - current_ = ABIArg(stackOffset_); switch (type) { case MIRType_Int32: case MIRType_Pointer: + current_ = ABIArg(stackOffset_); stackOffset_ += sizeof(uint32_t); break; case MIRType_Float32: // Float32 moves are actually double moves case MIRType_Double: + current_ = ABIArg(stackOffset_); stackOffset_ += sizeof(uint64_t); break; + case MIRType_Int32x4: + case MIRType_Float32x4: + // SIMD values aren't passed in or out of C++, so we can make up + // whatever internal ABI we like. visitAsmJSPassArg assumes + // SimdStackAlignment. + stackOffset_ = AlignBytes(stackOffset_, SimdStackAlignment); + current_ = ABIArg(stackOffset_); + stackOffset_ += Simd128DataSize; + break; default: MOZ_CRASH("Unexpected argument type"); } return current_; } const Register ABIArgGenerator::NonArgReturnReg0 = ecx; const Register ABIArgGenerator::NonArgReturnReg1 = edx;
--- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -103,33 +103,34 @@ static MOZ_CONSTEXPR_VAR Register AsmJSI // Registers used in the GenerateFFIIonExit Disable Activation block. static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegReturnData = edx; static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegReturnType = ecx; static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegD0 = edi; static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegD1 = eax; static MOZ_CONSTEXPR_VAR Register AsmJSIonExitRegD2 = esi; -// GCC stack is aligned on 16 bytes, but we don't maintain the invariant in -// jitted code. +// GCC stack is aligned on 16 bytes. Ion does not maintain this for internal +// calls. asm.js code does. #if defined(__GNUC__) -static const uint32_t StackAlignment = 16; +static const uint32_t ABIStackAlignment = 16; #else -static const uint32_t StackAlignment = 4; +static const uint32_t ABIStackAlignment = 4; #endif -static const bool StackKeptAligned = false; static const uint32_t CodeAlignment = 8; // This boolean indicates whether we support SIMD instructions flavoured for // this architecture or not. Rather than a method in the LIRGenerator, it is // here such that it is accessible from the entire codebase. Once full support // for SIMD is reached on all tier-1 platforms, this constant can be deleted. static const bool SupportsSimd = true; static const uint32_t SimdStackAlignment = 16; +static const uint32_t AsmJSStackAlignment = SimdStackAlignment; + struct ImmTag : public Imm32 { ImmTag(JSValueTag mask) : Imm32(int32_t(mask)) { } }; struct ImmType : public ImmTag @@ -517,16 +518,26 @@ class Assembler : public AssemblerX86Sha masm.movss_mr(src.addr, dest.code()); return CodeOffsetLabel(masm.currentOffset()); } CodeOffsetLabel movsdWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) { JS_ASSERT(HasSSE2()); masm.movsd_mr(src.addr, dest.code()); return CodeOffsetLabel(masm.currentOffset()); } + CodeOffsetLabel movdqaWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) { + JS_ASSERT(HasSSE2()); + masm.movdqa_mr(src.addr, dest.code()); + return CodeOffsetLabel(masm.currentOffset()); + } + CodeOffsetLabel movapsWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) { + JS_ASSERT(HasSSE2()); + masm.movaps_mr(src.addr, dest.code()); + return CodeOffsetLabel(masm.currentOffset()); + } // Store to *dest where dest can be patched. CodeOffsetLabel movbWithPatch(Register src, PatchedAbsoluteAddress dest) { masm.movb_rm(src.code(), dest.addr); return CodeOffsetLabel(masm.currentOffset()); } CodeOffsetLabel movwWithPatch(Register src, PatchedAbsoluteAddress dest) { masm.movw_rm(src.code(), dest.addr); @@ -541,16 +552,26 @@ class Assembler : public AssemblerX86Sha masm.movss_rm(src.code(), dest.addr); return CodeOffsetLabel(masm.currentOffset()); } CodeOffsetLabel movsdWithPatch(FloatRegister src, PatchedAbsoluteAddress dest) { JS_ASSERT(HasSSE2()); masm.movsd_rm(src.code(), dest.addr); return CodeOffsetLabel(masm.currentOffset()); } + CodeOffsetLabel movdqaWithPatch(FloatRegister src, PatchedAbsoluteAddress dest) { + JS_ASSERT(HasSSE2()); + masm.movdqa_rm(src.code(), dest.addr); + return CodeOffsetLabel(masm.currentOffset()); + } + CodeOffsetLabel movapsWithPatch(FloatRegister src, PatchedAbsoluteAddress dest) { + JS_ASSERT(HasSSE2()); + masm.movaps_rm(src.code(), dest.addr); + return CodeOffsetLabel(masm.currentOffset()); + } void loadAsmJSActivation(Register dest) { CodeOffsetLabel label = movlWithPatch(PatchedAbsoluteAddress(), dest); append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset)); } }; // Get a register in which we plan to put a quantity that will be used as an
--- a/js/src/jit/x86/CodeGenerator-x86.cpp +++ b/js/src/jit/x86/CodeGenerator-x86.cpp @@ -457,44 +457,74 @@ CodeGeneratorX86::visitAsmJSStoreHeap(LA return true; } bool CodeGeneratorX86::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins) { MAsmJSLoadGlobalVar *mir = ins->mir(); MIRType type = mir->type(); - JS_ASSERT(IsNumberType(type)); + JS_ASSERT(IsNumberType(type) || IsSimdType(type)); CodeOffsetLabel label; - if (type == MIRType_Int32) + switch (type) { + case MIRType_Int32: label = masm.movlWithPatch(PatchedAbsoluteAddress(), ToRegister(ins->output())); - else if (type == MIRType_Float32) + break; + case MIRType_Float32: label = masm.movssWithPatch(PatchedAbsoluteAddress(), ToFloatRegister(ins->output())); - else + break; + case MIRType_Double: label = masm.movsdWithPatch(PatchedAbsoluteAddress(), ToFloatRegister(ins->output())); + break; + // Aligned access: code is aligned on PageSize + there is padding + // before the global data section. + case MIRType_Int32x4: + label = masm.movdqaWithPatch(PatchedAbsoluteAddress(), ToFloatRegister(ins->output())); + break; + case MIRType_Float32x4: + label = masm.movapsWithPatch(PatchedAbsoluteAddress(), ToFloatRegister(ins->output())); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected type in visitAsmJSLoadGlobalVar"); + } masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset())); return true; } bool CodeGeneratorX86::visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar *ins) { MAsmJSStoreGlobalVar *mir = ins->mir(); MIRType type = mir->value()->type(); - JS_ASSERT(IsNumberType(type)); + JS_ASSERT(IsNumberType(type) || IsSimdType(type)); CodeOffsetLabel label; - if (type == MIRType_Int32) + switch (type) { + case MIRType_Int32: label = masm.movlWithPatch(ToRegister(ins->value()), PatchedAbsoluteAddress()); - else if (type == MIRType_Float32) + break; + case MIRType_Float32: label = masm.movssWithPatch(ToFloatRegister(ins->value()), PatchedAbsoluteAddress()); - else + break; + case MIRType_Double: label = masm.movsdWithPatch(ToFloatRegister(ins->value()), PatchedAbsoluteAddress()); + break; + // Aligned access: code is aligned on PageSize + there is padding + // before the global data section. + case MIRType_Int32x4: + label = masm.movdqaWithPatch(ToFloatRegister(ins->value()), PatchedAbsoluteAddress()); + break; + case MIRType_Float32x4: + label = masm.movapsWithPatch(ToFloatRegister(ins->value()), PatchedAbsoluteAddress()); + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected type in visitAsmJSStoreGlobalVar"); + } masm.append(AsmJSGlobalAccess(label, mir->globalDataOffset())); return true; } bool CodeGeneratorX86::visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr *ins) { MAsmJSLoadFuncPtr *mir = ins->mir();
--- a/js/src/jit/x86/MacroAssembler-x86.cpp +++ b/js/src/jit/x86/MacroAssembler-x86.cpp @@ -222,17 +222,17 @@ MacroAssemblerX86::setupAlignedABICall(u void MacroAssemblerX86::setupUnalignedABICall(uint32_t args, Register scratch) { setupABICall(args); dynamicAlignment_ = true; movl(esp, scratch); - andl(Imm32(~(StackAlignment - 1)), esp); + andl(Imm32(~(ABIStackAlignment - 1)), esp); push(scratch); } void MacroAssemblerX86::passABIArg(const MoveOperand &from, MoveOp::Type type) { ++passedArgs_; MoveOperand to = MoveOperand(StackPointer, stackForCall_); @@ -262,21 +262,21 @@ void MacroAssemblerX86::callWithABIPre(uint32_t *stackAdjust) { JS_ASSERT(inCall_); JS_ASSERT(args_ == passedArgs_); if (dynamicAlignment_) { *stackAdjust = stackForCall_ + ComputeByteAlignment(stackForCall_ + sizeof(intptr_t), - StackAlignment); + ABIStackAlignment); } else { *stackAdjust = stackForCall_ + ComputeByteAlignment(stackForCall_ + framePushed_, - StackAlignment); + ABIStackAlignment); } reserveStack(*stackAdjust); // Position all arguments. { enoughMemory_ &= moveResolver_.resolve(); if (!enoughMemory_) @@ -286,17 +286,17 @@ MacroAssemblerX86::callWithABIPre(uint32 emitter.emit(moveResolver_); emitter.finish(); } #ifdef DEBUG { // Check call alignment. Label good; - testl(esp, Imm32(StackAlignment - 1)); + testl(esp, Imm32(ABIStackAlignment - 1)); j(Equal, &good); breakpoint(); bind(&good); } #endif } void
--- a/js/src/jit/x86/Trampoline-x86.cpp +++ b/js/src/jit/x86/Trampoline-x86.cpp @@ -585,17 +585,16 @@ JitRuntime::generateBailoutHandler(JSCon #endif return code; } JitCode * JitRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f) { - JS_ASSERT(!StackKeptAligned); JS_ASSERT(functionWrappers_); JS_ASSERT(functionWrappers_->initialized()); VMWrapperMap::AddPtr p = functionWrappers_->lookupForAdd(&f); if (p) return p->value(); // Generate a separated code for the wrapper. MacroAssembler masm;
--- a/js/src/js.msg +++ b/js/src/js.msg @@ -286,16 +286,17 @@ MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 0 MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer") MSG_DEF(JSMSG_SEMI_BEFORE_STMNT, 0, JSEXN_SYNTAXERR, "missing ; before statement") MSG_DEF(JSMSG_SOURCE_TOO_LONG, 0, JSEXN_RANGEERR, "source is too long") MSG_DEF(JSMSG_STRICT_CODE_LET_EXPR_STMT, 0, JSEXN_ERR, "strict mode code may not contain unparenthesized let expression statements") MSG_DEF(JSMSG_STRICT_CODE_WITH, 0, JSEXN_SYNTAXERR, "strict mode code may not contain 'with' statements") MSG_DEF(JSMSG_STRICT_FUNCTION_STATEMENT, 0, JSEXN_SYNTAXERR, "in strict mode code, functions may be declared only at top level or immediately within another function") MSG_DEF(JSMSG_SYNTAX_ERROR, 0, JSEXN_SYNTAXERR, "syntax error") MSG_DEF(JSMSG_TEMPLSTR_UNTERM_EXPR, 0, JSEXN_SYNTAXERR, "missing } in template string") +MSG_DEF(JSMSG_SIMD_NOT_A_VECTOR, 0, JSEXN_TYPEERR, "value isn't a SIMD value object") MSG_DEF(JSMSG_TOO_MANY_CASES, 0, JSEXN_INTERNALERR, "too many switch cases") MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS, 0, JSEXN_SYNTAXERR, "too many catch variables") MSG_DEF(JSMSG_TOO_MANY_CON_ARGS, 0, JSEXN_SYNTAXERR, "too many constructor arguments") MSG_DEF(JSMSG_TOO_MANY_DEFAULTS, 0, JSEXN_SYNTAXERR, "more than one switch default") MSG_DEF(JSMSG_TOO_MANY_FUN_ARGS, 0, JSEXN_SYNTAXERR, "too many function arguments") MSG_DEF(JSMSG_TOO_MANY_LOCALS, 0, JSEXN_SYNTAXERR, "too many local variables") MSG_DEF(JSMSG_TOUGH_BREAK, 0, JSEXN_SYNTAXERR, "unlabeled break must be inside loop or switch") MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name")
--- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -290,16 +290,17 @@ struct ThreadSafeContext : ContextFriend void *runtimeAddressForJit() { return runtime_; } void *runtimeAddressOfInterrupt() { return &runtime_->interrupt; } void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } void *stackLimitAddressForJitCode(StackKind kind); size_t gcSystemPageSize() { return gc::SystemPageSize(); } bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); } bool canUseSignalHandlers() const { return runtime_->canUseSignalHandlers(); } bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; } + bool jitSupportsSimd() const { return runtime_->jitSupportsSimd; } // Thread local data that may be accessed freely. DtoaState *dtoaState() { return perThreadData->dtoaState; } }; struct HelperThread;
--- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -206,16 +206,17 @@ JSRuntime::JSRuntime(JSRuntime *parentRu atomsCompartment_(nullptr), staticStrings(nullptr), commonNames(nullptr), permanentAtoms(nullptr), wellKnownSymbols(nullptr), wrapObjectCallbacks(&DefaultWrapObjectCallbacks), preserveWrapperCallback(nullptr), jitSupportsFloatingPoint(false), + jitSupportsSimd(false), ionPcScriptCache(nullptr), threadPool(this), defaultJSContextCallback(nullptr), ctypesActivityCallback(nullptr), forkJoinWarmup(0), offthreadIonCompilationEnabled_(true), parallelParsingEnabled_(true), #ifdef DEBUG @@ -310,16 +311,17 @@ JSRuntime::init(uint32_t maxbytes, uint3 simulatorRuntime_ = js::jit::CreateSimulatorRuntime(); if (!simulatorRuntime_) return false; #endif nativeStackBase = GetNativeStackBase(); jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint(); + jitSupportsSimd = js::jit::JitSupportsSimd(); signalHandlersInstalled_ = EnsureAsmJSSignalHandlersInstalled(this); canUseSignalHandlers_ = signalHandlersInstalled_ && !SignalBasedTriggersDisabled(); if (!spsProfiler.init()) return false; return true;
--- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1269,16 +1269,17 @@ struct JSRuntime : public JS::shadow::Ru js::ScriptDataTable scriptDataTable_; public: js::ScriptDataTable &scriptDataTable() { JS_ASSERT(currentThreadHasExclusiveAccess()); return scriptDataTable_; } bool jitSupportsFloatingPoint; + bool jitSupportsSimd; // Used to reset stack limit after a signaled interrupt (i.e. jitStackLimit_ = -1) // has been noticed by Ion/Baseline. void resetJitStackLimit(); // Cache for jit::GetPcScript(). js::jit::PcScriptCache *ionPcScriptCache;
--- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1545,17 +1545,17 @@ jit::JitActivation::markRematerializedFr return; for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront()) RematerializedFrame::MarkInVector(trc, e.front().value()); } AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module) : Activation(cx, AsmJS), module_(module), - errorRejoinSP_(nullptr), + entrySP_(nullptr), profiler_(nullptr), resumePC_(nullptr), fp_(nullptr), exitReason_(AsmJSExit::None) { if (cx->runtime()->spsProfiler.enabled()) { // Use a profiler string that matches jsMatch regex in // browser/devtools/profiler/cleopatra/js/parserWorker.js. @@ -1568,17 +1568,17 @@ AsmJSActivation::AsmJSActivation(JSConte prevAsmJSForModule_ = module.activation(); module.activation() = this; prevAsmJS_ = cx->mainThread().asmJSActivationStack_; JSRuntime::AutoLockForInterrupt lock(cx->runtime()); cx->mainThread().asmJSActivationStack_ = this; - (void) errorRejoinSP_; // squelch GCC warning + (void) entrySP_; // squelch GCC warning } AsmJSActivation::~AsmJSActivation() { if (profiler_) profiler_->exitAsmJS(); JS_ASSERT(fp_ == nullptr);
--- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1477,17 +1477,17 @@ class InterpreterFrameIterator // JitActivation interleaved with Ion/Baseline jit code. This would allow // efficient calls back and forth but requires that we can walk the stack for // all kinds of jit code. class AsmJSActivation : public Activation { AsmJSModule &module_; AsmJSActivation *prevAsmJS_; AsmJSActivation *prevAsmJSForModule_; - void *errorRejoinSP_; + void *entrySP_; SPSProfiler *profiler_; void *resumePC_; uint8_t *fp_; AsmJSExit::Reason exitReason_; public: AsmJSActivation(JSContext *cx, AsmJSModule &module); ~AsmJSActivation(); @@ -1507,17 +1507,17 @@ class AsmJSActivation : public Activatio // Returns the reason why asm.js code called out of asm.js code. AsmJSExit::Reason exitReason() const { return exitReason_; } // Read by JIT code: static unsigned offsetOfContext() { return offsetof(AsmJSActivation, cx_); } static unsigned offsetOfResumePC() { return offsetof(AsmJSActivation, resumePC_); } // Written by JIT code: - static unsigned offsetOfErrorRejoinSP() { return offsetof(AsmJSActivation, errorRejoinSP_); } + static unsigned offsetOfEntrySP() { return offsetof(AsmJSActivation, entrySP_); } static unsigned offsetOfFP() { return offsetof(AsmJSActivation, fp_); } static unsigned offsetOfExitReason() { return offsetof(AsmJSActivation, exitReason_); } // Set from SIGSEGV handler: void setResumePC(void *pc) { resumePC_ = pc; } }; // A FrameIter walks over the runtime's stack of JS script activations,
--- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -22,16 +22,17 @@ #include "nsISelectionController.h" #include "nsIPresShell.h" #include "nsRegion.h" #include "nsStyleStructInlines.h" #include "nsStyleTransformMatrix.h" #include "gfxMatrix.h" #include "gfxPrefs.h" #include "nsSVGIntegrationUtils.h" +#include "nsSVGUtils.h" #include "nsLayoutUtils.h" #include "nsIScrollableFrame.h" #include "nsIFrameInlines.h" #include "nsThemeConstants.h" #include "LayerTreeInvalidation.h" #include "imgIContainer.h" #include "BasicLayers.h" @@ -5494,16 +5495,50 @@ bool nsDisplaySVGEffects::TryMerge(nsDis return false; nsDisplaySVGEffects* other = static_cast<nsDisplaySVGEffects*>(aItem); MergeFromTrackingMergedFrames(other); mEffectsBounds.UnionRect(mEffectsBounds, other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame)); return true; } +gfxRect +nsDisplaySVGEffects::BBoxInUserSpace() const +{ + return nsSVGUtils::GetBBox(mFrame); +} + +gfxPoint +nsDisplaySVGEffects::UserSpaceOffset() const +{ + return nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame); +} + +void +nsDisplaySVGEffects::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) +{ + const nsDisplaySVGEffectsGeometry* geometry = + static_cast<const nsDisplaySVGEffectsGeometry*>(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() || + geometry->mUserSpaceOffset != UserSpaceOffset() || + !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) { + // Filter and mask output can depend on the location of the frame's user + // space and on the frame's BBox. We need to invalidate if either of these + // change relative to the reference frame. + // Invalidations from our inactive layer manager are not enough to catch + // some of these cases because filters can produce output even if there's + // nothing in the filter input. + aInvalidRegion->Or(bounds, geometry->mBounds); + } +} + #ifdef MOZ_DUMP_PAINTING void nsDisplaySVGEffects::PrintEffects(nsACString& aTo) { nsIFrame* firstFrame = nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); nsSVGEffects::EffectProperties effectProperties = nsSVGEffects::GetEffectProperties(firstFrame);
--- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3145,23 +3145,27 @@ public: virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) MOZ_OVERRIDE; virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) MOZ_OVERRIDE; - + + gfxRect BBoxInUserSpace() const; + gfxPoint UserSpaceOffset() const; + + virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE + { + return new nsDisplaySVGEffectsGeometry(this, aBuilder); + } virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, - nsRegion* aInvalidRegion) MOZ_OVERRIDE - { - // We don't need to compute an invalidation region since we have LayerTreeInvalidation - } + nsRegion* aInvalidRegion) MOZ_OVERRIDE; void PaintAsLayer(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx, LayerManager* aManager); #ifdef MOZ_DUMP_PAINTING void PrintEffects(nsACString& aTo); #endif
--- a/layout/base/nsDisplayListInvalidation.cpp +++ b/layout/base/nsDisplayListInvalidation.cpp @@ -89,8 +89,22 @@ nsDisplayBoxShadowInnerGeometry::MoveBy( mPaddingRect.MoveBy(aOffset); } nsDisplayBoxShadowOuterGeometry::nsDisplayBoxShadowOuterGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder, float aOpacity) : nsDisplayItemGenericGeometry(aItem, aBuilder) , mOpacity(aOpacity) {} + +nsDisplaySVGEffectsGeometry::nsDisplaySVGEffectsGeometry(nsDisplaySVGEffects* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGeometry(aItem, aBuilder) + , mBBox(aItem->BBoxInUserSpace()) + , mUserSpaceOffset(aItem->UserSpaceOffset()) + , mFrameOffsetToReferenceFrame(aItem->ToReferenceFrame()) +{} + +void +nsDisplaySVGEffectsGeometry::MoveBy(const nsPoint& aOffset) +{ + mBounds.MoveBy(aOffset); + mFrameOffsetToReferenceFrame += aOffset; +}
--- a/layout/base/nsDisplayListInvalidation.h +++ b/layout/base/nsDisplayListInvalidation.h @@ -4,21 +4,23 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef NSDISPLAYLISTINVALIDATION_H_ #define NSDISPLAYLISTINVALIDATION_H_ #include "mozilla/Attributes.h" #include "nsRect.h" #include "nsColor.h" +#include "gfxRect.h" class nsDisplayItem; class nsDisplayListBuilder; class nsDisplayBackgroundImage; class nsDisplayThemedBackground; +class nsDisplaySVGEffects; /** * This stores the geometry of an nsDisplayItem, and the area * that will be affected when painting the item. * * It is used to retain information about display items so they * can be compared against new display items in the next paint. */ @@ -136,9 +138,21 @@ public: nscolor aColor) : nsDisplayItemBoundsGeometry(aItem, aBuilder) , mColor(aColor) { } nscolor mColor; }; +class nsDisplaySVGEffectsGeometry : public nsDisplayItemGeometry +{ +public: + nsDisplaySVGEffectsGeometry(nsDisplaySVGEffects* aItem, nsDisplayListBuilder* aBuilder); + + virtual void MoveBy(const nsPoint& aOffset) MOZ_OVERRIDE; + + gfxRect mBBox; + gfxPoint mUserSpaceOffset; + nsPoint mFrameOffsetToReferenceFrame; +}; + #endif /*NSDISPLAYLISTINVALIDATION_H_*/
--- a/layout/mathml/nsMathMLContainerFrame.cpp +++ b/layout/mathml/nsMathMLContainerFrame.cpp @@ -466,16 +466,17 @@ nsMathMLContainerFrame::FinalizeReflow(n // Place() will call FinishReflowChild() when placeOrigin is true but if // it returns before reaching FinishReflowChild() due to errors we need // to fulfill the reflow protocol by calling DidReflow for the child frames // that still needs it here (or we may crash - bug 366012). // If placeOrigin is false we should reach Place() with aPlaceOrigin == true // through Stretch() eventually. if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) { + GatherAndStoreOverflow(&aDesiredSize); DidReflowChildren(GetFirstPrincipalChild()); return rv; } bool parentWillFireStretch = false; if (!placeOrigin) { // This means the rect.x and rect.y of our children were not set!! // Don't go without checking to see if our parent will later fire a Stretch() command
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1021564-1.html @@ -0,0 +1,46 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en" class="reftest-wait"> +<meta charset="utf-8"> +<title>When the filtered element's BBox relative to the page changes, the filtered element needs to be invalidated</title> + +<style> + +#spacer { + height: 100px; +} + +#filtered { + width: 100px; + height: 100px; + background-color: lime; + filter: url(#filter); +} + +</style> + +<svg height="0"> + <defs> + <filter id="filter" filterUnits="objectBoundingBox" + x="0%" y="0%" width="100%" height="100%" + color-interpolation-filters="sRGB"> + <feMerge><feMergeNode/></feMerge> + </filter> + </defs> +</svg> + +<div id="spacer"></div> + +<div id="filtered"></div> + +<script> + +window.addEventListener("MozReftestInvalidate", function () { + document.getElementById("spacer").style.height = "50px"; + document.documentElement.removeAttribute("class"); +}); + +</script>
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1021564-2.html @@ -0,0 +1,45 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en" class="reftest-wait"> +<meta charset="utf-8"> +<title>When the filtered element's BBox relative to the page changes, the filtered element needs to be invalidated</title> + +<style> + +#spacer { + height: 100px; +} + +#filtered { + width: 100px; + height: 100px; + filter: url(#filter); +} + +</style> + +<svg height="0"> + <defs> + <filter id="filter" filterUnits="objectBoundingBox" + x="0%" y="0%" width="100%" height="100%" + color-interpolation-filters="sRGB"> + <feFlood flood-color="lime"/> + </filter> + </defs> +</svg> + +<div id="spacer"></div> + +<div id="filtered"></div> + +<script> + +window.addEventListener("MozReftestInvalidate", function () { + document.getElementById("spacer").style.height = "50px"; + document.documentElement.removeAttribute("class"); +}); + +</script>
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1021564-3.html @@ -0,0 +1,46 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en" class="reftest-wait"> +<meta charset="utf-8"> +<title>When the filtered element's BBox relative to the page changes, the filtered element needs to be invalidated</title> + +<style> + +#spacer { + height: 100px; +} + +#filtered { + width: 100px; + height: 100px; + background-color: lime; + filter: url(#filter); +} + +</style> + +<svg height="0"> + <defs> + <filter id="filter" filterUnits="userSpaceOnUse" + x="0" y="0" width="100" height="100" + color-interpolation-filters="sRGB"> + <feMerge><feMergeNode/></feMerge> + </filter> + </defs> +</svg> + +<div id="spacer"></div> + +<div id="filtered"></div> + +<script> + +window.addEventListener("MozReftestInvalidate", function () { + document.getElementById("spacer").style.height = "50px"; + document.documentElement.removeAttribute("class"); +}); + +</script>
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1021564-4.html @@ -0,0 +1,45 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en" class="reftest-wait"> +<meta charset="utf-8"> +<title>When the filtered element's BBox relative to the page changes, the filtered element needs to be invalidated</title> + +<style> + +#spacer { + height: 100px; +} + +#filtered { + width: 100px; + height: 100px; + filter: url(#filter); +} + +</style> + +<svg height="0"> + <defs> + <filter id="filter" filterUnits="userSpaceOnUse" + x="0" y="0" width="100" height="100" + color-interpolation-filters="sRGB"> + <feFlood flood-color="lime"/> + </filter> + </defs> +</svg> + +<div id="spacer"></div> + +<div id="filtered"></div> + +<script> + +window.addEventListener("MozReftestInvalidate", function () { + document.getElementById("spacer").style.height = "50px"; + document.documentElement.removeAttribute("class"); +}); + +</script>
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1021564-ref.html @@ -0,0 +1,28 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en"> +<meta charset="utf-8"> +<title>When the filtered element's BBox relative to the page changes, the filtered element needs to be invalidated</title> + +<style> + +#spacer { + height: 50px; +} + +#filtered { + width: 100px; + height: 100px; + background-color: lime; +} + +</style> + +<svg height="0"></svg> + +<div id="spacer"></div> + +<div id="filtered"></div>
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1025914-1-ref.html @@ -0,0 +1,44 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en"> +<meta charset="utf-8"> +<title>Make sure that scrolling #scrolledBox into view paints the scrolled strip even while #coveringFixedBar covers that strip</title> + +<style> + +html { + overflow: hidden; +} + +body { + margin: 0; +} + +#coveringFixedBar { + position: absolute; + left: 10px; + top: 0; + width: 380px; + height: 20px; + background: blue; + z-index: 100; +} + +#scrolledBox { + position: relative; + margin: 0 100px; + opacity: 0.9; + width: 200px; + height: 200px; + background: lime; + border: 1px solid black; +} + +</style> + +<div id="coveringFixedBar"></div> + +<div id="scrolledBox"></div>
new file mode 100644 --- /dev/null +++ b/layout/reftests/bugs/1025914-1.html @@ -0,0 +1,59 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html lang="en" class="reftest-wait"> +<meta charset="utf-8"> +<title>Make sure that scrolling #scrolledBox into view paints the scrolled strip even while #coveringFixedBar covers that strip</title> + +<style> + +html { + overflow: hidden; +} + +body { + margin: 0; + height: 2000px; +} + +#coveringFixedBar { + position: fixed; + left: 10px; + top: 0; + width: 380px; + height: 20px; + background: blue; + z-index: 100; +} + +#scrolledBox { + position: relative; + margin: 0 100px; + opacity: 0.9; + width: 200px; + height: 200px; + background: lime; + border: 1px solid black; +} + +</style> + +<div id="coveringFixedBar"></div> + +<div id="scrolledBox"></div> + +<script> + +document.documentElement.scrollTop = 40; + +window.addEventListener("MozReftestInvalidate", function () { + document.documentElement.scrollTop = 20; + window.requestAnimationFrame(function () { + document.documentElement.scrollTop = 0; + document.documentElement.removeAttribute("class"); + }); +}); + +</script>
--- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1810,18 +1810,23 @@ skip-if(Android) == 966510-2.html 966510 == 987680-1.html 987680-1-ref.html fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,24) == 991046-1.html 991046-1-ref.html pref(layout.css.overflow-clip-box.enabled,true) == 992447.html 992447-ref.html == 1003425-1.html 1003425-1-ref.html == 1003425-2.html 1003425-2-ref.html pref(layout.css.sticky.enabled,true) == 1005405-1.html 1005405-1-ref.html fuzzy-if(/^Windows\x20NT\x205\.1/.test(http.oscpu),255,1) == 1013054-1.html 1013054-1-ref.html pref(layout.css.will-change.enabled,true) == 1018522-1.html 1018522-1-ref.html +== 1021564-1.html 1021564-ref.html +== 1021564-2.html 1021564-ref.html +== 1021564-3.html 1021564-ref.html +== 1021564-4.html 1021564-ref.html pref(browser.display.use_document_fonts,0) == 1022481-1.html 1022481-1-ref.html == 1022612-1.html 1022612-1-ref.html == 1024473-1.html 1024473-1-ref.html +== 1025914-1.html 1025914-1-ref.html == 1042104-1.html 1042104-1-ref.html == 1044198-1.html 1044198-1-ref.html == 1049499-1.html 1049499-1-ref.html == 1050788-1.html about:blank == 1053035-1-flex.html 1053035-1-ref.html test-pref(layout.css.grid.enabled,true) == 1053035-1-grid.html 1053035-1-ref.html == 1059167-1.html 1059167-1-ref.html
--- a/security/pkix/test/lib/pkixtestutil.cpp +++ b/security/pkix/test/lib/pkixtestutil.cpp @@ -130,16 +130,17 @@ TamperOnce(SECItem& item, } if (!memcmp(foundFirstByte, from, fromLen)) { if (alreadyFoundMatch) { return Result::FATAL_ERROR_INVALID_ARGS; } alreadyFoundMatch = true; memmove(foundFirstByte, to, toLen); p = foundFirstByte + toLen; + remaining -= toLen; } else { p = foundFirstByte + 1; --remaining; } } } Result
--- a/toolkit/xre/WindowsCrtPatch.h +++ b/toolkit/xre/WindowsCrtPatch.h @@ -87,17 +87,17 @@ PatchModuleImports(HMODULE module, PIMAG RVAPtr<IMAGE_IMPORT_DESCRIPTOR> descriptor(module, importDirectory->VirtualAddress); for (; descriptor->OriginalFirstThunk; ++descriptor) { RVAPtr<char> importedModule(module, descriptor->Name); if (!stricmp(importedModule, "kernel32.dll")) { RVAPtr<IMAGE_THUNK_DATA> thunk(module, descriptor->OriginalFirstThunk); for (; thunk->u1.AddressOfData; ++thunk) { RVAPtr<IMAGE_IMPORT_BY_NAME> import(module, thunk->u1.AddressOfData); - if (!strcmp(import->Name, "GetLogicalProcessorInformation")) { + if (!strcmp((char*)import->Name, "GetLogicalProcessorInformation")) { memcpy(import->Name, "DebugBreak", sizeof("DebugBreak")); } } } } } PIMAGE_NT_HEADERS NTAPI