#include "wasm/WasmCraneliftCompile.h"
#include "js/Printf.h"
#include "wasm/cranelift/baldrapi.h"
#include "wasm/cranelift/clifapi.h"
#include "wasm/WasmGenerator.h"
#include "jit/MacroAssembler-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
bool wasm::CraneliftCanCompile() {
#ifdef JS_CODEGEN_X64
return true;
#else
return false;
#endif
}
static inline SymbolicAddress ToSymbolicAddress(BD_SymbolicAddress bd) {
switch (bd) {
case BD_SymbolicAddress::MemoryGrow:
return SymbolicAddress::MemoryGrow;
case BD_SymbolicAddress::MemorySize:
return SymbolicAddress::MemorySize;
case BD_SymbolicAddress::FloorF32:
return SymbolicAddress::FloorF;
case BD_SymbolicAddress::FloorF64:
return SymbolicAddress::FloorD;
case BD_SymbolicAddress::CeilF32:
return SymbolicAddress::CeilF;
case BD_SymbolicAddress::CeilF64:
return SymbolicAddress::CeilD;
case BD_SymbolicAddress::NearestF32:
return SymbolicAddress::NearbyIntF;
case BD_SymbolicAddress::NearestF64:
return SymbolicAddress::NearbyIntD;
case BD_SymbolicAddress::TruncF32:
return SymbolicAddress::TruncF;
case BD_SymbolicAddress::TruncF64:
return SymbolicAddress::TruncD;
case BD_SymbolicAddress::Limit:
break;
}
MOZ_CRASH("unknown baldrdash symbolic address");
}
static bool GenerateCraneliftCode(WasmMacroAssembler& masm,
const CraneliftCompiledFunc& func,
const FuncTypeIdDesc& funcTypeId,
uint32_t lineOrBytecode,
uint32_t funcBytecodeSize,
FuncOffsets* offsets) {
wasm::GenerateFunctionPrologue(masm, funcTypeId, mozilla::Nothing(), offsets);
if (func.framePushed < MAX_UNCHECKED_LEAF_FRAME_SIZE && !func.containsCalls) {
masm.reserveStack(func.framePushed);
} else {
masm.wasmReserveStackChecked(func.framePushed,
BytecodeOffset(lineOrBytecode));
}
MOZ_ASSERT(masm.framePushed() == func.framePushed);
uint32_t funcBase = masm.currentOffset();
if (!masm.appendRawCode(func.code, func.codeSize)) {
return false;
}
masm.loadWasmTlsRegFromFrame();
masm.loadWasmPinnedRegsFromTls();
wasm::GenerateFunctionEpilogue(masm, func.framePushed, offsets);
masm.flush();
if (masm.oom()) {
return false;
}
offsets->end = masm.currentOffset();
for (size_t i = 0; i < func.numMetadata; i++) {
const CraneliftMetadataEntry& metadata = func.metadatas[i];
CheckedInt<size_t> offset = funcBase;
offset += metadata.offset;
if (!offset.isValid()) {
return false;
}
#ifdef DEBUG
MOZ_ASSERT(offset.value() >= offsets->normalEntry);
MOZ_ASSERT(offset.value() < offsets->ret);
if (metadata.srcLoc > 0 && lineOrBytecode > 0) {
MOZ_ASSERT(metadata.srcLoc >= lineOrBytecode);
MOZ_ASSERT(metadata.srcLoc < lineOrBytecode + funcBytecodeSize);
}
#endif
uint32_t bytecodeOffset =
metadata.srcLoc ? metadata.srcLoc : lineOrBytecode;
switch (metadata.which) {
case CraneliftMetadataEntry::Which::DirectCall: {
CallSiteDesc desc(bytecodeOffset, CallSiteDesc::Func);
masm.append(desc, CodeOffset(offset.value()), metadata.extra);
break;
}
case CraneliftMetadataEntry::Which::IndirectCall: {
CallSiteDesc desc(bytecodeOffset, CallSiteDesc::Dynamic);
masm.append(desc, CodeOffset(offset.value()));
break;
}
case CraneliftMetadataEntry::Which::Trap: {
Trap trap = (Trap)metadata.extra;
BytecodeOffset trapOffset(bytecodeOffset);
masm.append(trap, wasm::TrapSite(offset.value(), trapOffset));
break;
}
case CraneliftMetadataEntry::Which::MemoryAccess: {
BytecodeOffset trapOffset(bytecodeOffset);
masm.appendOutOfBoundsTrap(trapOffset, offset.value());
break;
}
case CraneliftMetadataEntry::Which::SymbolicAccess: {
SymbolicAddress sym =
ToSymbolicAddress(BD_SymbolicAddress(metadata.extra));
masm.append(SymbolicAccess(CodeOffset(offset.value()), sym));
break;
}
default: { MOZ_CRASH("unknown cranelift metadata kind"); }
}
}
return true;
}
class AutoCranelift {
CraneliftStaticEnvironment staticEnv_;
CraneliftModuleEnvironment env_;
CraneliftCompiler* compiler_;
public:
explicit AutoCranelift(const ModuleEnvironment& env)
: env_(env), compiler_(nullptr) {}
bool init() {
compiler_ = cranelift_compiler_create(&staticEnv_, &env_);
return !!compiler_;
}
~AutoCranelift() {
if (compiler_) {
cranelift_compiler_destroy(compiler_);
}
}
operator CraneliftCompiler*() { return compiler_; }
};
CraneliftFuncCompileInput::CraneliftFuncCompileInput(
const FuncCompileInput& func)
: bytecode(func.begin),
bytecodeSize(func.end - func.begin),
index(func.index),
offset_in_module(func.lineOrBytecode) {}
#ifndef WASM_HUGE_MEMORY
static_assert(offsetof(TlsData, boundsCheckLimit) == sizeof(size_t),
"fix make_heap() in wasm2clif.rs");
#endif
CraneliftStaticEnvironment::CraneliftStaticEnvironment()
:
#ifdef JS_CODEGEN_X64
hasSse2(Assembler::HasSSE2()),
hasSse3(Assembler::HasSSE3()),
hasSse41(Assembler::HasSSE41()),
hasSse42(Assembler::HasSSE42()),
hasPopcnt(Assembler::HasPOPCNT()),
hasAvx(Assembler::HasAVX()),
hasBmi1(Assembler::HasBMI1()),
hasBmi2(Assembler::HasBMI2()),
hasLzcnt(Assembler::HasLZCNT()),
#else
hasSse2(false),
hasSse3(false),
hasSse41(false),
hasSse42(false),
hasPopcnt(false),
hasAvx(false),
hasBmi1(false),
hasBmi2(false),
hasLzcnt(false),
#endif
staticMemoryBound(
#ifdef WASM_HUGE_MEMORY
IndexRange
#else
0
#endif
),
memoryGuardSize(OffsetGuardLimit),
instanceTlsOffset(offsetof(TlsData, instance)),
interruptTlsOffset(offsetof(TlsData, interrupt)),
cxTlsOffset(offsetof(TlsData, cx)),
realmCxOffset(JSContext::offsetOfRealm()),
realmTlsOffset(offsetof(TlsData, realm)),
realmFuncImportTlsOffset(offsetof(FuncImportTls, realm)) {
}
static size_t globalToTlsOffset(size_t globalOffset) {
return offsetof(wasm::TlsData, globalArea) + globalOffset;
}
CraneliftModuleEnvironment::CraneliftModuleEnvironment(
const ModuleEnvironment& env)
: env(&env), min_memory_length(env.minMemoryLength) {}
TypeCode env_unpack(BD_ValType valType) {
return TypeCode(UnpackTypeCodeType(PackedTypeCode(valType.packed)));
}
const FuncTypeWithId* env_function_signature(
const CraneliftModuleEnvironment* wrapper, size_t funcIndex) {
return wrapper->env->funcTypes[funcIndex];
}
size_t env_func_import_tls_offset(const CraneliftModuleEnvironment* wrapper,
size_t funcIndex) {
return globalToTlsOffset(
wrapper->env->funcImportGlobalDataOffsets[funcIndex]);
}
bool env_func_is_import(const CraneliftModuleEnvironment* wrapper,
size_t funcIndex) {
return wrapper->env->funcIsImport(funcIndex);
}
const FuncTypeWithId* env_signature(const CraneliftModuleEnvironment* wrapper,
size_t funcTypeIndex) {
return &wrapper->env->types[funcTypeIndex].funcType();
}
const TableDesc* env_table(const CraneliftModuleEnvironment* wrapper,
size_t tableIndex) {
return &wrapper->env->tables[tableIndex];
}
const GlobalDesc* env_global(const CraneliftModuleEnvironment* wrapper,
size_t globalIndex) {
return &wrapper->env->globals[globalIndex];
}
bool wasm::CraneliftCompileFunctions(const ModuleEnvironment& env,
LifoAlloc& lifo,
const FuncCompileInputVector& inputs,
CompiledCode* code, UniqueChars* error) {
MOZ_RELEASE_ASSERT(CraneliftCanCompile());
MOZ_ASSERT(env.tier() == Tier::Optimized);
MOZ_ASSERT(env.optimizedBackend() == OptimizedBackend::Cranelift);
MOZ_ASSERT(!env.isAsmJS());
AutoCranelift compiler(env);
if (!compiler.init()) {
return false;
}
TempAllocator alloc(&lifo);
JitContext jitContext(&alloc);
WasmMacroAssembler masm(alloc);
MOZ_ASSERT(IsCompilingWasm());
MOZ_ASSERT(code->empty());
if (!code->swap(masm)) {
return false;
}
for (const FuncCompileInput& func : inputs) {
Decoder d(func.begin, func.end, func.lineOrBytecode, error);
size_t funcBytecodeSize = func.end - func.begin;
if (!ValidateFunctionBody(env, func.index, funcBytecodeSize, d)) {
return false;
}
CraneliftFuncCompileInput clifInput(func);
CraneliftCompiledFunc clifFunc;
if (!cranelift_compile_function(compiler, &clifInput, &clifFunc)) {
*error = JS_smprintf("Cranelift error in clifFunc #%u", clifInput.index);
return false;
}
uint32_t lineOrBytecode = func.lineOrBytecode;
const FuncTypeIdDesc& funcTypeId = env.funcTypes[clifInput.index]->id;
FuncOffsets offsets;
if (!GenerateCraneliftCode(masm, clifFunc, funcTypeId, lineOrBytecode,
funcBytecodeSize, &offsets)) {
return false;
}
if (!code->codeRanges.emplaceBack(func.index, lineOrBytecode, offsets)) {
return false;
}
}
masm.finish();
if (masm.oom()) {
return false;
}
return code->swap(masm);
}
static_assert(offsetof(wasm::TlsData, memoryBase) == 0, "memory base moved");
static_assert(offsetof(wasm::FuncImportTls, code) == 0,
"Import code field moved");
static_assert(offsetof(wasm::FuncImportTls, tls) == sizeof(void*),
"Import tls moved");
bool global_isConstant(const GlobalDesc* global) {
return global->isConstant();
}
bool global_isIndirect(const GlobalDesc* global) {
return global->isIndirect();
}
BD_ConstantValue global_constantValue(const GlobalDesc* global) {
Val value(global->constantValue());
BD_ConstantValue v;
v.t = TypeCode(value.type().code());
switch (v.t) {
case TypeCode::I32:
v.u.i32 = value.i32();
break;
case TypeCode::I64:
v.u.i64 = value.i64();
break;
case TypeCode::F32:
v.u.f32 = value.f32();
break;
case TypeCode::F64:
v.u.f64 = value.f64();
break;
default:
MOZ_CRASH("Bad type");
}
return v;
}
#ifdef DEBUG
static bool IsCraneliftCompatible(TypeCode type) {
switch (type) {
case TypeCode::I32:
case TypeCode::I64:
case TypeCode::F32:
case TypeCode::F64:
return true;
default:
return false;
}
}
#endif
TypeCode global_type(const GlobalDesc* global) {
TypeCode type = TypeCode(global->type().code());
MOZ_ASSERT(IsCraneliftCompatible(type));
return type;
}
size_t global_tlsOffset(const GlobalDesc* global) {
return globalToTlsOffset(global->offset());
}
size_t table_tlsOffset(const TableDesc* table) {
MOZ_RELEASE_ASSERT(table->kind == TableKind::AnyFunction ||
table->kind == TableKind::TypedFunction,
"cranelift doesn't support AnyRef tables yet.");
return globalToTlsOffset(table->globalDataOffset);
}
size_t funcType_numArgs(const FuncTypeWithId* funcType) {
return funcType->args().length();
}
const BD_ValType* funcType_args(const FuncTypeWithId* funcType) {
#ifdef DEBUG
for (ValType valType : funcType->args()) {
MOZ_ASSERT(IsCraneliftCompatible(TypeCode(valType.code())));
}
#endif
static_assert(sizeof(BD_ValType) == sizeof(ValType), "update BD_ValType");
return (const BD_ValType*)&funcType->args()[0];
}
TypeCode funcType_retType(const FuncTypeWithId* funcType) {
return TypeCode(funcType->ret().code());
}
FuncTypeIdDescKind funcType_idKind(const FuncTypeWithId* funcType) {
return funcType->id.kind();
}
size_t funcType_idImmediate(const FuncTypeWithId* funcType) {
return funcType->id.immediate();
}
size_t funcType_idTlsOffset(const FuncTypeWithId* funcType) {
return globalToTlsOffset(funcType->id.globalDataOffset());
}