Bug 1286517: Implement Global section in WebAssembly; r=luke

MozReview-Commit-ID: aD6rBQ3e57

--HG--
extra : rebase_source : 6987461ccfee07a5f494745fcb02cbac825def95
This commit is contained in:
Benjamin Bouvier 2016-07-25 19:50:20 +02:00
parent 8839c5fde7
commit 3bb9e21ec3
17 changed files with 703 additions and 146 deletions

View File

@ -111,7 +111,6 @@ class AsmJSGlobal
Which which_;
union {
struct {
uint32_t globalDataOffset_;
VarInitKind initKind_;
union {
ValType importType_;
@ -150,10 +149,6 @@ class AsmJSGlobal
Which which() const {
return pod.which_;
}
uint32_t varGlobalDataOffset() const {
MOZ_ASSERT(pod.which_ == Variable);
return pod.u.var.globalDataOffset_;
}
VarInitKind varInitKind() const {
MOZ_ASSERT(pod.which_ == Variable);
return pod.u.var.initKind_;
@ -1447,7 +1442,6 @@ class MOZ_STACK_CLASS ModuleValidator
}
unsigned varOrConstIndex() const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
MOZ_ASSERT(u.varOrConst.index_ != -1u);
return u.varOrConst.index_;
}
bool isConst() const {
@ -1851,7 +1845,7 @@ class MOZ_STACK_CLASS ModuleValidator
MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));
uint32_t index;
if (!mg_.allocateGlobal(type.canonicalToValType(), isConst, &index))
if (!mg_.addGlobal(type.canonicalToValType(), isConst, &index))
return false;
Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
@ -1868,7 +1862,6 @@ class MOZ_STACK_CLASS ModuleValidator
AsmJSGlobal g(AsmJSGlobal::Variable, nullptr);
g.pod.u.var.initKind_ = AsmJSGlobal::InitConstant;
g.pod.u.var.u.val_ = lit.value();
g.pod.u.var.globalDataOffset_ = mg_.global(index).globalDataOffset;
return asmJSMetadata_->asmJSGlobals.append(Move(g));
}
bool addGlobalVarImport(PropertyName* var, PropertyName* field, Type type, bool isConst) {
@ -1880,7 +1873,7 @@ class MOZ_STACK_CLASS ModuleValidator
uint32_t index;
ValType valType = type.canonicalToValType();
if (!mg_.allocateGlobal(valType, isConst, &index))
if (!mg_.addGlobal(valType, isConst, &index))
return false;
Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
@ -1895,7 +1888,6 @@ class MOZ_STACK_CLASS ModuleValidator
AsmJSGlobal g(AsmJSGlobal::Variable, Move(fieldChars));
g.pod.u.var.initKind_ = AsmJSGlobal::InitImport;
g.pod.u.var.u.importType_ = valType;
g.pod.u.var.globalDataOffset_ = mg_.global(index).globalDataOffset;
return asmJSMetadata_->asmJSGlobals.append(Move(g));
}
bool addArrayView(PropertyName* var, Scalar::Type vt, PropertyName* maybeField) {
@ -7817,25 +7809,9 @@ CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata, HandleValue bufferVal,
}
static bool
TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata& metadata,
MutableHandleWasmInstanceObject instanceObj, MutableHandleObject exportObj)
GetImports(JSContext* cx, const AsmJSMetadata& metadata, HandleValue globalVal,
HandleValue importVal, MutableHandle<FunctionVector> funcImports, ValVector* valImports)
{
HandleValue globalVal = args.get(0);
HandleValue importVal = args.get(1);
HandleValue bufferVal = args.get(2);
RootedArrayBufferObjectMaybeShared buffer(cx);
RootedWasmMemoryObject memory(cx);
if (module.metadata().usesMemory()) {
if (!CheckBuffer(cx, metadata, bufferVal, &buffer))
return false;
memory = WasmMemoryObject::create(cx, buffer, nullptr);
if (!memory)
return false;
}
Vector<Val> valImports(cx);
Rooted<FunctionVector> ffis(cx, FunctionVector(cx));
if (!ffis.resize(metadata.numFFIs))
return false;
@ -7843,12 +7819,10 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata
for (const AsmJSGlobal& global : metadata.asmJSGlobals) {
switch (global.which()) {
case AsmJSGlobal::Variable: {
// We don't have any global data into which to write the imported
// values until after instantiation, so save them in a Vector.
Val val;
if (!ValidateGlobalVariable(cx, global, importVal, &val))
return false;
if (!valImports.append(val))
if (!valImports->append(val))
return false;
break;
}
@ -7884,14 +7858,40 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata
}
}
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
for (const AsmJSImport& import : metadata.asmJSImports) {
if (!funcs.append(ffis[import.ffiIndex()]))
if (!funcImports.append(ffis[import.ffiIndex()]))
return false;
}
return true;
}
static bool
TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata& metadata,
MutableHandleWasmInstanceObject instanceObj, MutableHandleObject exportObj)
{
HandleValue globalVal = args.get(0);
HandleValue importVal = args.get(1);
HandleValue bufferVal = args.get(2);
RootedArrayBufferObjectMaybeShared buffer(cx);
RootedWasmMemoryObject memory(cx);
if (module.metadata().usesMemory()) {
if (!CheckBuffer(cx, metadata, bufferVal, &buffer))
return false;
memory = WasmMemoryObject::create(cx, buffer, nullptr);
if (!memory)
return false;
}
ValVector valImports;
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
if (!GetImports(cx, metadata, globalVal, importVal, &funcs, &valImports))
return false;
RootedWasmTableObject table(cx);
if (!module.instantiate(cx, funcs, table, memory, nullptr, instanceObj))
if (!module.instantiate(cx, funcs, table, memory, valImports, nullptr, instanceObj))
return false;
RootedValue exportObjVal(cx);
@ -7900,16 +7900,6 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata
MOZ_RELEASE_ASSERT(exportObjVal.isObject());
exportObj.set(&exportObjVal.toObject());
// Now write the imported values into global data.
uint8_t* globalData = instanceObj->instance().codeSegment().globalData();
uint32_t valIndex = 0;
for (const AsmJSGlobal& global : metadata.asmJSGlobals) {
if (global.which() == AsmJSGlobal::Variable)
valImports[valIndex++].writePayload(globalData + global.varGlobalDataOffset());
}
MOZ_ASSERT(valIndex == valImports.length());
return true;
}

