static bool ValidateAtomicsBuiltinFunction(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) { RootedValue v(cx); if (!GetDataProperty(cx, globalVal, cx->names().Atomics, &v)) return false; RootedPropertyName field(cx, global.atomicsName()); if (!GetDataProperty(cx, v, field, &v)) return false; Native native = nullptr; switch (global.atomicsBuiltinFunction()) { case AsmJSAtomicsBuiltin_compareExchange: native = atomics_compareExchange; break; case AsmJSAtomicsBuiltin_load: native = atomics_load; break; case AsmJSAtomicsBuiltin_store: native = atomics_store; break; case AsmJSAtomicsBuiltin_fence: native = atomics_fence; break; case AsmJSAtomicsBuiltin_add: native = atomics_add; break; case AsmJSAtomicsBuiltin_sub: native = atomics_sub; break; case AsmJSAtomicsBuiltin_and: native = atomics_and; break; case AsmJSAtomicsBuiltin_or: native = atomics_or; break; case AsmJSAtomicsBuiltin_xor: native = atomics_xor; break; } if (!IsNativeFunction(v, native)) return LinkFail(cx, "bad Atomics.* builtin function"); 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)) return false; } if (!GetDataProperty(cx, v, field, &v)) return false; if (!v.isNumber()) return LinkFail(cx, "math / global constant value needs to be a number"); // NaN != NaN if (IsNaN(global.constantValue())) { if (!IsNaN(v.toNumber())) return LinkFail(cx, "global constant value needs to be NaN"); } else { if (v.toNumber() != global.constantValue()) return LinkFail(cx, "global constant value mismatch"); } return true; }
static bool ValidateMathBuiltin(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) { RootedValue v(cx); if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) return false; RootedPropertyName field(cx, global.mathName()); if (!GetDataProperty(cx, v, field, &v)) return false; Native native = nullptr; switch (global.mathBuiltin()) { case AsmJSMathBuiltin_sin: native = math_sin; break; case AsmJSMathBuiltin_cos: native = math_cos; break; case AsmJSMathBuiltin_tan: native = math_tan; break; case AsmJSMathBuiltin_asin: native = math_asin; break; case AsmJSMathBuiltin_acos: native = math_acos; break; case AsmJSMathBuiltin_atan: native = math_atan; break; case AsmJSMathBuiltin_ceil: native = math_ceil; break; case AsmJSMathBuiltin_floor: native = math_floor; break; case AsmJSMathBuiltin_exp: native = math_exp; break; case AsmJSMathBuiltin_log: native = math_log; break; case AsmJSMathBuiltin_pow: native = js_math_pow; break; case AsmJSMathBuiltin_sqrt: native = js_math_sqrt; break; case AsmJSMathBuiltin_abs: native = js_math_abs; break; case AsmJSMathBuiltin_atan2: native = math_atan2; break; case AsmJSMathBuiltin_imul: native = math_imul; break; case AsmJSMathBuiltin_fround: native = math_fround; break; } if (!IsNativeFunction(v, native)) return LinkFail(cx, "bad Math.* builtin"); return true; }
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 simdDesc(cx, &v.toObject()); if (!simdDesc->is<SimdTypeDescr>()) return LinkFail(cx, "bad SIMD type"); if (AsmJSSimdTypeToTypeDescrType(type) != simdDesc->as<SimdTypeDescr>().type()) return LinkFail(cx, "bad SIMD type"); out.set(v); return true; }
static bool ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal, HandleValue bufferVal) { RootedPropertyName field(cx, global.viewName()); RootedValue v(cx); if (!GetDataProperty(cx, globalVal, field, &v)) return false; if (!IsTypedArrayConstructor(v, global.viewType())) return LinkFail(cx, "bad typed array constructor"); return true; }
static bool ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal, AutoObjectVector *ffis) { RootedPropertyName field(cx, global.ffiField()); RootedValue v(cx); if (!GetDataProperty(cx, importVal, field, &v)) return false; if (!v.isObject() || !v.toObject().is<JSFunction>()) return LinkFail(cx, "FFI imports must be functions"); (*ffis)[global.ffiIndex()] = &v.toObject().as<JSFunction>(); return true; }
static bool ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal, bool isShared) { RootedPropertyName field(cx, global.maybeViewName()); if (!field) return true; RootedValue v(cx); if (!GetDataProperty(cx, globalVal, field, &v)) return false; bool tac = IsTypedArrayConstructor(v, global.viewType()); bool stac = IsSharedTypedArrayConstructor(v, global.viewType()); if (!((tac || stac) && stac == isShared)) return LinkFail(cx, "bad typed array constructor"); return true; }
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()) { #define SET_NATIVE_INT32X4(op) case AsmJSSimdOperation_##op: native = simd_int32x4_##op; break; #define SET_NATIVE_FLOAT32X4(op) case AsmJSSimdOperation_##op: native = simd_float32x4_##op; break; #define FALLTHROUGH(op) case AsmJSSimdOperation_##op: case AsmJSSimdType_int32x4: switch (global.simdOperation()) { FOREACH_INT32X4_SIMD_OP(SET_NATIVE_INT32X4) FOREACH_COMMONX4_SIMD_OP(SET_NATIVE_INT32X4) FOREACH_FLOAT32X4_SIMD_OP(FALLTHROUGH) MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("shouldn't have been validated in the first " "place"); } break; case AsmJSSimdType_float32x4: switch (global.simdOperation()) { FOREACH_FLOAT32X4_SIMD_OP(SET_NATIVE_FLOAT32X4) FOREACH_COMMONX4_SIMD_OP(SET_NATIVE_FLOAT32X4) FOREACH_INT32X4_SIMD_OP(FALLTHROUGH) MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("shouldn't have been validated in the first " "place"); } break; #undef FALLTHROUGH #undef SET_NATIVE_FLOAT32X4 #undef SET_NATIVE_INT32X4 #undef SET_NATIVE } if (!native || !IsNativeFunction(v, native)) return LinkFail(cx, "bad SIMD.type.* operation"); return true; }
static bool ValidateGlobalConstant(JSContext *cx, AsmJSModule::Global global, HandleValue globalVal) { RootedPropertyName field(cx, global.constantName()); RootedValue v(cx); if (!GetProperty(cx, globalVal, field, &v)) return false; if (!v.isNumber()) return LinkFail(cx, "global constant value needs to be a number"); // NaN != NaN if (MOZ_DOUBLE_IS_NaN(global.constantValue())) { if (!MOZ_DOUBLE_IS_NaN(v.toNumber())) return LinkFail(cx, "global constant value needs to be NaN"); } else { if (v.toNumber() != global.constantValue()) return LinkFail(cx, "global constant value mismatch"); } return true; }
static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { JS_ASSERT(global.which() == AsmJSModule::Global::Variable); void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const Value &v = global.varInitConstant(); switch (global.varInitCoercion()) { case AsmJS_ToInt32: *(int32_t *)datum = v.toInt32(); break; case AsmJS_ToNumber: *(double *)datum = v.toDouble(); break; case AsmJS_FRound: *(float *)datum = static_cast<float>(v.toDouble()); break; } break; } case AsmJSModule::Global::InitImport: { RootedPropertyName field(cx, global.varImportField()); RootedValue v(cx); if (!GetDataProperty(cx, importVal, field, &v)) return false; 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; } break; } } return true; }
static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { JS_ASSERT(global.which() == AsmJSModule::Global::Variable); void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const 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(); break; case AsmJSNumLit::Double: *(double *)datum = v.toDouble(); break; case AsmJSNumLit::Float: *(float *)datum = static_cast<float>(v.toDouble()); 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"); } } 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; } break; } } return true; }
static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { MOZ_ASSERT(global.which() == AsmJSModule::Global::Variable); void *datum = module.globalVarToGlobalDatum(global); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const AsmJSNumLit &lit = global.varInitNumLit(); switch (lit.which()) { case AsmJSNumLit::Fixnum: case AsmJSNumLit::NegativeInt: case AsmJSNumLit::BigUnsigned: *(int32_t *)datum = lit.scalarValue().toInt32(); break; case AsmJSNumLit::Double: *(double *)datum = lit.scalarValue().toDouble(); break; case AsmJSNumLit::Float: *(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() && !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; }
static bool DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { if (module.isLinked()) return LinkFail(cx, "As a temporary limitation, modules cannot be linked more than " "once. This limitation should be removed in a future release. To " "work around this, compile a second module (e.g., using the " "Function constructor)."); RootedValue globalVal(cx, UndefinedValue()); if (args.length() > 0) globalVal = args[0]; RootedValue importVal(cx, UndefinedValue()); if (args.length() > 1) importVal = args[1]; RootedValue bufferVal(cx, UndefinedValue()); if (args.length() > 2) bufferVal = args[2]; Rooted<ArrayBufferObject*> heap(cx); if (module.hasArrayView()) { if (!IsTypedArrayBuffer(bufferVal)) return LinkFail(cx, "bad ArrayBuffer argument"); heap = &bufferVal.toObject().asArrayBuffer(); if (!IsPowerOfTwo(heap->byteLength()) || heap->byteLength() < AsmJSAllocationGranularity) return LinkFail(cx, "ArrayBuffer byteLength must be a power of two greater than or equal to 4096"); if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); #if defined(JS_CPU_X86) void *heapOffset = (void*)heap->dataPointer(); void *heapLength = (void*)heap->byteLength(); uint8_t *code = module.functionCode(); for (unsigned i = 0; i < module.numHeapAccesses(); i++) { const AsmJSHeapAccess &access = module.heapAccess(i); JSC::X86Assembler::setPointer(access.patchLengthAt(code), heapLength); JSC::X86Assembler::setPointer(access.patchOffsetAt(code), heapOffset); } #endif } AutoObjectVector ffis(cx); if (!ffis.resize(module.numFFIs())) return false; for (unsigned i = 0; i < module.numGlobals(); i++) { AsmJSModule::Global global = module.global(i); switch (global.which()) { case AsmJSModule::Global::Variable: if (!ValidateGlobalVariable(cx, module, global, importVal)) return false; break; case AsmJSModule::Global::FFI: if (!ValidateFFI(cx, global, importVal, &ffis)) return false; break; case AsmJSModule::Global::ArrayView: if (!ValidateArrayView(cx, global, globalVal, bufferVal)) return false; break; case AsmJSModule::Global::MathBuiltin: if (!ValidateMathBuiltin(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateGlobalConstant(cx, global, globalVal)) return false; break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = ffis[module.exit(i).ffiIndex()]->toFunction(); module.setIsLinked(heap); return true; }
static bool ValidateGlobalVariable(JSContext* cx, const AsmJSModule& module, AsmJSModule::Global& global, HandleValue importVal) { void* datum = module.globalData() + global.varGlobalDataOffset(); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { Val v = global.varInitVal(); switch (v.type()) { case ValType::I32: *(int32_t*)datum = v.i32(); break; case ValType::I64: MOZ_CRASH("int64"); case ValType::F32: *(float*)datum = v.f32(); break; case ValType::F64: *(double*)datum = v.f64(); break; case ValType::I32x4: memcpy(datum, v.i32x4(), Simd128DataSize); break; case ValType::F32x4: memcpy(datum, v.f32x4(), Simd128DataSize); break; } break; } case AsmJSModule::Global::InitImport: { RootedPropertyName field(cx, global.varImportField()); RootedValue v(cx); if (!GetDataProperty(cx, importVal, field, &v)) return false; if (!v.isPrimitive() && !HasPureCoercion(cx, v)) return LinkFail(cx, "Imported values must be primitives"); switch (global.varInitImportType()) { case ValType::I32: if (!ToInt32(cx, v, (int32_t*)datum)) return false; break; case ValType::I64: MOZ_CRASH("int64"); case ValType::F32: if (!RoundFloat32(cx, v, (float*)datum)) return false; break; case ValType::F64: if (!ToNumber(cx, v, (double*)datum)) return false; break; case ValType::I32x4: { SimdConstant simdConstant; if (!ToSimdConstant<Int32x4>(cx, v, &simdConstant)) return false; memcpy(datum, simdConstant.asInt32x4(), Simd128DataSize); break; } case ValType::F32x4: { SimdConstant simdConstant; if (!ToSimdConstant<Float32x4>(cx, v, &simdConstant)) return false; memcpy(datum, simdConstant.asFloat32x4(), Simd128DataSize); break; } } break; } } return true; }