1321122 - Baldr: add Module.customSections.

1321122 - Baldr: add Module.customSections.

As added here:
  https://github.com/WebAssembly/design/pull/877
This commit is contained in:
win7-7 2025-12-28 19:55:44 +02:00 committed by wuggy
commit 1f6c6d4469
13 changed files with 482 additions and 319 deletions

View file

@ -2053,9 +2053,7 @@ class MOZ_STACK_CLASS ModuleValidator
if (!bytes)
return nullptr;
return mg_.finish(*bytes,
DataSegmentVector(),
NameInBytecodeVector());
return mg_.finish(*bytes);
}
};

View file

@ -30,7 +30,7 @@ static const uint32_t EncodingVersion = 0x01;
static const uint32_t PrevEncodingVersion = 0x0d;
enum class SectionId {
UserDefined = 0,
Custom = 0,
Type = 1,
Import = 2,
Function = 3,

View file

@ -1825,10 +1825,10 @@ AstDecodeEnvironment(AstDecodeContext& c)
}
static bool
AstDecodeCodeSection(AstDecodeContext &c)
AstDecodeCodeSection(AstDecodeContext& c)
{
uint32_t sectionStart, sectionSize;
if (!c.d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
if (!c.d.startSection(SectionId::Code, &c.env(), &sectionStart, &sectionSize, "code"))
return false;
if (sectionStart == Decoder::NotStarted) {
@ -1863,15 +1863,14 @@ AstDecodeCodeSection(AstDecodeContext &c)
static const size_t WRAP_DATA_BYTES = 30;
static bool
AstDecodeDataSection(AstDecodeContext &c)
AstDecodeModuleTail(AstDecodeContext& c)
{
MOZ_ASSERT(c.module().memories().length() <= 1, "at most one memory in MVP");
DataSegmentVector segments;
if (!DecodeDataSection(c.d, c.env(), &segments))
if (!DecodeModuleTail(c.d, &c.env()))
return false;
for (DataSegment& s : segments) {
for (DataSegment& s : c.env().dataSegments) {
char16_t* buffer = static_cast<char16_t*>(c.lifo.alloc(s.length * sizeof(char16_t)));
if (!buffer)
return false;
@ -1913,8 +1912,7 @@ wasm::BinaryToAst(JSContext* cx, const uint8_t* bytes, uint32_t length,
if (!AstDecodeEnvironment(c) ||
!AstDecodeCodeSection(c) ||
!AstDecodeDataSection(c) ||
!DecodeUnknownSections(c.d))
!AstDecodeModuleTail(c))
{
if (error) {
JS_ReportErrorNumberASCII(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,

View file

@ -469,6 +469,7 @@ Metadata::serializedSize() const
SerializedPodVectorSize(callSites) +
SerializedPodVectorSize(callThunks) +
SerializedPodVectorSize(funcNames) +
SerializedPodVectorSize(customSections) +
filename.serializedSize();
}
@ -488,6 +489,7 @@ Metadata::serialize(uint8_t* cursor) const
cursor = SerializePodVector(cursor, callSites);
cursor = SerializePodVector(cursor, callThunks);
cursor = SerializePodVector(cursor, funcNames);
cursor = SerializePodVector(cursor, customSections);
cursor = filename.serialize(cursor);
return cursor;
}
@ -508,6 +510,7 @@ Metadata::deserialize(const uint8_t* cursor)
(cursor = DeserializePodVector(cursor, &callSites)) &&
(cursor = DeserializePodVector(cursor, &callThunks)) &&
(cursor = DeserializePodVector(cursor, &funcNames)) &&
(cursor = DeserializePodVector(cursor, &customSections)) &&
(cursor = filename.deserialize(cursor));
return cursor;
}
@ -527,6 +530,7 @@ Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
callSites.sizeOfExcludingThis(mallocSizeOf) +
callThunks.sizeOfExcludingThis(mallocSizeOf) +
funcNames.sizeOfExcludingThis(mallocSizeOf) +
customSections.sizeOfExcludingThis(mallocSizeOf) +
filename.sizeOfExcludingThis(mallocSizeOf);
}

View file

@ -397,12 +397,32 @@ struct NameInBytecode
uint32_t length;
NameInBytecode() = default;
NameInBytecode(uint32_t offset, uint32_t length) : offset(offset), length(length) {}
NameInBytecode(uint32_t offset, uint32_t length)
: offset(offset), length(length)
{}
};
typedef Vector<NameInBytecode, 0, SystemAllocPolicy> NameInBytecodeVector;
typedef Vector<char16_t, 64> TwoByteName;
// CustomSection represents a custom section in the bytecode which can be
// extracted via Module.customSections. The (offset, length) pair does not
// include the custom section name.
struct CustomSection
{
NameInBytecode name;
uint32_t offset;
uint32_t length;
CustomSection() = default;
CustomSection(NameInBytecode name, uint32_t offset, uint32_t length)
: name(name), offset(offset), length(length)
{}
};
typedef Vector<CustomSection, 0, SystemAllocPolicy> CustomSectionVector;
// Metadata holds all the data that is needed to describe compiled wasm code
// at runtime (as opposed to data that is only used to statically link or
// instantiate a module).
@ -445,6 +465,7 @@ struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
CallSiteVector callSites;
CallThunkVector callThunks;
NameInBytecodeVector funcNames;
CustomSectionVector customSections;
CacheableChars filename;
bool usesMemory() const { return UsesMemory(memoryUsage); }

View file

@ -64,11 +64,11 @@ DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex)
static bool
DecodeCodeSection(Decoder& d, ModuleGenerator& mg)
{
if (!mg.startFuncDefs())
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Code, &mg.mutableEnv(), &sectionStart, &sectionSize, "code"))
return false;
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
if (!mg.startFuncDefs())
return false;
if (sectionStart == Decoder::NotStarted) {
@ -96,71 +96,6 @@ DecodeCodeSection(Decoder& d, ModuleGenerator& mg)
return mg.finishFuncDefs();
}
static void
MaybeDecodeNameSectionBody(Decoder& d, NameInBytecodeVector* pfuncNames)
{
// For simplicity, ignore all failures, even OOM. Failure will simply result
// in the names section not being included for this module.
uint32_t numFuncNames;
if (!d.readVarU32(&numFuncNames))
return;
if (numFuncNames > MaxFuncs)
return;
// Use a local vector (and not pfuncNames) since it could result in a
// partially initialized result in case of failure in the middle.
NameInBytecodeVector funcNames;
if (!funcNames.resize(numFuncNames))
return;
for (uint32_t i = 0; i < numFuncNames; i++) {
uint32_t numBytes;
if (!d.readVarU32(&numBytes))
return;
NameInBytecode name;
name.offset = d.currentOffset();
name.length = numBytes;
funcNames[i] = name;
if (!d.readBytes(numBytes))
return;
// Skip local names for a function.
uint32_t numLocals;
if (!d.readVarU32(&numLocals))
return;
for (uint32_t j = 0; j < numLocals; j++) {
uint32_t numBytes;
if (!d.readVarU32(&numBytes))
return;
if (!d.readBytes(numBytes))
return;
}
}
*pfuncNames = Move(funcNames);
}
static bool
DecodeNameSection(Decoder& d, NameInBytecodeVector* funcNames)
{
uint32_t sectionStart, sectionSize;
if (!d.startUserDefinedSection(NameSectionName, &sectionStart, &sectionSize))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
// Once started, user-defined sections do not report validation errors.
MaybeDecodeNameSectionBody(d, funcNames);
d.finishUserDefinedSection(sectionStart, sectionSize);
return true;
}
bool
CompileArgs::initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller)
{
@ -190,20 +125,10 @@ wasm::Compile(const ShareableBytes& bytecode, const CompileArgs& args, UniqueCha
if (!DecodeCodeSection(d, mg))
return nullptr;
DataSegmentVector dataSegments;
if (!DecodeDataSection(d, mg.env(), &dataSegments))
return nullptr;
NameInBytecodeVector funcNames;
if (!DecodeNameSection(d, &funcNames))
return nullptr;
if (!DecodeUnknownSections(d))
if (!DecodeModuleTail(d, &mg.mutableEnv()))
return nullptr;
MOZ_ASSERT(!*error, "unreported error in decoding");
return mg.finish(bytecode,
Move(dataSegments),
Move(funcNames));
return mg.finish(bytecode);
}

View file

@ -218,6 +218,15 @@ ModuleGenerator::init(UniqueModuleEnvironment env, const CompileArgs& args,
return true;
}
ModuleEnvironment&
ModuleGenerator::mutableEnv()
{
// Mutation is not safe during parallel compilation.
MOZ_ASSERT(!startedFuncDefs_ || finishedFuncDefs_);
return *env_;
}
bool
ModuleGenerator::finishOutstandingTask()
{
@ -1020,8 +1029,7 @@ ModuleGenerator::initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncInd
}
SharedModule
ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& dataSegments,
NameInBytecodeVector&& funcNames)
ModuleGenerator::finish(const ShareableBytes& bytecode)
{
MOZ_ASSERT(!activeFuncDef_);
MOZ_ASSERT(finishedFuncDefs_);
@ -1058,8 +1066,6 @@ ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& data
if (!metadata_->callSites.appendAll(masm_.callSites()))
return nullptr;
metadata_->funcNames = Move(funcNames);
// The MacroAssembler has accumulated all the memory accesses during codegen.
metadata_->memoryAccesses = masm_.extractMemoryAccesses();
metadata_->memoryPatches = masm_.extractMemoryPatches();
@ -1071,6 +1077,8 @@ ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& data
metadata_->maxMemoryLength = env_->maxMemoryLength;
metadata_->tables = Move(env_->tables);
metadata_->globals = Move(env_->globals);
metadata_->funcNames = Move(env_->funcNames);
metadata_->customSections = Move(env_->customSections);
// These Vectors can get large and the excess capacity can be significant,
// so realloc them down to size.
@ -1103,7 +1111,7 @@ ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& data
Move(linkData_),
Move(env_->imports),
Move(env_->exports),
Move(dataSegments),
Move(env_->dataSegments),
Move(env_->elemSegments),
*metadata_,
bytecode));