View File

@ -5468,28 +5468,49 @@ BaseCompiler::emitGetGlobal()
const GlobalDesc& global = mg_.globals[id];
switch (global.type) {
if (global.isConstant()) {
Val value = global.constantValue();
switch (value.type()) {
case ValType::I32:
pushI32(value.i32());
break;
case ValType::I64:
pushI64(value.i64());
break;
case ValType::F32:
pushF32(value.f32());
break;
case ValType::F64:
pushF64(value.f64());
break;
default:
MOZ_CRASH("Global constant type");
}
return true;
}
switch (global.type()) {
case ValType::I32: {
RegI32 rv = needI32();
loadGlobalVarI32(global.globalDataOffset, rv);
loadGlobalVarI32(global.offset(), rv);
pushI32(rv);
break;
}
case ValType::I64: {
RegI64 rv = needI64();
loadGlobalVarI64(global.globalDataOffset, rv);
loadGlobalVarI64(global.offset(), rv);
pushI64(rv);
break;
}
case ValType::F32: {
RegF32 rv = needF32();
loadGlobalVarF32(global.globalDataOffset, rv);
loadGlobalVarF32(global.offset(), rv);
pushF32(rv);
break;
}
case ValType::F64: {
RegF64 rv = needF64();
loadGlobalVarF64(global.globalDataOffset, rv);
loadGlobalVarF64(global.offset(), rv);
pushF64(rv);
break;
}
@ -5513,28 +5534,28 @@ BaseCompiler::emitSetGlobal()
const GlobalDesc& global = mg_.globals[id];
switch (global.type) {
switch (global.type()) {
case ValType::I32: {
RegI32 rv = popI32();
storeGlobalVarI32(global.globalDataOffset, rv);
storeGlobalVarI32(global.offset(), rv);
pushI32(rv);
break;
}
case ValType::I64: {
RegI64 rv = popI64();
storeGlobalVarI64(global.globalDataOffset, rv);
storeGlobalVarI64(global.offset(), rv);
pushI64(rv);
break;
}
case ValType::F32: {
RegF32 rv = popF32();
storeGlobalVarF32(global.globalDataOffset, rv);
storeGlobalVarF32(global.offset(), rv);
pushF32(rv);
break;
}
case ValType::F64: {
RegF64 rv = popF64();
storeGlobalVarF64(global.globalDataOffset, rv);
storeGlobalVarF64(global.offset(), rv);
pushF64(rv);
break;
}

View File

@ -28,6 +28,7 @@ static const uint32_t MagicNumber = 0x6d736100; // "\0asm"
static const uint32_t EncodingVersion = 0x0b;
static const char TypeSectionId[] = "type";
static const char GlobalSectionId[] = "global";
static const char ImportSectionId[] = "import";
static const char FunctionSectionId[] = "function";
static const char TableSectionId[] = "table";
@ -70,7 +71,8 @@ enum class DefinitionKind
{
Function = 0x00,
Table = 0x01,
Memory = 0x02
Memory = 0x02,
Global = 0x03
};
enum class ResizableFlags
@ -80,6 +82,12 @@ enum class ResizableFlags
AllowedMask = 0x3
};
enum class GlobalFlags
{
IsMutable = 0x1,
AllowedMask = 0x1
};
enum class Expr
{
// Control flow operators

View File

@ -1211,7 +1211,7 @@ ExprIter<Policy>::readGetGlobal(const GlobalDescVector& globals, uint32_t* id)
if (Validate && validateId >= globals.length())
return fail("get_global index out of range");
if (!push(ToExprType(globals[validateId].type)))
if (!push(ToExprType(globals[validateId].type())))
return false;
if (Output)
@ -1233,7 +1233,10 @@ ExprIter<Policy>::readSetGlobal(const GlobalDescVector& globals, uint32_t* id, V
if (Validate && validateId >= globals.length())
return fail("set_global index out of range");
if (!topWithType(ToExprType(globals[validateId].type), value))
if (Validate && !globals[validateId].isMutable())
return fail("can't write an immutable global");
if (!topWithType(ToExprType(globals[validateId].type()), value))
return false;
if (Output)

View File

@ -417,6 +417,7 @@ Metadata::serializedSize() const
SerializedVectorSize(funcImports) +
SerializedVectorSize(funcExports) +
SerializedVectorSize(sigIds) +
SerializedPodVectorSize(globals) +
SerializedPodVectorSize(tables) +
SerializedPodVectorSize(memoryAccesses) +
SerializedPodVectorSize(boundsChecks) +
@ -435,6 +436,7 @@ Metadata::serialize(uint8_t* cursor) const
cursor = SerializeVector(cursor, funcImports);
cursor = SerializeVector(cursor, funcExports);
cursor = SerializeVector(cursor, sigIds);
cursor = SerializePodVector(cursor, globals);
cursor = SerializePodVector(cursor, tables);
cursor = SerializePodVector(cursor, memoryAccesses);
cursor = SerializePodVector(cursor, boundsChecks);
@ -454,6 +456,7 @@ Metadata::deserialize(const uint8_t* cursor)
(cursor = DeserializeVector(cursor, &funcImports)) &&
(cursor = DeserializeVector(cursor, &funcExports)) &&
(cursor = DeserializeVector(cursor, &sigIds)) &&
(cursor = DeserializePodVector(cursor, &globals)) &&
(cursor = DeserializePodVector(cursor, &tables)) &&
(cursor = DeserializePodVector(cursor, &memoryAccesses)) &&
(cursor = DeserializePodVector(cursor, &boundsChecks)) &&
@ -472,6 +475,7 @@ Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
return SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
SizeOfVectorExcludingThis(funcExports, mallocSizeOf) +
SizeOfVectorExcludingThis(sigIds, mallocSizeOf) +
globals.sizeOfExcludingThis(mallocSizeOf) +
tables.sizeOfExcludingThis(mallocSizeOf) +
memoryAccesses.sizeOfExcludingThis(mallocSizeOf) +
boundsChecks.sizeOfExcludingThis(mallocSizeOf) +

View File

@ -424,7 +424,6 @@ typedef Vector<char16_t, 64> TwoByteName;
// Metadata is built incrementally by ModuleGenerator and then shared immutably
// between modules.
class MetadataCacheablePod
{
static const uint32_t NO_START_FUNCTION = UINT32_MAX;
@ -467,6 +466,7 @@ struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
FuncImportVector funcImports;
FuncExportVector funcExports;
SigWithIdVector sigIds;
GlobalDescVector globals;
TableDescVector tables;
MemoryAccessVector memoryAccesses;
BoundsCheckVector boundsChecks;

View File

@ -236,6 +236,10 @@ DecodeExpr(FunctionDecoder& f)
return f.iter().readGetLocal(f.locals(), nullptr);
case Expr::SetLocal:
return f.iter().readSetLocal(f.locals(), nullptr, nullptr);
case Expr::GetGlobal:
return f.iter().readGetGlobal(f.mg().globals(), nullptr);
case Expr::SetGlobal:
return f.iter().readSetGlobal(f.mg().globals(), nullptr, nullptr);
case Expr::Select:
return f.iter().readSelect(nullptr, nullptr, nullptr, nullptr);
case Expr::Block:
@ -746,6 +750,48 @@ DecodeResizableTable(Decoder& d, ModuleGeneratorData* init)
return init->tables.append(table);
}
static bool
DecodeGlobalType(Decoder& d, ValType* type, bool* isMutable)
{
if (!d.readValType(type))
return Fail(d, "bad global type");
if (*type == ValType::I64 && !IsI64Implemented())
return Fail(d, "int64 NYI");
uint32_t flags;
if (!d.readVarU32(&flags))
return Fail(d, "expected flags");
if (flags & ~uint32_t(GlobalFlags::AllowedMask))
return Fail(d, "unexpected bits set in flags");
*isMutable = flags & uint32_t(GlobalFlags::IsMutable);
return true;
}
static bool
GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable)
{
switch (type) {
case ValType::I32:
case ValType::F32:
case ValType::F64:
break;
case ValType::I64:
if (!JitOptions.wasmTestMode)
return Fail(d, "can't import/export an Int64 global to JS");
break;
default:
return Fail(d, "unexpected variable type in global import/export");
}
if (isMutable)
return Fail(d, "can't import/export mutable globals in the MVP");
return true;
}
static bool
DecodeImport(Decoder& d, bool newFormat, ModuleGeneratorData* init, ImportVector* imports)
{
@ -810,6 +856,17 @@ DecodeImport(Decoder& d, bool newFormat, ModuleGeneratorData* init, ImportVector
return false;
break;
}
case DefinitionKind::Global: {
ValType type;
bool isMutable;
if (!DecodeGlobalType(d, &type, &isMutable))
return false;
if (!GlobalIsJSCompatible(d, type, isMutable))
return false;
if (!init->globals.append(GlobalDesc(type, isMutable, init->globals.length())))
return false;
break;
}
default:
return Fail(d, "unsupported import kind");
}
@ -946,6 +1003,105 @@ DecodeMemorySection(Decoder& d, bool newFormat, ModuleGeneratorData* init, bool*
return true;
}
static bool
DecodeInitializerExpression(Decoder& d, const GlobalDescVector& globals, ValType expected,
InitExpr* init)
{
Expr expr;
if (!d.readExpr(&expr))
return Fail(d, "failed to read initializer type");
switch (expr) {
case Expr::I32Const: {
int32_t i32;
if (!d.readVarS32(&i32))
return Fail(d, "failed to read initializer i32 expression");
*init = InitExpr(Val(uint32_t(i32)));
break;
}
case Expr::I64Const: {
int64_t i64;
if (!d.readVarS64(&i64))
return Fail(d, "failed to read initializer i64 expression");
*init = InitExpr(Val(uint64_t(i64)));
break;
}
case Expr::F32Const: {
float f32;
if (!d.readFixedF32(&f32))
return Fail(d, "failed to read initializer f32 expression");
*init = InitExpr(Val(f32));
break;
}
case Expr::F64Const: {
double f64;
if (!d.readFixedF64(&f64))
return Fail(d, "failed to read initializer f64 expression");
*init = InitExpr(Val(f64));
break;
}
case Expr::GetGlobal: {
uint32_t i;
if (!d.readVarU32(&i))
return Fail(d, "failed to read get_global index in initializer expression");
if (i >= globals.length())
return Fail(d, "global index out of range in initializer expression");
if (!globals[i].isImport() || globals[i].isMutable())
return Fail(d, "initializer expression must reference a global immutable import");
*init = InitExpr(i, globals[i].type());
break;
}
default: {
return Fail(d, "unexpected initializer expression");
}
}
if (expected != init->type())
return Fail(d, "type mismatch: initializer type and expected type don't match");
Expr end;
if (!d.readExpr(&end) || end != Expr::End)
return Fail(d, "failed to read end of initializer expression");
return true;
}
static bool
DecodeGlobalSection(Decoder& d, ModuleGeneratorData* init)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(GlobalSectionId, &sectionStart, &sectionSize))
return Fail(d, "failed to start section");
if (sectionStart == Decoder::NotStarted)
return true;
uint32_t numGlobals;
if (!d.readVarU32(&numGlobals))
return Fail(d, "expected number of globals");
if (numGlobals > MaxGlobals)
return Fail(d, "too many globals");
for (uint32_t i = 0; i < numGlobals; i++) {
ValType type;
bool isMutable;
if (!DecodeGlobalType(d, &type, &isMutable))
return false;
InitExpr initializer;
if (!DecodeInitializerExpression(d, init->globals, type, &initializer))
return false;
if (!init->globals.append(GlobalDesc(initializer, isMutable)))
return false;
}
if (!d.finishSection(sectionStart, sectionSize))
return Fail(d, "globals section byte size mismatch");
return true;
}
typedef HashSet<const char*, CStringHasher, SystemAllocPolicy> CStringSet;
static UniqueChars
@ -1032,6 +1188,20 @@ DecodeExport(Decoder& d, bool newFormat, ModuleGenerator& mg, CStringSet* dupSet
return mg.addMemoryExport(Move(fieldName));
}
case DefinitionKind::Global: {
uint32_t globalIndex;
if (!d.readVarU32(&globalIndex))
return Fail(d, "expected global index");
if (globalIndex >= mg.globals().length())
return Fail(d, "exported global index out of bounds");
const GlobalDesc& global = mg.globals()[globalIndex];
if (!GlobalIsJSCompatible(d, global.type(), global.isMutable()))
return false;
return mg.addGlobalExport(Move(fieldName), globalIndex);
}
default:
return Fail(d, "unexpected export kind");
}
@ -1459,6 +1629,9 @@ wasm::Compile(const ShareableBytes& bytecode, CompileArgs&& args, UniqueChars* e
if (!DecodeMemorySection(d, newFormat, init.get(), &memoryExported))
return nullptr;
if (!DecodeGlobalSection(d, init.get()))
return nullptr;
ModuleGenerator mg(Move(imports));
if (!mg.init(Move(init), Move(args)))
return nullptr;

View File

@ -172,6 +172,13 @@ ModuleGenerator::init(UniqueModuleGeneratorData shared, CompileArgs&& args,
sig.id = SigIdDesc::immediate(sig);
}
}
for (GlobalDesc& global : shared_->globals) {
if (global.isConstant())
continue;
if (!allocateGlobal(&global))
return false;
}
} else {
MOZ_ASSERT(shared_->sigs.length() == MaxSigs);
MOZ_ASSERT(shared_->tables.length() == MaxTables);
@ -590,11 +597,11 @@ ModuleGenerator::allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* g
}
bool
ModuleGenerator::allocateGlobal(ValType type, bool isConst, uint32_t* index)
ModuleGenerator::allocateGlobal(GlobalDesc* global)
{
MOZ_ASSERT(!startedFuncDefs_);
unsigned width = 0;
switch (type) {
switch (global->type()) {
case ValType::I32:
case ValType::F32:
width = 4;
@ -621,8 +628,22 @@ ModuleGenerator::allocateGlobal(ValType type, bool isConst, uint32_t* index)
if (!allocateGlobalBytes(width, width, &offset))
return false;
global->setOffset(offset);
return true;
}
bool
ModuleGenerator::addGlobal(ValType type, bool isConst, uint32_t* index)
{
MOZ_ASSERT(isAsmJS());
MOZ_ASSERT(!startedFuncDefs_);
*index = shared_->globals.length();
return shared_->globals.append(GlobalDesc(type, offset, isConst));
GlobalDesc global(type, !isConst, *index);
if (!allocateGlobal(&global))
return false;
return shared_->globals.append(global);
}
void
@ -713,7 +734,7 @@ ModuleGenerator::funcImport(uint32_t funcImportIndex) const
bool
ModuleGenerator::addFuncExport(UniqueChars fieldName, uint32_t funcIndex)
{
return exports_.emplaceBack(Move(fieldName), funcIndex) &&
return exports_.emplaceBack(Move(fieldName), funcIndex, DefinitionKind::Function) &&
exportedFuncs_.put(funcIndex);
}
@ -731,6 +752,12 @@ ModuleGenerator::addMemoryExport(UniqueChars fieldName)
return exports_.emplaceBack(Move(fieldName), DefinitionKind::Memory);
}
bool
ModuleGenerator::addGlobalExport(UniqueChars fieldName, uint32_t globalIndex)
{
return exports_.emplaceBack(Move(fieldName), globalIndex, DefinitionKind::Global);
}
bool
ModuleGenerator::setStartFunction(uint32_t funcIndex)
{
@ -970,6 +997,7 @@ ModuleGenerator::finish(const ShareableBytes& bytecode)
metadata_->minMemoryLength = shared_->minMemoryLength;
metadata_->maxMemoryLength = shared_->maxMemoryLength;
metadata_->tables = Move(shared_->tables);
metadata_->globals = Move(shared_->globals);
// These Vectors can get large and the excess capacity can be significant,
// so realloc them down to size.

View File

@ -136,6 +136,7 @@ class MOZ_STACK_CLASS ModuleGenerator
MOZ_MUST_USE bool finishLinkData(Bytes& code);
MOZ_MUST_USE bool addFuncImport(const Sig& sig, uint32_t globalDataOffset);
MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOff);
MOZ_MUST_USE bool allocateGlobal(GlobalDesc* global);
public:
explicit ModuleGenerator(ImportVector&& imports);
@ -165,8 +166,7 @@ class MOZ_STACK_CLASS ModuleGenerator
const SigWithId& funcSig(uint32_t funcIndex) const;
// Globals:
MOZ_MUST_USE bool allocateGlobal(ValType type, bool isConst, uint32_t* index);
const GlobalDesc& global(unsigned index) const { return shared_->globals[index]; }
const GlobalDescVector& globals() const { return shared_->globals; }
// Imports:
uint32_t numFuncImports() const;
@ -176,6 +176,7 @@ class MOZ_STACK_CLASS ModuleGenerator
MOZ_MUST_USE bool addFuncExport(UniqueChars fieldName, uint32_t funcIndex);
MOZ_MUST_USE bool addTableExport(UniqueChars fieldName);
MOZ_MUST_USE bool addMemoryExport(UniqueChars fieldName);
MOZ_MUST_USE bool addGlobalExport(UniqueChars fieldName, uint32_t globalIndex);
// Function definitions:
MOZ_MUST_USE bool startFuncDefs();
@ -201,6 +202,7 @@ class MOZ_STACK_CLASS ModuleGenerator
MOZ_MUST_USE bool initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
void initMemoryUsage(MemoryUsage memoryUsage);
void bumpMinMemoryLength(uint32_t newMinMemoryLength);
MOZ_MUST_USE bool addGlobal(ValType type, bool isConst, uint32_t* index);
// Finish compilation, provided the list of imports and source bytecode.
// Both these Vectors may be empty (viz., b/c asm.js does different things

View File

@ -203,51 +203,6 @@ Instance::toggleProfiling(JSContext* cx)
return true;
}
static bool
ReadI64Object(JSContext* cx, HandleValue v, int64_t* i64)
{
if (!v.isObject()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL,
"i64 JS value must be an object");
return false;
}
RootedObject obj(cx, &v.toObject());
int32_t* i32 = (int32_t*)i64;
RootedValue val(cx);
if (!JS_GetProperty(cx, obj, "low", &val))
return false;
if (!ToInt32(cx, val, &i32[0]))
return false;
if (!JS_GetProperty(cx, obj, "high", &val))
return false;
if (!ToInt32(cx, val, &i32[1]))
return false;
return true;
}
static JSObject*
CreateI64Object(JSContext* cx, int64_t i64)
{
RootedObject result(cx, JS_NewPlainObject(cx));
if (!result)
return nullptr;
RootedValue val(cx, Int32Value(uint32_t(i64)));
if (!JS_DefineProperty(cx, result, "low", val, JSPROP_ENUMERATE))
return nullptr;
val = Int32Value(uint32_t(i64 >> 32));
if (!JS_DefineProperty(cx, result, "high", val, JSPROP_ENUMERATE))
return nullptr;
return result;
}
bool
Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, const uint64_t* argv,
MutableHandleValue rval)
@ -423,7 +378,8 @@ Instance::Instance(JSContext* cx,
const ShareableBytes* maybeBytecode,
HandleWasmMemoryObject memory,
SharedTableVector&& tables,
Handle<FunctionVector> funcImports)
Handle<FunctionVector> funcImports,
const ValVector& globalImports)
: codeSegment_(Move(codeSegment)),
metadata_(&metadata),
maybeBytecode_(maybeBytecode),
@ -442,6 +398,40 @@ Instance::Instance(JSContext* cx,
exit.baselineScript = nullptr;
}
uint8_t* globalData = codeSegment_->globalData();
for (size_t i = 0; i < metadata.globals.length(); i++) {
const GlobalDesc& global = metadata.globals[i];
if (global.isConstant())
continue;
uint8_t* globalAddr = globalData + global.offset();
switch (global.kind()) {
case GlobalKind::Import: {
globalImports[global.importIndex()].writePayload(globalAddr);
break;
}
case GlobalKind::Variable: {
const InitExpr& init = global.initExpr();
switch (init.kind()) {
case InitExpr::Kind::Constant: {
init.val().writePayload(globalAddr);
break;
}
case InitExpr::Kind::GetGlobal: {
const GlobalDesc& imported = metadata.globals[init.globalIndex()];
globalImports[imported.importIndex()].writePayload(globalAddr);
break;
}
}
break;
}
case GlobalKind::Constant: {
MOZ_CRASH("skipped at the top");
}
}
}
if (memory)
*addressOfMemoryBase() = memory->buffer().dataPointerEither().unwrap();

