#include "wasm/WasmCompile.h"
#include "mozilla/Maybe.h"
#include "mozilla/Unused.h"
#include "jit/ProcessExecutableMemory.h"
#include "util/Text.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmCraneliftCompile.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmOpIter.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
uint32_t wasm::ObservedCPUFeatures() {
enum Arch {
X86 = 0x1,
X64 = 0x2,
ARM = 0x3,
MIPS = 0x4,
MIPS64 = 0x5,
ARM64 = 0x6,
ARCH_BITS = 3
};
#if defined(JS_CODEGEN_X86)
MOZ_ASSERT(uint32_t(jit::CPUInfo::GetSSEVersion()) <=
(UINT32_MAX >> ARCH_BITS));
return X86 | (uint32_t(jit::CPUInfo::GetSSEVersion()) << ARCH_BITS);
#elif defined(JS_CODEGEN_X64)
MOZ_ASSERT(uint32_t(jit::CPUInfo::GetSSEVersion()) <=
(UINT32_MAX >> ARCH_BITS));
return X64 | (uint32_t(jit::CPUInfo::GetSSEVersion()) << ARCH_BITS);
#elif defined(JS_CODEGEN_ARM)
MOZ_ASSERT(jit::GetARMFlags() <= (UINT32_MAX >> ARCH_BITS));
return ARM | (jit::GetARMFlags() << ARCH_BITS);
#elif defined(JS_CODEGEN_ARM64)
MOZ_ASSERT(jit::GetARM64Flags() <= (UINT32_MAX >> ARCH_BITS));
return ARM64 | (jit::GetARM64Flags() << ARCH_BITS);
#elif defined(JS_CODEGEN_MIPS32)
MOZ_ASSERT(jit::GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
return MIPS | (jit::GetMIPSFlags() << ARCH_BITS);
#elif defined(JS_CODEGEN_MIPS64)
MOZ_ASSERT(jit::GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
return MIPS64 | (jit::GetMIPSFlags() << ARCH_BITS);
#elif defined(JS_CODEGEN_NONE)
return 0;
#else
# error "unknown architecture"
#endif
}
SharedCompileArgs CompileArgs::build(JSContext* cx,
ScriptedCaller&& scriptedCaller) {
bool baseline = BaselineCanCompile() && cx->options().wasmBaseline();
bool ion = IonCanCompile() && cx->options().wasmIon();
#ifdef ENABLE_WASM_CRANELIFT
bool cranelift = CraneliftCanCompile() && cx->options().wasmCranelift();
#else
bool cranelift = false;
#endif
#ifdef ENABLE_WASM_GC
bool gc = cx->options().wasmGc();
#else
bool gc = false;
#endif
bool debug = cx->realm()->debuggerObservesAsmJS();
bool sharedMemory =
cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled();
bool forceTiering =
cx->options().testWasmAwaitTier2() || JitOptions.wasmDelayTier2;
if (debug || gc) {
if (!baseline) {
JS_ReportErrorASCII(cx, "can't use wasm debug/gc without baseline");
return nullptr;
}
ion = false;
cranelift = false;
}
if (forceTiering && (!baseline || (!cranelift && !ion))) {
forceTiering = false;
}
MOZ_RELEASE_ASSERT(baseline || ion || cranelift);
CompileArgs* target = cx->new_<CompileArgs>(std::move(scriptedCaller));
if (!target) {
return nullptr;
}
target->baselineEnabled = baseline;
target->ionEnabled = ion;
target->craneliftEnabled = cranelift;
target->debugEnabled = debug;
target->sharedMemoryEnabled = sharedMemory;
target->forceTiering = forceTiering;
target->gcEnabled = gc;
return target;
}
enum class SystemClass {
DesktopX86,
DesktopX64,
DesktopUnknown32,
DesktopUnknown64,
MobileX86,
MobileArm32,
MobileArm64,
MobileUnknown32,
MobileUnknown64
};
static SystemClass ClassifySystem() {
bool isDesktop;
#if defined(ANDROID) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
isDesktop = false;
#else
isDesktop = true;
#endif
if (isDesktop) {
#if defined(JS_CODEGEN_X64)
return SystemClass::DesktopX64;
#elif defined(JS_CODEGEN_X86)
return SystemClass::DesktopX86;
#elif defined(JS_64BIT)
return SystemClass::DesktopUnknown64;
#else
return SystemClass::DesktopUnknown32;
#endif
} else {
#if defined(JS_CODEGEN_X86)
return SystemClass::MobileX86;
#elif defined(JS_CODEGEN_ARM)
return SystemClass::MobileArm32;
#elif defined(JS_CODEGEN_ARM64)
return SystemClass::MobileArm64;
#elif defined(JS_64BIT)
return SystemClass::MobileUnknown64;
#else
return SystemClass::MobileUnknown32;
#endif
}
}
static const double x64Tox86Inflation = 1.25;
static const double x64IonBytesPerBytecode = 2.45;
static const double x86IonBytesPerBytecode =
x64IonBytesPerBytecode * x64Tox86Inflation;
static const double arm32IonBytesPerBytecode = 3.3;
static const double arm64IonBytesPerBytecode = 3.0 / 1.4;
static const double x64BaselineBytesPerBytecode = x64IonBytesPerBytecode * 1.43;
static const double x86BaselineBytesPerBytecode =
x64BaselineBytesPerBytecode * x64Tox86Inflation;
static const double arm32BaselineBytesPerBytecode =
arm32IonBytesPerBytecode * 1.39;
static const double arm64BaselineBytesPerBytecode = 3.0;
static double OptimizedBytesPerBytecode(SystemClass cls) {
switch (cls) {
case SystemClass::DesktopX86:
case SystemClass::MobileX86:
case SystemClass::DesktopUnknown32:
return x86IonBytesPerBytecode;
case SystemClass::DesktopX64:
case SystemClass::DesktopUnknown64:
return x64IonBytesPerBytecode;
case SystemClass::MobileArm32:
case SystemClass::MobileUnknown32:
return arm32IonBytesPerBytecode;
case SystemClass::MobileArm64:
case SystemClass::MobileUnknown64:
return arm64IonBytesPerBytecode;
default:
MOZ_CRASH();
}
}
static double BaselineBytesPerBytecode(SystemClass cls) {
switch (cls) {
case SystemClass::DesktopX86:
case SystemClass::MobileX86:
case SystemClass::DesktopUnknown32:
return x86BaselineBytesPerBytecode;
case SystemClass::DesktopX64:
case SystemClass::DesktopUnknown64:
return x64BaselineBytesPerBytecode;
case SystemClass::MobileArm32:
case SystemClass::MobileUnknown32:
return arm32BaselineBytesPerBytecode;
case SystemClass::MobileArm64:
case SystemClass::MobileUnknown64:
return arm64BaselineBytesPerBytecode;
default:
MOZ_CRASH();
}
}
double wasm::EstimateCompiledCodeSize(Tier tier, size_t bytecodeSize) {
SystemClass cls = ClassifySystem();
switch (tier) {
case Tier::Baseline:
return double(bytecodeSize) * BaselineBytesPerBytecode(cls);
case Tier::Optimized:
return double(bytecodeSize) * OptimizedBytesPerBytecode(cls);
}
MOZ_CRASH("bad tier");
}
static const double tierCutoffMs = 250;
static const double x64IonBytecodesPerMs = 2100;
static const double x86IonBytecodesPerMs = 1500;
static const double arm32IonBytecodesPerMs = 450;
static const double arm64IonBytecodesPerMs = 750;
static const double x64DesktopTierCutoff = x64IonBytecodesPerMs * tierCutoffMs;
static const double x86DesktopTierCutoff = x86IonBytecodesPerMs * tierCutoffMs;
static const double x86MobileTierCutoff = x86DesktopTierCutoff / 2; static const double arm32MobileTierCutoff =
arm32IonBytecodesPerMs * tierCutoffMs;
static const double arm64MobileTierCutoff =
arm64IonBytecodesPerMs * tierCutoffMs;
static double CodesizeCutoff(SystemClass cls) {
switch (cls) {
case SystemClass::DesktopX86:
case SystemClass::DesktopUnknown32:
return x86DesktopTierCutoff;
case SystemClass::DesktopX64:
case SystemClass::DesktopUnknown64:
return x64DesktopTierCutoff;
case SystemClass::MobileX86:
return x86MobileTierCutoff;
case SystemClass::MobileArm32:
case SystemClass::MobileUnknown32:
return arm32MobileTierCutoff;
case SystemClass::MobileArm64:
case SystemClass::MobileUnknown64:
return arm64MobileTierCutoff;
default:
MOZ_CRASH();
}
}
static double EffectiveCores(SystemClass cls, uint32_t cores) {
if (cores <= 3) {
return pow(cores, 0.9);
}
return pow(cores, 0.75);
}
#ifndef JS_64BIT
static const double spaceCutoffPct = 0.9;
#endif
static bool TieringBeneficial(uint32_t codeSize) {
uint32_t cpuCount = HelperThreadState().cpuCount;
MOZ_ASSERT(cpuCount > 0);
if (cpuCount == 1) {
return false;
}
MOZ_ASSERT(HelperThreadState().threadCount >= cpuCount);
uint32_t workers = HelperThreadState().maxWasmCompilationThreads();
uint32_t cores = Min(cpuCount, workers);
SystemClass cls = ClassifySystem();
double cutoffSize = CodesizeCutoff(cls);
double effectiveCores = EffectiveCores(cls, cores);
if ((codeSize / effectiveCores) < cutoffSize) {
return false;
}
#ifndef JS_64BIT
double ionRatio = OptimizedBytesPerBytecode(cls);
double baselineRatio = BaselineBytesPerBytecode(cls);
double needMemory = codeSize * (ionRatio + baselineRatio);
double availMemory = LikelyAvailableExecutableMemory();
double cutoff = spaceCutoffPct * MaxCodeBytesPerProcess;
if ((MaxCodeBytesPerProcess - availMemory) + needMemory > cutoff) {
return false;
}
#endif
return true;
}
CompilerEnvironment::CompilerEnvironment(const CompileArgs& args)
: state_(InitialWithArgs), args_(&args) {}
CompilerEnvironment::CompilerEnvironment(CompileMode mode, Tier tier,
OptimizedBackend optimizedBackend,
DebugEnabled debugEnabled,
bool gcTypesConfigured)
: state_(InitialWithModeTierDebug),
mode_(mode),
tier_(tier),
optimizedBackend_(optimizedBackend),
debug_(debugEnabled),
gcTypes_(gcTypesConfigured) {}
void CompilerEnvironment::computeParameters(bool gcFeatureOptIn) {
MOZ_ASSERT(state_ == InitialWithModeTierDebug);
if (gcTypes_) {
gcTypes_ = gcFeatureOptIn;
}
state_ = Computed;
}
void CompilerEnvironment::computeParameters(Decoder& d, bool gcFeatureOptIn) {
MOZ_ASSERT(!isComputed());
if (state_ == InitialWithModeTierDebug) {
computeParameters(gcFeatureOptIn);
return;
}
bool gcEnabled = args_->gcEnabled && gcFeatureOptIn;
bool baselineEnabled = args_->baselineEnabled;
bool ionEnabled = args_->ionEnabled;
bool debugEnabled = args_->debugEnabled;
bool craneliftEnabled = args_->craneliftEnabled;
bool forceTiering = args_->forceTiering;
bool hasSecondTier = ionEnabled || craneliftEnabled;
MOZ_ASSERT_IF(gcEnabled || debugEnabled, baselineEnabled);
MOZ_ASSERT_IF(forceTiering, baselineEnabled && hasSecondTier);
MOZ_RELEASE_ASSERT(baselineEnabled || ionEnabled || craneliftEnabled);
uint32_t codeSectionSize = 0;
SectionRange range;
if (StartsCodeSection(d.begin(), d.end(), &range)) {
codeSectionSize = range.size;
}
if (baselineEnabled && hasSecondTier && CanUseExtraThreads() &&
(TieringBeneficial(codeSectionSize) || forceTiering)) {
mode_ = CompileMode::Tier1;
tier_ = Tier::Baseline;
} else {
mode_ = CompileMode::Once;
tier_ = hasSecondTier ? Tier::Optimized : Tier::Baseline;
}
optimizedBackend_ =
craneliftEnabled ? OptimizedBackend::Cranelift : OptimizedBackend::Ion;
debug_ = debugEnabled ? DebugEnabled::True : DebugEnabled::False;
gcTypes_ = gcEnabled;
state_ = Computed;
}
template <class DecoderT>
static bool DecodeFunctionBody(DecoderT& d, ModuleGenerator& mg,
uint32_t funcIndex) {
uint32_t bodySize;
if (!d.readVarU32(&bodySize)) {
return d.fail("expected number of function body bytes");
}
if (bodySize > MaxFunctionBytes) {
return d.fail("function body too big");
}
const size_t offsetInModule = d.currentOffset();
const uint8_t* bodyBegin;
if (!d.readBytes(bodySize, &bodyBegin)) {
return d.fail("function body length too big");
}
return mg.compileFuncDef(funcIndex, offsetInModule, bodyBegin,
bodyBegin + bodySize);
}
template <class DecoderT>
static bool DecodeCodeSection(const ModuleEnvironment& env, DecoderT& d,
ModuleGenerator& mg) {
if (!env.codeSection) {
if (env.numFuncDefs() != 0) {
return d.fail("expected code section");
}
return mg.finishFuncDefs();
}
uint32_t numFuncDefs;
if (!d.readVarU32(&numFuncDefs)) {
return d.fail("expected function body count");
}
if (numFuncDefs != env.numFuncDefs()) {
return d.fail(
"function body count does not match function signature count");
}
for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
if (!DecodeFunctionBody(d, mg, env.numFuncImports() + funcDefIndex)) {
return false;
}
}
if (!d.finishSection(*env.codeSection, "code")) {
return false;
}
return mg.finishFuncDefs();
}
SharedModule wasm::CompileBuffer(const CompileArgs& args,
const ShareableBytes& bytecode,
UniqueChars* error,
UniqueCharsVector* warnings,
JS::OptimizedEncodingListener* listener) {
Decoder d(bytecode.bytes, 0, error, warnings);
CompilerEnvironment compilerEnv(args);
ModuleEnvironment env(
args.gcEnabled, &compilerEnv,
args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
if (!DecodeModuleEnvironment(d, &env)) {
return nullptr;
}
ModuleGenerator mg(args, &env, nullptr, error);
if (!mg.init()) {
return nullptr;
}
if (!DecodeCodeSection(env, d, mg)) {
return nullptr;
}
if (!DecodeModuleTail(d, &env)) {
return nullptr;
}
return mg.finishModule(bytecode, listener);
}
void wasm::CompileTier2(const CompileArgs& args, const Bytes& bytecode,
const Module& module, Atomic<bool>* cancelled) {
UniqueChars error;
Decoder d(bytecode, 0, &error);
bool gcTypesConfigured = false; OptimizedBackend optimizedBackend = args.craneliftEnabled
? OptimizedBackend::Cranelift
: OptimizedBackend::Ion;
CompilerEnvironment compilerEnv(CompileMode::Tier2, Tier::Optimized,
optimizedBackend, DebugEnabled::False,
gcTypesConfigured);
ModuleEnvironment env(
gcTypesConfigured, &compilerEnv,
args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
if (!DecodeModuleEnvironment(d, &env)) {
return;
}
ModuleGenerator mg(args, &env, cancelled, &error);
if (!mg.init()) {
return;
}
if (!DecodeCodeSection(env, d, mg)) {
return;
}
if (!DecodeModuleTail(d, &env)) {
return;
}
if (!mg.finishTier2(module)) {
return;
}
}
class StreamingDecoder {
Decoder d_;
const ExclusiveBytesPtr& codeBytesEnd_;
const Atomic<bool>& cancelled_;
public:
StreamingDecoder(const ModuleEnvironment& env, const Bytes& begin,
const ExclusiveBytesPtr& codeBytesEnd,
const Atomic<bool>& cancelled, UniqueChars* error,
UniqueCharsVector* warnings)
: d_(begin, env.codeSection->start, error, warnings),
codeBytesEnd_(codeBytesEnd),
cancelled_(cancelled) {}
bool fail(const char* msg) { return d_.fail(msg); }
bool done() const { return d_.done(); }
size_t currentOffset() const { return d_.currentOffset(); }
bool waitForBytes(size_t numBytes) {
numBytes = Min(numBytes, d_.bytesRemain());
const uint8_t* requiredEnd = d_.currentPosition() + numBytes;
auto codeBytesEnd = codeBytesEnd_.lock();
while (codeBytesEnd < requiredEnd) {
if (cancelled_) {
return false;
}
codeBytesEnd.wait();
}
return true;
}
bool readVarU32(uint32_t* u32) {
return waitForBytes(MaxVarU32DecodedBytes) && d_.readVarU32(u32);
}
bool readBytes(size_t size, const uint8_t** begin) {
return waitForBytes(size) && d_.readBytes(size, begin);
}
bool finishSection(const SectionRange& range, const char* name) {
return d_.finishSection(range, name);
}
};
static SharedBytes CreateBytecode(const Bytes& env, const Bytes& code,
const Bytes& tail, UniqueChars* error) {
size_t size = env.length() + code.length() + tail.length();
if (size > MaxModuleBytes) {
*error = DuplicateString("module too big");
return nullptr;
}
MutableBytes bytecode = js_new<ShareableBytes>();
if (!bytecode || !bytecode->bytes.resize(size)) {
return nullptr;
}
uint8_t* p = bytecode->bytes.begin();
memcpy(p, env.begin(), env.length());
p += env.length();
memcpy(p, code.begin(), code.length());
p += code.length();
memcpy(p, tail.begin(), tail.length());
p += tail.length();
MOZ_ASSERT(p == bytecode->end());
return bytecode;
}
SharedModule wasm::CompileStreaming(
const CompileArgs& args, const Bytes& envBytes, const Bytes& codeBytes,
const ExclusiveBytesPtr& codeBytesEnd,
const ExclusiveStreamEndData& exclusiveStreamEnd,
const Atomic<bool>& cancelled, UniqueChars* error,
UniqueCharsVector* warnings) {
CompilerEnvironment compilerEnv(args);
ModuleEnvironment env(
args.gcEnabled, &compilerEnv,
args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
{
Decoder d(envBytes, 0, error, warnings);
if (!DecodeModuleEnvironment(d, &env)) {
return nullptr;
}
if (!env.codeSection) {
d.fail("unknown section before code section");
return nullptr;
}
MOZ_RELEASE_ASSERT(env.codeSection->size == codeBytes.length());
MOZ_RELEASE_ASSERT(d.done());
}
ModuleGenerator mg(args, &env, &cancelled, error);
if (!mg.init()) {
return nullptr;
}
{
StreamingDecoder d(env, codeBytes, codeBytesEnd, cancelled, error,
warnings);
if (!DecodeCodeSection(env, d, mg)) {
return nullptr;
}
MOZ_RELEASE_ASSERT(d.done());
}
{
auto streamEnd = exclusiveStreamEnd.lock();
while (!streamEnd->reached) {
if (cancelled) {
return nullptr;
}
streamEnd.wait();
}
}
const StreamEndData& streamEnd = exclusiveStreamEnd.lock();
const Bytes& tailBytes = *streamEnd.tailBytes;
{
Decoder d(tailBytes, env.codeSection->end(), error, warnings);
if (!DecodeModuleTail(d, &env)) {
return nullptr;
}
MOZ_RELEASE_ASSERT(d.done());
}
SharedBytes bytecode = CreateBytecode(envBytes, codeBytes, tailBytes, error);
if (!bytecode) {
return nullptr;
}
return mg.finishModule(*bytecode, streamEnd.tier2Listener);
}