View file

@ -265,6 +265,7 @@ private:
Metadata* maybeAsmJSMetadata = nullptr);
const ModuleEnvironment& env() const { return *env_; }
ModuleEnvironment& mutableEnv();
bool isAsmJS() const { return metadata_->kind == ModuleKind::AsmJS; }
jit::MacroAssembler& masm() { return masm_; }
@ -303,11 +304,8 @@ private:
[[nodiscard]] bool addGlobal(ValType type, bool isConst, uint32_t* index);
[[nodiscard]] bool addExport(CacheableChars&& fieldChars, uint32_t funcIndex);
// Finish compilation, provided the list of imports and source bytecode.
// Both these Vectors may be empty (viz., b/c asm.js does different things
// for imports and source).
SharedModule finish(const ShareableBytes& bytecode, DataSegmentVector&& dataSegments,
NameInBytecodeVector&& funcNames);
// Finish compilation of the given bytecode.
SharedModule finish(const ShareableBytes& bytecode);
};
// A FunctionGenerator encapsulates the generation of a single function body.

View file

@ -20,6 +20,7 @@
#include "mozilla/CheckedInt.h"
#include "mozilla/Maybe.h"
#include "mozilla/RangedPtr.h"
#include "jsprf.h"
@ -44,6 +45,7 @@ using mozilla::CheckedInt;
using mozilla::IsNaN;
using mozilla::IsSame;
using mozilla::Nothing;
using mozilla::RangedPtr;
bool
wasm::HasCompilerSupport(ExclusiveContext* cx)
@ -503,6 +505,7 @@ const JSFunctionSpec WasmModuleObject::static_methods[] =
{
JS_FN("imports", WasmModuleObject::imports, 1, 0),
JS_FN("exports", WasmModuleObject::exports, 1, 0),
JS_FN("customSections", WasmModuleObject::customSections, 2, 0),
JS_FS_END
};
@ -689,6 +692,58 @@ WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/* static */ bool
WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Module* module;
if (!GetModuleArg(cx, args, "WebAssembly.Module.customSections", &module))
return false;
Vector<char, 8> name(cx);
{
RootedString str(cx, ToString(cx, args.get(1)));
if (!str)
return false;
Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
if (!flat)
return false;
if (!name.initLengthUninitialized(JS::GetDeflatedUTF8StringLength(flat)))
return false;
JS::DeflateStringToUTF8Buffer(flat, RangedPtr<char>(name.begin(), name.length()));
}
const uint8_t* bytecode = module->bytecode().begin();
AutoValueVector elems(cx);
RootedArrayBufferObject buf(cx);
for (const CustomSection& sec : module->metadata().customSections) {
if (name.length() != sec.name.length)
continue;
if (memcmp(name.begin(), bytecode + sec.name.offset, name.length()))
continue;
buf = ArrayBufferObject::create(cx, sec.length);
if (!buf)
return false;
memcpy(buf->dataPointer(), bytecode + sec.offset, sec.length);
if (!elems.append(ObjectValue(*buf)))
return false;
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr)
return false;
args.rval().setObject(*arr);
return true;
}
/* static */ WasmModuleObject*
WasmModuleObject::create(ExclusiveContext* cx, Module& module, HandleObject proto)
{

View file

@ -123,6 +123,7 @@ class WasmModuleObject : public NativeObject
static void finalize(FreeOp* fop, JSObject* obj);
static bool imports(JSContext* cx, unsigned argc, Value* vp);
static bool exports(JSContext* cx, unsigned argc, Value* vp);
static bool customSections(JSContext* cx, unsigned argc, Value* vp);
public:
static const unsigned RESERVED_SLOTS = 1;

View file

@ -136,6 +136,7 @@ class Module : public JS::WasmModule
const Metadata& metadata() const { return *metadata_; }
const ImportVector& imports() const { return imports_; }
const ExportVector& exports() const { return exports_; }
const Bytes& bytecode() const { return bytecode_->bytes; }
// Instantiate this module with the given imports:

View file

@ -57,6 +57,149 @@ Decoder::fail(UniqueChars msg)
return false;
}
bool
Decoder::startSection(SectionId id, ModuleEnvironment* env, uint32_t* sectionStart,
uint32_t* sectionSize, const char* sectionName)
{
// Record state at beginning of section to allow rewinding to this point
// if, after skipping through several custom sections, we don't find the
// section 'id'.
const uint8_t* const initialCur = cur_;
const size_t initialCustomSectionsLength = env->customSections.length();
// Maintain a pointer to the current section that gets updated as custom
// sections are skipped.
const uint8_t* currentSectionStart = cur_;
// Only start a section with 'id', skipping any custom sections before it.
uint32_t idValue;
if (!readVarU32(&idValue))
goto rewind;
while (idValue != uint32_t(id)) {
if (idValue != uint32_t(SectionId::Custom))
goto rewind;
// Rewind to the beginning of the current section since this is what
// skipCustomSection() assumes.
cur_ = currentSectionStart;
if (!skipCustomSection(env))
return false;
// Having successfully skipped a custom section, consider the next
// section.
currentSectionStart = cur_;
if (!readVarU32(&idValue))
goto rewind;
}
// Found it, now start the section.
if (!readVarU32(sectionSize) || bytesRemain() < *sectionSize)
goto fail;
*sectionStart = cur_ - beg_;
return true;
rewind:
cur_ = initialCur;
env->customSections.shrinkTo(initialCustomSectionsLength);
*sectionStart = NotStarted;
return true;
fail:
return fail("failed to start %s section", sectionName);
}
bool
Decoder::finishSection(uint32_t sectionStart, uint32_t sectionSize, const char* sectionName)
{
if (sectionSize != (cur_ - beg_) - sectionStart)
return fail("byte size mismatch in %s section", sectionName);
return true;
}
bool
Decoder::startCustomSection(const char* expected, size_t expectedLength, ModuleEnvironment* env,
uint32_t* sectionStart, uint32_t* sectionSize)
{
// Record state at beginning of section to allow rewinding to this point
// if, after skipping through several custom sections, we don't find the
// section 'id'.
const uint8_t* const initialCur = cur_;
const size_t initialCustomSectionsLength = env->customSections.length();
while (true) {
// Try to start a custom section. If we can't, rewind to the beginning
// since we may have skipped several custom sections already looking for
// 'expected'.
if (!startSection(SectionId::Custom, env, sectionStart, sectionSize, "custom"))
return false;
if (*sectionStart == NotStarted)
goto rewind;
NameInBytecode name;
if (!readVarU32(&name.length) || name.length > bytesRemain())
goto fail;
name.offset = currentOffset();
uint32_t payloadOffset = name.offset + name.length;
uint32_t payloadEnd = *sectionStart + *sectionSize;
if (payloadOffset > payloadEnd)
goto fail;
// Now that we have a valid custom section, record its offsets in the
// metadata which can be queried by the user via Module.customSections.
// Note: after an entry is appended, it may be popped if this loop or
// the loop in startSection needs to rewind.
if (!env->customSections.emplaceBack(name, payloadOffset, payloadEnd - payloadOffset))
return false;
// If this is the expected custom section, we're done.
if (!expected || (expectedLength == name.length && !memcmp(cur_, expected, name.length))) {
cur_ += name.length;
return true;
}
// Otherwise, blindly skip the custom section and keep looking.
finishCustomSection(*sectionStart, *sectionSize);
}
MOZ_CRASH("unreachable");
rewind:
cur_ = initialCur;
env->customSections.shrinkTo(initialCustomSectionsLength);
return true;
fail:
return fail("failed to start custom section");
}
void
Decoder::finishCustomSection(uint32_t sectionStart, uint32_t sectionSize)
{
MOZ_ASSERT(cur_ >= beg_);
MOZ_ASSERT(cur_ <= end_);
cur_ = (beg_ + sectionStart) + sectionSize;
MOZ_ASSERT(cur_ <= end_);
clearError();
}
bool
Decoder::skipCustomSection(ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!startCustomSection(nullptr, 0, env, &sectionStart, &sectionSize))
return false;
if (sectionStart == NotStarted)
return fail("expected custom section");
finishCustomSection(sectionStart, sectionSize);
return true;
}
// Misc helpers.
bool
@ -558,10 +701,10 @@ DecodePreamble(Decoder& d)
}
static bool
DecodeTypeSection(Decoder& d, SigWithIdVector* sigs)
DecodeTypeSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Type, &sectionStart, &sectionSize, "type"))
if (!d.startSection(SectionId::Type, env, &sectionStart, &sectionSize, "type"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -573,7 +716,7 @@ DecodeTypeSection(Decoder& d, SigWithIdVector* sigs)
if (numSigs > MaxSigs)
return d.fail("too many signatures");
if (!sigs->resize(numSigs))
if (!env->sigs.resize(numSigs))
return false;
for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
@ -614,7 +757,7 @@ DecodeTypeSection(Decoder& d, SigWithIdVector* sigs)
result = ToExprType(type);
}
(*sigs)[sigIndex] = Sig(Move(args), result);
env->sigs[sigIndex] = Sig(Move(args), result);
}
if (!d.finishSection(sectionStart, sectionSize, "type"))
@ -839,7 +982,7 @@ static bool
DecodeImportSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Import, &sectionStart, &sectionSize, "import"))
if (!d.startSection(SectionId::Import, env, &sectionStart, &sectionSize, "import"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -870,7 +1013,7 @@ static bool
DecodeFunctionSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Function, &sectionStart, &sectionSize, "function"))
if (!d.startSection(SectionId::Function, env, &sectionStart, &sectionSize, "function"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -901,10 +1044,10 @@ DecodeFunctionSection(Decoder& d, ModuleEnvironment* env)
}
static bool
DecodeTableSection(Decoder& d, TableDescVector* tables)
DecodeTableSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Table, &sectionStart, &sectionSize, "table"))
if (!d.startSection(SectionId::Table, env, &sectionStart, &sectionSize, "table"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -916,7 +1059,7 @@ DecodeTableSection(Decoder& d, TableDescVector* tables)
if (numTables != 1)
return d.fail("the number of tables must be exactly one");
if (!DecodeTableLimits(d, tables))
if (!DecodeTableLimits(d, &env->tables))
return false;
if (!d.finishSection(sectionStart, sectionSize, "table"))
@ -929,7 +1072,7 @@ static bool
DecodeMemorySection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Memory, &sectionStart, &sectionSize, "memory"))
if (!d.startSection(SectionId::Memory, env, &sectionStart, &sectionSize, "memory"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -1014,10 +1157,10 @@ DecodeInitializerExpression(Decoder& d, const GlobalDescVector& globals, ValType
}
static bool
DecodeGlobalSection(Decoder& d, GlobalDescVector* globals)
DecodeGlobalSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Global, &sectionStart, &sectionSize, "global"))
if (!d.startSection(SectionId::Global, env, &sectionStart, &sectionSize, "global"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -1026,12 +1169,12 @@ DecodeGlobalSection(Decoder& d, GlobalDescVector* globals)
if (!d.readVarU32(&numDefs))
return d.fail("expected number of globals");
CheckedInt<uint32_t> numGlobals = globals->length();
CheckedInt<uint32_t> numGlobals = env->globals.length();
numGlobals += numDefs;
if (!numGlobals.isValid() || numGlobals.value() > MaxGlobals)
return d.fail("too many globals");
if (!globals->reserve(numGlobals.value()))
if (!env->globals.reserve(numGlobals.value()))
return false;
for (uint32_t i = 0; i < numDefs; i++) {
@ -1041,10 +1184,10 @@ DecodeGlobalSection(Decoder& d, GlobalDescVector* globals)
return false;
InitExpr initializer;
if (!DecodeInitializerExpression(d, *globals, type, &initializer))
if (!DecodeInitializerExpression(d, env->globals, type, &initializer))
return false;
globals->infallibleAppend(GlobalDesc(initializer, isMutable));
env->globals.infallibleAppend(GlobalDesc(initializer, isMutable));
}
if (!d.finishSection(sectionStart, sectionSize, "global"))
@ -1146,7 +1289,7 @@ static bool
DecodeExportSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Export, &sectionStart, &sectionSize, "export"))
if (!d.startSection(SectionId::Export, env, &sectionStart, &sectionSize, "export"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -1177,7 +1320,7 @@ static bool
DecodeStartSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Start, &sectionStart, &sectionSize, "start"))
if (!d.startSection(SectionId::Start, env, &sectionStart, &sectionSize, "start"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -1208,7 +1351,7 @@ static bool
DecodeElemSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Elem, &sectionStart, &sectionSize, "elem"))
if (!d.startSection(SectionId::Elem, env, &sectionStart, &sectionSize, "elem"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
@ -1266,7 +1409,7 @@ wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env)
if (!DecodePreamble(d))
return false;
if (!DecodeTypeSection(d, &env->sigs))
if (!DecodeTypeSection(d, env))
return false;
if (!DecodeImportSection(d, env))
@ -1275,13 +1418,13 @@ wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env)
if (!DecodeFunctionSection(d, env))
return false;
if (!DecodeTableSection(d, &env->tables))
if (!DecodeTableSection(d, env))
return false;
if (!DecodeMemorySection(d, env))
return false;
if (!DecodeGlobalSection(d, &env->globals))
if (!DecodeGlobalSection(d, env))
return false;
if (!DecodeExportSection(d, env))
@ -1318,14 +1461,14 @@ DecodeFunctionBody(Decoder& d, const ModuleEnvironment& env, uint32_t funcIndex)
}
static bool
DecodeCodeSection(Decoder& d, const ModuleEnvironment& env)
DecodeCodeSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
if (!d.startSection(SectionId::Code, env, &sectionStart, &sectionSize, "code"))
return false;
if (sectionStart == Decoder::NotStarted) {
if (env.numFuncDefs() != 0)
if (env->numFuncDefs() != 0)
return d.fail("expected function bodies");
return true;
}
@ -1334,11 +1477,11 @@ DecodeCodeSection(Decoder& d, const ModuleEnvironment& env)
if (!d.readVarU32(&numFuncDefs))
return d.fail("expected function body count");
if (numFuncDefs != env.numFuncDefs())
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, env, env.numFuncImports() + funcDefIndex))
if (!DecodeFunctionBody(d, *env, env->numFuncImports() + funcDefIndex))
return false;
}
@ -1349,16 +1492,16 @@ DecodeCodeSection(Decoder& d, const ModuleEnvironment& env)
}
bool
wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments)
static bool
DecodeDataSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Data, &sectionStart, &sectionSize, "data"))
if (!d.startSection(SectionId::Data, env, &sectionStart, &sectionSize, "data"))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
if (!env.usesMemory())
if (!env->usesMemory())
return d.fail("data section requires a memory section");
uint32_t numSegments;
@ -1377,7 +1520,7 @@ wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVec
return d.fail("linear memory index must currently be 0");
DataSegment seg;
if (!DecodeInitializerExpression(d, env.globals, ValType::I32, &seg.offset))
if (!DecodeInitializerExpression(d, env->globals, ValType::I32, &seg.offset))
return false;
if (!d.readVarU32(&seg.length))
@ -1388,7 +1531,7 @@ wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVec
if (!d.readBytes(seg.length))
return d.fail("data segment shorter than declared");
if (!segments->append(seg))
if (!env->dataSegments.append(seg))
return false;
}
@ -1398,11 +1541,82 @@ wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVec
return true;
}
bool
wasm::DecodeUnknownSections(Decoder& d)
static void
MaybeDecodeNameSectionBody(Decoder& d, ModuleEnvironment* env)
{
// For simplicity, ignore all failures, even OOM. Failure will simply result
// in the names section not being included for this module.
uint32_t numFuncNames;
if (!d.readVarU32(&numFuncNames))
return;
if (numFuncNames > MaxFuncs)
return;
// Use a local vector (and not env->funcNames) since it could result in a
// partially initialized result in case of failure in the middle.
NameInBytecodeVector funcNames;
if (!funcNames.resize(numFuncNames))
return;
for (uint32_t i = 0; i < numFuncNames; i++) {
uint32_t numBytes;
if (!d.readVarU32(&numBytes))
return;
NameInBytecode name;
name.offset = d.currentOffset();
name.length = numBytes;
funcNames[i] = name;
if (!d.readBytes(numBytes))
return;
// Skip local names for a function.
uint32_t numLocals;
if (!d.readVarU32(&numLocals))
return;
for (uint32_t j = 0; j < numLocals; j++) {
uint32_t numBytes;
if (!d.readVarU32(&numBytes))
return;
if (!d.readBytes(numBytes))
return;
}
}
env->funcNames = Move(funcNames);
}
static bool
DecodeNameSection(Decoder& d, ModuleEnvironment* env)
{
uint32_t sectionStart, sectionSize;
if (!d.startCustomSection(NameSectionName, env, &sectionStart, &sectionSize))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
// Once started, custom sections do not report validation errors.
MaybeDecodeNameSectionBody(d, env);
d.finishCustomSection(sectionStart, sectionSize);
return true;
}
bool
wasm::DecodeModuleTail(Decoder& d, ModuleEnvironment* env)
{
if (!DecodeDataSection(d, env))
return false;
if (!DecodeNameSection(d, env))
return false;
while (!d.done()) {
if (!d.skipUserDefinedSection())
if (!d.skipCustomSection(env))
return false;
}
@ -1421,15 +1635,13 @@ wasm::Validate(const ShareableBytes& bytecode, UniqueChars* error)
if (!DecodeModuleEnvironment(d, &env))
return false;
if (!DecodeCodeSection(d, env))
if (!DecodeCodeSection(d, &env))
return false;
DataSegmentVector dataSegments;
if (!DecodeDataSection(d, env, &dataSegments))
if (!DecodeModuleTail(d, &env))
return false;
if (!DecodeUnknownSections(d))
return false;
MOZ_ASSERT(!*error, "unreported error in decoding");
return true;
}