View File

@ -89,7 +89,8 @@ class Instance
const ShareableBytes* maybeBytecode,
HandleWasmMemoryObject memory,
SharedTableVector&& tables,
Handle<FunctionVector> funcImports);
Handle<FunctionVector> funcImports,
const ValVector& globalImports);
~Instance();
bool init(JSContext* cx);
void trace(JSTracer* trc);

View File

@ -1848,8 +1848,46 @@ EmitGetGlobal(FunctionCompiler& f)
return false;
const GlobalDesc& global = f.mg().globals[id];
f.iter().setResult(f.loadGlobalVar(global.globalDataOffset, global.isConst,
ToMIRType(global.type)));
if (!global.isConstant()) {
f.iter().setResult(f.loadGlobalVar(global.offset(), !global.isMutable(),
ToMIRType(global.type())));
return true;
}
Val value = global.constantValue();
MIRType mirType = ToMIRType(value.type());
MDefinition* result;
switch (value.type()) {
case ValType::I32:
result = f.constant(Int32Value(value.i32()), mirType);
break;
case ValType::I64:
result = f.constant(value.i64());
break;
case ValType::F32:
result = f.constant(Float32Value(value.f32()), mirType);
break;
case ValType::F64:
result = f.constant(DoubleValue(value.f64()), mirType);
break;
case ValType::I8x16:
result = f.constant(SimdConstant::CreateX16(value.i8x16()), mirType);
break;
case ValType::I16x8:
result = f.constant(SimdConstant::CreateX8(value.i16x8()), mirType);
break;
case ValType::I32x4:
result = f.constant(SimdConstant::CreateX4(value.i32x4()), mirType);
break;
case ValType::F32x4:
result = f.constant(SimdConstant::CreateX4(value.f32x4()), mirType);
break;
default:
MOZ_CRASH("unexpected type in EmitGetGlobal");
}
f.iter().setResult(result);
return true;
}
@ -1862,7 +1900,9 @@ EmitSetGlobal(FunctionCompiler& f)
return false;
const GlobalDesc& global = f.mg().globals[id];
f.storeGlobalVar(global.globalDataOffset, value);
MOZ_ASSERT(global.isMutable());
f.storeGlobalVar(global.offset(), value);
return true;
}

View File

@ -22,11 +22,14 @@
#include "asmjs/WasmInstance.h"
#include "asmjs/WasmModule.h"
#include "jit/JitOptions.h"
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
bool
@ -89,15 +92,21 @@ GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleVal
static bool
GetImports(JSContext* cx,
const Module& module,
HandleObject importObj,
const ImportVector& imports,
MutableHandle<FunctionVector> funcImports,
MutableHandleWasmTableObject tableImport,
MutableHandleWasmMemoryObject memoryImport)
MutableHandleWasmMemoryObject memoryImport,
ValVector* globalImports)
{
const ImportVector& imports = module.imports();
if (!imports.empty() && !importObj)
return Throw(cx, "no import object given");
const Metadata& metadata = module.metadata();
uint32_t globalIndex = 0;
const GlobalDescVector& globals = metadata.globals;
for (const Import& import : imports) {
RootedValue v(cx);
if (!GetProperty(cx, importObj, import.module.get(), &v))
@ -135,9 +144,53 @@ GetImports(JSContext* cx,
MOZ_ASSERT(!memoryImport);
memoryImport.set(&v.toObject().as<WasmMemoryObject>());
break;
case DefinitionKind::Global:
Val val;
const GlobalDesc& global = globals[globalIndex++];
MOZ_ASSERT(global.importIndex() == globalIndex - 1);
MOZ_ASSERT(!global.isMutable());
switch (global.type()) {
case ValType::I32: {
int32_t i32;
if (!ToInt32(cx, v, &i32))
return false;
val = Val(uint32_t(i32));
break;
}
case ValType::I64: {
MOZ_ASSERT(JitOptions.wasmTestMode, "no int64 in JS");
int64_t i64;
if (!ReadI64Object(cx, v, &i64))
return false;
val = Val(uint64_t(i64));
break;
}
case ValType::F32: {
double d;
if (!ToNumber(cx, v, &d))
return false;
val = Val(float(d));
break;
}
case ValType::F64: {
double d;
if (!ToNumber(cx, v, &d))
return false;
val = Val(d);
break;
}
default: {
MOZ_CRASH("unexpected import value type");
}
}
if (!globalImports->append(val))
return false;
}
}
MOZ_ASSERT(globalIndex == globals.length() || !globals[globalIndex].isImport());
return true;
}
@ -184,10 +237,11 @@ wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
RootedWasmTableObject table(cx);
RootedWasmMemoryObject memory(cx);
if (!GetImports(cx, importObj, module->imports(), &funcs, &table, &memory))
ValVector globals;
if (!GetImports(cx, *module, importObj, &funcs, &table, &memory, &globals))
return false;
return module->instantiate(cx, funcs, table, memory, nullptr, instanceObj);
return module->instantiate(cx, funcs, table, memory, globals, nullptr, instanceObj);
}
static bool
@ -511,12 +565,13 @@ WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp)
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
RootedWasmTableObject table(cx);
RootedWasmMemoryObject memory(cx);
if (!GetImports(cx, importObj, module.imports(), &funcs, &table, &memory))
ValVector globals;
if (!GetImports(cx, module, importObj, &funcs, &table, &memory, &globals))
return false;
RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
RootedWasmInstanceObject instanceObj(cx);
if (!module.instantiate(cx, funcs, table, memory, instanceProto, &instanceObj))
if (!module.instantiate(cx, funcs, table, memory, globals, instanceProto, &instanceObj))
return false;
args.rval().setObject(*instanceObj);