View file

@ -25,6 +25,83 @@
namespace js {
namespace wasm {
// ModuleEnvironment contains all the state necessary to validate, process or
// render functions. It is created by decoding all the sections before the wasm
// code section and then used immutably during. When compiling a module using a
// ModuleGenerator, the ModuleEnvironment holds state shared between the
// ModuleGenerator thread and background compile threads. All the threads
// are given a read-only view of the ModuleEnvironment, thus preventing race
// conditions.
struct ModuleEnvironment
{
ModuleKind kind;
MemoryUsage memoryUsage;
mozilla::Atomic<uint32_t> minMemoryLength;
Maybe<uint32_t> maxMemoryLength;
SigWithIdVector sigs;
SigWithIdPtrVector funcSigs;
Uint32Vector funcImportGlobalDataOffsets;
GlobalDescVector globals;
TableDescVector tables;
Uint32Vector asmJSSigToTableIndex;
ImportVector imports;
ExportVector exports;
Maybe<uint32_t> startFuncIndex;
ElemSegmentVector elemSegments;
DataSegmentVector dataSegments;
NameInBytecodeVector funcNames;
CustomSectionVector customSections;
explicit ModuleEnvironment(ModuleKind kind = ModuleKind::Wasm)
: kind(kind),
memoryUsage(MemoryUsage::None),
minMemoryLength(0)
{}
size_t numTables() const {
return tables.length();
}
size_t numSigs() const {
return sigs.length();
}
size_t numFuncs() const {
// asm.js pre-reserves a bunch of function index space which is
// incrementally filled in during function-body validation. Thus, there
// are a few possible interpretations of numFuncs() (total index space
// size vs. exact number of imports/definitions encountered so far) and
// to simplify things we simply only define this quantity for wasm.
MOZ_ASSERT(!isAsmJS());
return funcSigs.length();
}
size_t numFuncDefs() const {
// asm.js overallocates the length of funcSigs and in general does not
// know the number of function definitions until it's done compiling.
MOZ_ASSERT(!isAsmJS());
return funcSigs.length() - funcImportGlobalDataOffsets.length();
}
size_t numFuncImports() const {
MOZ_ASSERT(!isAsmJS());
return funcImportGlobalDataOffsets.length();
}
bool usesMemory() const {
return UsesMemory(memoryUsage);
}
bool isAsmJS() const {
return kind == ModuleKind::AsmJS;
}
bool funcIsImport(uint32_t funcIndex) const {
return funcIndex < funcImportGlobalDataOffsets.length();
}
uint32_t funcIndexToSigIndex(uint32_t funcIndex) const {
return funcSigs[funcIndex] - sigs.begin();
}
};
typedef UniquePtr<ModuleEnvironment> UniqueModuleEnvironment;
// The Encoder class appends bytes to the Bytes object it is given during
// construction. The client is responsible for the Bytes's lifetime and must
// keep the Bytes alive as long as the Encoder is used.
@ -196,8 +273,7 @@ class Encoder
// end while the size's varU32 must be stored at the beginning. Immediately
// after the section length is the string id of the section.
MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) {
MOZ_ASSERT(id != SectionId::UserDefined); // not supported yet
[[nodiscard]] bool startSection(SectionId id, size_t* offset) {
return writeVarU32(uint32_t(id)) &&
writePatchableVarU32(offset);
@ -418,104 +494,34 @@ class Decoder
static const uint32_t NotStarted = UINT32_MAX;
MOZ_MUST_USE bool startSection(SectionId id,
uint32_t* startOffset,
uint32_t* size,
const char* sectionName)
{
const uint8_t* const before = cur_;
const uint8_t* beforeId = before;
uint32_t idValue;
if (!readVarU32(&idValue))
goto backup;
while (idValue != uint32_t(id)) {
if (idValue != uint32_t(SectionId::UserDefined))
goto backup;
// Rewind to the section id since skipUserDefinedSection expects it.
cur_ = beforeId;
if (!skipUserDefinedSection())
return false;
beforeId = cur_;
if (!readVarU32(&idValue))
goto backup;
}
if (!readVarU32(size))
goto fail;
if (bytesRemain() < *size)
goto fail;
*startOffset = cur_ - beg_;
return true;
backup:
cur_ = before;
*startOffset = NotStarted;
return true;
fail:
return fail("failed to start %s section", sectionName);
}
MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size,
const char* sectionName)
{
if (size != (cur_ - beg_) - startOffset)
return fail("byte size mismatch in %s section", sectionName);
return true;
}
[[nodiscard]] bool startSection(SectionId id,
ModuleEnvironment* env,
uint32_t* sectionStart,
uint32_t* sectionSize,
const char* sectionName);
[[nodiscard]] bool finishSection(uint32_t sectionStart,
uint32_t sectionSize,
const char* sectionName);
// "User sections" do not cause validation errors unless the error is in
// the user-defined section header itself.
// Custom sections do not cause validation errors unless the error is in
// the section header itself.
MOZ_MUST_USE bool startUserDefinedSection(const char* expectedId,
size_t expectedIdSize,
uint32_t* sectionStart,
uint32_t* sectionSize)
[[nodiscard]] bool startCustomSection(const char* expected,
size_t expectedLength,
ModuleEnvironment* env,
uint32_t* sectionStart,
uint32_t* sectionSize);
template <size_t NameSizeWith0>
[[nodiscard]] bool startCustomSection(const char (&name)[NameSizeWith0],
ModuleEnvironment* env,
uint32_t* sectionStart,
uint32_t* sectionSize)
{
const uint8_t* const before = cur_;
while (true) {
if (!startSection(SectionId::UserDefined, sectionStart, sectionSize, "user-defined"))
return false;
if (*sectionStart == NotStarted) {
cur_ = before;
return true;
}
uint32_t idSize;
if (!readVarU32(&idSize))
goto fail;
if (idSize > bytesRemain() || currentOffset() + idSize > *sectionStart + *sectionSize)
goto fail;
if (expectedId && (expectedIdSize != idSize || !!memcmp(cur_, expectedId, idSize))) {
finishUserDefinedSection(*sectionStart, *sectionSize);
continue;
}
cur_ += idSize;
return true;
}
MOZ_CRASH("unreachable");
fail:
return fail("failed to start user-defined section");
}
template <size_t IdSizeWith0>
MOZ_MUST_USE bool startUserDefinedSection(const char (&id)[IdSizeWith0],
uint32_t* sectionStart,
uint32_t* sectionSize)
{
MOZ_ASSERT(id[IdSizeWith0 - 1] == '\0');
return startUserDefinedSection(id, IdSizeWith0 - 1, sectionStart, sectionSize);
}
void finishUserDefinedSection(uint32_t sectionStart, uint32_t sectionSize) {
MOZ_ASSERT(cur_ >= beg_);
MOZ_ASSERT(cur_ <= end_);
cur_ = (beg_ + sectionStart) + sectionSize;
MOZ_ASSERT(cur_ <= end_);
clearError();
}
MOZ_MUST_USE bool skipUserDefinedSection() {
uint32_t sectionStart, sectionSize;
if (!startUserDefinedSection(nullptr, 0, &sectionStart, &sectionSize))
return false;
if (sectionStart == NotStarted)
return fail("expected user-defined section");
finishUserDefinedSection(sectionStart, sectionSize);
return true;
MOZ_ASSERT(name[NameSizeWith0 - 1] == '\0');
return startCustomSection(name, NameSizeWith0 - 1, env, sectionStart, sectionSize);
}
void finishCustomSection(uint32_t sectionStart, uint32_t sectionSize);
[[nodiscard]] bool skipCustomSection(ModuleEnvironment* env);
// The infallible "unchecked" decoding functions can be used when we are
// sure that the bytes are well-formed (by construction or due to previous
@ -579,11 +585,8 @@ class Decoder
}
};
// Reusable macro encoding/decoding functions reused by both the two
// encoders (AsmJS/WasmTextToBinary) and all the decoders
// (WasmCompile/WasmIonCompile/WasmBaselineCompile/WasmBinaryToText).
// Misc helpers.
// The local entries are part of function bodies and thus serialized by both
// wasm and asm.js and decoded as part of both validation and compilation.
[[nodiscard]] bool
EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
@ -599,86 +602,25 @@ DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals);
// are given a read-only view of the ModuleEnvironment, thus preventing race
// conditions.
struct ModuleEnvironment
{
ModuleKind kind;
MemoryUsage memoryUsage;
mozilla::Atomic<uint32_t> minMemoryLength;
Maybe<uint32_t> maxMemoryLength;
SigWithIdVector sigs;
SigWithIdPtrVector funcSigs;
Uint32Vector funcImportGlobalDataOffsets;
GlobalDescVector globals;
TableDescVector tables;
Uint32Vector asmJSSigToTableIndex;
ImportVector imports;
ExportVector exports;
Maybe<uint32_t> startFuncIndex;
ElemSegmentVector elemSegments;
explicit ModuleEnvironment(ModuleKind kind = ModuleKind::Wasm)
: kind(kind),
memoryUsage(MemoryUsage::None),
minMemoryLength(0)
{}
size_t numTables() const {
return tables.length();
}
size_t numSigs() const {
return sigs.length();
}
size_t numFuncs() const {
// asm.js pre-reserves a bunch of function index space which is
// incrementally filled in during function-body validation. Thus, there
// are a few possible interpretations of numFuncs() (total index space
// size vs. exact number of imports/definitions encountered so far) and
// to simplify things we simply only define this quantity for wasm.
MOZ_ASSERT(!isAsmJS());
return funcSigs.length();
}
size_t numFuncDefs() const {
// asm.js overallocates the length of funcSigs and in general does not
// know the number of function definitions until it's done compiling.
MOZ_ASSERT(!isAsmJS());
return funcSigs.length() - funcImportGlobalDataOffsets.length();
}
size_t numFuncImports() const {
MOZ_ASSERT(!isAsmJS());
return funcImportGlobalDataOffsets.length();
}
bool usesMemory() const {
return UsesMemory(memoryUsage);
}
bool isAsmJS() const {
return kind == ModuleKind::AsmJS;
}
bool funcIsImport(uint32_t funcIndex) const {
return funcIndex < funcImportGlobalDataOffsets.length();
}
uint32_t funcIndexToSigIndex(uint32_t funcIndex) const {
return funcSigs[funcIndex] - sigs.begin();
}
};
typedef UniquePtr<ModuleEnvironment> UniqueModuleEnvironment;
// Section macros.
// Calling DecodeModuleEnvironment decodes all sections up to the code section
// and performs full validation of all those sections. The client must then
// decode the code section itself, reusing ValidateFunctionBody if necessary,
// and finally call DecodeModuleTail to decode all remaining sections after the
// code section (again, performing full validation).
[[nodiscard]] bool
DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env);
[[nodiscard]] bool
DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments);
MOZ_MUST_USE bool
DecodeUnknownSections(Decoder& d);
ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d);
[[nodiscard]] bool
ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d);
DecodeModuleTail(Decoder& d, ModuleEnvironment* env);
// Validate an entire module, returning true if the module was validated
// successfully. If Validate returns false:
// - if *error is null, the caller should report out-of-memory
// - otherwise, there was a legitimate error described by *error
[[nodiscard]] bool
Validate(const ShareableBytes& bytecode, UniqueChars* error);