View File

@ -21,15 +21,62 @@
#include "asmjs/WasmInstance.h"
#include "asmjs/WasmJS.h"
#include "asmjs/WasmSerialize.h"
#include "jit/JitOptions.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/Debugger-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
const char wasm::InstanceExportField[] = "exports";
JSObject*
js::wasm::CreateI64Object(JSContext* cx, int64_t i64)
{
RootedObject result(cx, JS_NewPlainObject(cx));
if (!result)
return nullptr;
RootedValue val(cx, Int32Value(uint32_t(i64)));
if (!JS_DefineProperty(cx, result, "low", val, JSPROP_ENUMERATE))
return nullptr;
val = Int32Value(uint32_t(i64 >> 32));
if (!JS_DefineProperty(cx, result, "high", val, JSPROP_ENUMERATE))
return nullptr;
return result;
}
bool
js::wasm::ReadI64Object(JSContext* cx, HandleValue v, int64_t* i64)
{
if (!v.isObject()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL,
"i64 JS value must be an object");
return false;
}
RootedObject obj(cx, &v.toObject());
int32_t* i32 = (int32_t*)i64;
RootedValue val(cx);
if (!JS_GetProperty(cx, obj, "low", &val))
return false;
if (!ToInt32(cx, val, &i32[0]))
return false;
if (!JS_GetProperty(cx, obj, "high", &val))
return false;
if (!ToInt32(cx, val, &i32[1]))
return false;
return true;
}
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// On MIPS, CodeLabels are instruction immediates so InternalLinks only
// patch instruction immediates.
@ -158,25 +205,32 @@ Import::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
func.sizeOfExcludingThis(mallocSizeOf);
}
Export::Export(UniqueChars fieldName, uint32_t funcIndex)
Export::Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind)
: fieldName_(Move(fieldName))
{
pod.kind_ = DefinitionKind::Function;
pod.funcIndex_ = funcIndex;
pod.kind_ = kind;
pod.index_ = index;
}
Export::Export(UniqueChars fieldName, DefinitionKind kind)
: fieldName_(Move(fieldName))
{
pod.kind_ = kind;
pod.funcIndex_ = 0;
pod.index_ = 0;
}
uint32_t
Export::funcIndex() const
{
MOZ_ASSERT(pod.kind_ == DefinitionKind::Function);
return pod.funcIndex_;
return pod.index_;
}
uint32_t
Export::globalIndex() const
{
MOZ_ASSERT(pod.kind_ == DefinitionKind::Global);
return pod.index_;
}
size_t
@ -468,11 +522,54 @@ Module::instantiateTable(JSContext* cx, HandleWasmTableObject tableImport,
return true;
}
static bool
ExportGlobalValue(JSContext* cx, const GlobalDescVector& globals, uint32_t globalIndex,
const ValVector& globalImports, MutableHandleValue jsval)
{
const GlobalDesc& global = globals[globalIndex];
// Imports are located upfront in the globals array.
Val val;
switch (global.kind()) {
case GlobalKind::Import: val = globalImports[globalIndex]; break;
case GlobalKind::Variable: MOZ_CRASH("mutable variables can't be exported");
case GlobalKind::Constant: val = global.constantValue(); break;
}
switch (global.type()) {
case ValType::I32: {
jsval.set(Int32Value(val.i32()));
return true;
}
case ValType::I64: {
MOZ_ASSERT(JitOptions.wasmTestMode, "no int64 in asm.js/wasm");
RootedObject obj(cx, CreateI64Object(cx, val.i64()));
if (!obj)
return false;
jsval.set(ObjectValue(*obj));
return true;
}
case ValType::F32: {
jsval.set(DoubleValue(double(val.f32())));
return true;
}
case ValType::F64: {
jsval.set(DoubleValue(val.f64()));
return true;
}
default: {
break;
}
}
MOZ_CRASH("unexpected type when creating global exports");
}
static bool
CreateExportObject(JSContext* cx,
HandleWasmInstanceObject instanceObj,
MutableHandleWasmTableObject tableObj,
HandleWasmMemoryObject memoryObj,
const ValVector& globalImports,
const ExportVector& exports,
MutableHandleObject exportObj)
{
@ -523,6 +620,11 @@ CreateExportObject(JSContext* cx,
val = ObjectValue(memoryObj->buffer());
break;
}
case DefinitionKind::Global: {
if (!ExportGlobalValue(cx, metadata.globals, exp.globalIndex(), globalImports, &val))
return false;
break;
}
}
if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE))
@ -537,6 +639,7 @@ Module::instantiate(JSContext* cx,
Handle<FunctionVector> funcImports,
HandleWasmTableObject tableImport,
HandleWasmMemoryObject memoryImport,
const ValVector& globalImports,
HandleObject instanceProto,
MutableHandleWasmInstanceObject instanceObj) const
{
@ -580,7 +683,8 @@ Module::instantiate(JSContext* cx,
maybeBytecode,
memory,
Move(tables),
funcImports);
funcImports,
globalImports);
if (!instance)
return false;
@ -594,7 +698,7 @@ Module::instantiate(JSContext* cx,
RootedObject exportObj(cx);
RootedWasmTableObject table(cx, tableImport);
if (!CreateExportObject(cx, instanceObj, &table, memory, exports_, &exportObj))
if (!CreateExportObject(cx, instanceObj, &table, memory, globalImports, exports_, &exportObj))
return false;
JSAtom* atom = Atomize(cx, InstanceExportField, strlen(InstanceExportField));

View File

@ -26,6 +26,18 @@
namespace js {
namespace wasm {
// Creates a JS object containing two fields (low: low 32 bits; high: high 32
// bits) of a given Int64 value. For testing purposes only.
JSObject*
CreateI64Object(JSContext* cx, int64_t i64);
// Reads an int64 from a JS object with the same shape as described in the
// comment above. For testing purposes only.
bool
ReadI64Object(JSContext* cx, HandleValue v, int64_t* i64);
// LinkData contains all the metadata necessary to patch all the locations
// that depend on the absolute address of a CodeSegment.
//
@ -113,18 +125,19 @@ class Export
CacheableChars fieldName_;
struct CacheablePod {
DefinitionKind kind_;
uint32_t funcIndex_;
uint32_t index_;
} pod;
public:
Export() = default;
explicit Export(UniqueChars fieldName, uint32_t funcIndex);
explicit Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind);
explicit Export(UniqueChars fieldName, DefinitionKind kind);
const char* fieldName() const { return fieldName_.get(); }
DefinitionKind kind() const { return pod.kind_; }
uint32_t funcIndex() const;
uint32_t globalIndex() const;
WASM_DECLARE_SERIALIZABLE(Export)
};
@ -219,6 +232,7 @@ class Module : public RefCounted<Module>
Handle<FunctionVector> funcImports,
HandleWasmTableObject tableImport,
HandleWasmMemoryObject memoryImport,
const ValVector& globalImports,
HandleObject instanceProto,
MutableHandleWasmInstanceObject instanceObj) const;

View File

@ -37,7 +37,7 @@ using namespace js::jit;
using namespace js::wasm;
void
Val::writePayload(uint8_t* dst)
Val::writePayload(uint8_t* dst) const
{
switch (type_) {
case ValType::I32:

View File

@ -361,9 +361,11 @@ class Val
return u.f32x4_;
}
void writePayload(uint8_t* dst);
void writePayload(uint8_t* dst) const;
};
typedef Vector<Val, 0, SystemAllocPolicy> ValVector;
// The Sig class represents a WebAssembly function signature which takes a list
// of value types and returns an expression type. The engine uses two in-memory
// representations of the argument Vector's memory (when elements do not fit
@ -413,6 +415,142 @@ struct SigHashPolicy
static bool match(const Sig* lhs, Lookup rhs) { return *lhs == rhs; }
};
// An InitExpr describes a deferred initializer expression, used to initialize
// a global or a table element offset. Such expressions are created during
// decoding and actually executed on module instantiation.
class InitExpr
{
public:
enum class Kind {
Constant,
GetGlobal
};
private:
Kind kind_;
union {
Val val_;
struct {
uint32_t index_;
ValType type_;
} global;
} u;
public:
InitExpr() = default;
explicit InitExpr(Val val) : kind_(Kind::Constant) {
u.val_ = val;
}
explicit InitExpr(uint32_t globalIndex, ValType type) : kind_(Kind::GetGlobal) {
u.global.index_ = globalIndex;
u.global.type_ = type;
}
Kind kind() const { return kind_; }
bool isVal() const { return kind() == Kind::Constant; }
Val val() const { MOZ_ASSERT(isVal()); return u.val_; }
uint32_t globalIndex() const { MOZ_ASSERT(kind() == Kind::GetGlobal); return u.global.index_; }
ValType type() const {
switch (kind()) {
case Kind::Constant: return u.val_.type();
case Kind::GetGlobal: return u.global.type_;
}
MOZ_CRASH("unexpected initExpr type");
}
};
// A GlobalDesc describes a single global variable. Currently, asm.js and wasm
// exposes mutable and immutable private globals, but can't import nor export
// mutable globals.
enum class GlobalKind
{
Import,
Constant,
Variable
};
class GlobalDesc
{
union {
struct {
union {
InitExpr initial_;
struct {
ValType type_;
uint32_t index_;
} import;
} val;
unsigned offset_;
bool isMutable_;
} var;
Val cst_;
} u;
GlobalKind kind_;
public:
GlobalDesc() = default;
explicit GlobalDesc(InitExpr initial, bool isMutable)
: kind_((isMutable || !initial.isVal()) ? GlobalKind::Variable : GlobalKind::Constant)
{
if (isVariable()) {
u.var.val.initial_ = initial;
u.var.isMutable_ = isMutable;
u.var.offset_ = UINT32_MAX;
} else {
u.cst_ = initial.val();
}
}
explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex)
: kind_(GlobalKind::Import)
{
u.var.val.import.type_ = type;
u.var.val.import.index_ = importIndex;
u.var.isMutable_ = isMutable;
u.var.offset_ = UINT32_MAX;
}
void setOffset(unsigned offset) {
MOZ_ASSERT(!isConstant());
MOZ_ASSERT(u.var.offset_ == UINT32_MAX);
u.var.offset_ = offset;
}
unsigned offset() const {
MOZ_ASSERT(!isConstant());
MOZ_ASSERT(u.var.offset_ != UINT32_MAX);
return u.var.offset_;
}
GlobalKind kind() const { return kind_; }
bool isVariable() const { return kind_ == GlobalKind::Variable; }
bool isConstant() const { return kind_ == GlobalKind::Constant; }
bool isImport() const { return kind_ == GlobalKind::Import; }
bool isMutable() const { return !isConstant() && u.var.isMutable_; }
Val constantValue() const { MOZ_ASSERT(isConstant()); return u.cst_; }
const InitExpr& initExpr() const { MOZ_ASSERT(isVariable()); return u.var.val.initial_; }
uint32_t importIndex() const { MOZ_ASSERT(isImport()); return u.var.val.import.index_; }
ValType type() const {
switch (kind_) {
case GlobalKind::Import: return u.var.val.import.type_;
case GlobalKind::Variable: return u.var.val.initial_.type();
case GlobalKind::Constant: return u.cst_.type();
}
MOZ_CRASH("unexpected global kind");
}
};
typedef Vector<GlobalDesc, 0, SystemAllocPolicy> GlobalDescVector;
// SigIdDesc describes a signature id that can be used by call_indirect and
// table-entry prologues to structurally compare whether the caller and callee's
// signatures *structurally* match. To handle the general case, a Sig is
@ -468,21 +606,6 @@ struct SigWithId : Sig
typedef Vector<SigWithId, 0, SystemAllocPolicy> SigWithIdVector;
typedef Vector<const SigWithId*, 0, SystemAllocPolicy> SigWithIdPtrVector;
// A GlobalDesc describes a single global variable. Currently, globals are only
// exposed through asm.js.
struct GlobalDesc
{
ValType type;
unsigned globalDataOffset;
bool isConst;
GlobalDesc(ValType type, unsigned offset, bool isConst)
: type(type), globalDataOffset(offset), isConst(isConst)
{}
};
typedef Vector<GlobalDesc, 0, SystemAllocPolicy> GlobalDescVector;
// The (,Profiling,Func)Offsets classes are used to record the offsets of
// different key points in a CodeRange during compilation.
@ -940,6 +1063,7 @@ static const unsigned InitialGlobalDataBytes = NaN32GlobalDataOffset + sizeo
static const unsigned MaxSigs = 4 * 1024;
static const unsigned MaxFuncs = 512 * 1024;
static const unsigned MaxGlobals = 4 * 1024;
static const unsigned MaxLocals = 64 * 1024;
static const unsigned MaxImports = 64 * 1024;
static const unsigned MaxExports = 64 * 1024;