Bug 1284155 - Baldr: add 'newFormat' binary encoding flag (r=sunfish)

MozReview-Commit-ID: JvHGfetLQjT
This commit is contained in:
Luke Wagner 2016-07-06 18:40:35 -05:00
Родитель d28873f5c8
Коммит fbeece22fc
13 изменённых файлов: 321 добавлений и 113 удалений

Просмотреть файл

@ -520,23 +520,21 @@ class AstImport : public AstNode
AstRef& sig() { return sig_; }
};
enum class AstExportKind { Func, Memory };
class AstExport : public AstNode
{
AstName name_;
AstExportKind kind_;
DefinitionKind kind_;
AstRef func_;
public:
AstExport(AstName name, AstRef func)
: name_(name), kind_(AstExportKind::Func), func_(func)
: name_(name), kind_(DefinitionKind::Function), func_(func)
{}
explicit AstExport(AstName name)
: name_(name), kind_(AstExportKind::Memory)
: name_(name), kind_(DefinitionKind::Memory)
{}
AstName name() const { return name_; }
AstExportKind kind() const { return kind_; }
DefinitionKind kind() const { return kind_; }
AstRef& func() { return func_; }
};

Просмотреть файл

@ -64,6 +64,12 @@ enum class TypeConstructor
Function = 0x40
};
enum class DefinitionKind
{
Function = 0x00,
Memory = 0x01
};
enum class Expr
{
// Control flow operators

Просмотреть файл

@ -1494,7 +1494,7 @@ PrintExport(WasmPrintContext& c, AstExport& export_, const AstModule::FuncVector
return false;
if (!c.buffer.append("export "))
return false;
if (export_.kind() == AstExportKind::Memory) {
if (export_.kind() == DefinitionKind::Memory) {
if (!c.buffer.append("memory"))
return false;
} else {

Просмотреть файл

@ -1171,7 +1171,7 @@ RenderExport(WasmRenderContext& c, AstExport& export_, const AstModule::FuncVect
return false;
if (!c.buffer.append("\" "))
return false;
if (export_.kind() == AstExportKind::Memory) {
if (export_.kind() == DefinitionKind::Memory) {
if (!c.buffer.append("memory"))
return false;
} else {

Просмотреть файл

@ -668,17 +668,32 @@ MaybeDecodeName(Decoder& d)
}
static bool
DecodeImport(Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
DecodeImport(Decoder& d, bool newFormat, ModuleGeneratorData* init, ImportNameVector* importNames)
{
const DeclaredSig* sig = nullptr;
if (!DecodeSignatureIndex(d, *init, &sig))
return false;
if (!newFormat) {
const DeclaredSig* sig = nullptr;
if (!DecodeSignatureIndex(d, *init, &sig))
return false;
if (!init->imports.emplaceBack(sig))
return false;
if (!CheckTypeForJS(d, *sig))
return false;
if (!CheckTypeForJS(d, *sig))
return false;
if (!init->imports.emplaceBack(sig))
return false;
UniqueChars moduleName = MaybeDecodeName(d);
if (!moduleName)
return Fail(d, "expected valid import module name");
if (!strlen(moduleName.get()))
return Fail(d, "module name cannot be empty");
UniqueChars funcName = MaybeDecodeName(d);
if (!funcName)
return Fail(d, "expected valid import func name");
return importNames->emplaceBack(Move(moduleName), Move(funcName));
}
UniqueChars moduleName = MaybeDecodeName(d);
if (!moduleName)
@ -691,11 +706,33 @@ DecodeImport(Decoder& d, ModuleGeneratorData* init, ImportNameVector* importName
if (!funcName)
return Fail(d, "expected valid import func name");
return importNames->emplaceBack(Move(moduleName), Move(funcName));
if (!importNames->emplaceBack(Move(moduleName), Move(funcName)))
return false;
uint32_t importKind;
if (!d.readVarU32(&importKind))
return Fail(d, "failed to read import kind");
switch (DefinitionKind(importKind)) {
case DefinitionKind::Function: {
const DeclaredSig* sig = nullptr;
if (!DecodeSignatureIndex(d, *init, &sig))
return false;
if (!CheckTypeForJS(d, *sig))
return false;
if (!init->imports.emplaceBack(sig))
return false;
break;
}
default:
return Fail(d, "unsupported import kind");
}
return true;
}
static bool
DecodeImportSection(Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
DecodeImportSection(Decoder& d, bool newFormat, ModuleGeneratorData* init, ImportNameVector* importNames)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(ImportSectionId, &sectionStart, &sectionSize))
@ -711,7 +748,7 @@ DecodeImportSection(Decoder& d, ModuleGeneratorData* init, ImportNameVector* imp
return Fail(d, "too many imports");
for (uint32_t i = 0; i < numImports; i++) {
if (!DecodeImport(d, init, importNames))
if (!DecodeImport(d, newFormat, init, importNames))
return false;
}
@ -722,7 +759,7 @@ DecodeImportSection(Decoder& d, ModuleGeneratorData* init, ImportNameVector* imp
}
static bool
DecodeMemorySection(Decoder& d, ModuleGenerator& mg)
DecodeMemorySection(Decoder& d, bool newFormat, ModuleGenerator& mg)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(MemorySectionId, &sectionStart, &sectionSize))
@ -755,14 +792,16 @@ DecodeMemorySection(Decoder& d, ModuleGenerator& mg)
if (maxSize.value() < initialSize.value())
return Fail(d, "maximum memory size less than initial memory size");
uint8_t exported;
if (!d.readFixedU8(&exported))
return Fail(d, "expected exported byte");
if (!newFormat) {
uint8_t exported;
if (!d.readFixedU8(&exported))
return Fail(d, "expected exported byte");
if (exported) {
UniqueChars fieldName = DuplicateString("memory");
if (!fieldName || !mg.addMemoryExport(Move(fieldName)))
return false;
if (exported) {
UniqueChars fieldName = DuplicateString("memory");
if (!fieldName || !mg.addMemoryExport(Move(fieldName)))
return false;
}
}
if (!d.finishSection(sectionStart, sectionSize))
@ -796,27 +835,67 @@ DecodeExportName(Decoder& d, CStringSet* dupSet)
}
static bool
DecodeFunctionExport(Decoder& d, ModuleGenerator& mg, CStringSet* dupSet)
DecodeExport(Decoder& d, bool newFormat, ModuleGenerator& mg, CStringSet* dupSet)
{
uint32_t funcIndex;
if (!d.readVarU32(&funcIndex))
return Fail(d, "expected export internal index");
if (!newFormat) {
uint32_t funcIndex;
if (!d.readVarU32(&funcIndex))
return Fail(d, "expected export internal index");
if (funcIndex >= mg.numFuncSigs())
return Fail(d, "export function index out of range");
if (funcIndex >= mg.numFuncSigs())
return Fail(d, "export function index out of range");
if (!CheckTypeForJS(d, mg.funcSig(funcIndex)))
return false;
if (!CheckTypeForJS(d, mg.funcSig(funcIndex)))
return false;
UniqueChars fieldName = DecodeExportName(d, dupSet);
if (!fieldName)
return false;
return mg.declareExport(Move(fieldName), funcIndex);
}
UniqueChars fieldName = DecodeExportName(d, dupSet);
if (!fieldName)
return false;
return mg.declareExport(Move(fieldName), funcIndex);
uint32_t exportKind;
if (!d.readVarU32(&exportKind))
return Fail(d, "failed to read export kind");
switch (DefinitionKind(exportKind)) {
case DefinitionKind::Function: {
uint32_t funcIndex;
if (!d.readVarU32(&funcIndex))
return Fail(d, "expected export internal index");
if (funcIndex >= mg.numFuncSigs())
return Fail(d, "export function index out of range");
if (!CheckTypeForJS(d, mg.funcSig(funcIndex)))
return false;
return mg.declareExport(Move(fieldName), funcIndex);
}
case DefinitionKind::Memory: {
uint32_t memoryIndex;
if (!d.readVarU32(&memoryIndex))
return Fail(d, "expected memory index");
if (memoryIndex > 0)
return Fail(d, "memory index out of bounds");
return mg.addMemoryExport(Move(fieldName));
}
default:
return Fail(d, "unexpected export kind");
}
MOZ_CRASH("unreachable");
}
static bool
DecodeExportSection(Decoder& d, ModuleGenerator& mg)
DecodeExportSection(Decoder& d, bool newFormat, ModuleGenerator& mg)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(ExportSectionId, &sectionStart, &sectionSize))
@ -836,7 +915,7 @@ DecodeExportSection(Decoder& d, ModuleGenerator& mg)
return Fail(d, "too many exports");
for (uint32_t i = 0; i < numExports; i++) {
if (!DecodeFunctionExport(d, mg, &dupSet))
if (!DecodeExport(d, newFormat, mg, &dupSet))
return false;
}
@ -1074,6 +1153,8 @@ CompileArgs::init(ExclusiveContext* cx)
UniqueModule
wasm::Compile(Bytes&& bytecode, CompileArgs&& args, UniqueChars* error)
{
bool newFormat = args.assumptions.newFormat;
auto init = js::MakeUnique<ModuleGeneratorData>(args.assumptions.usesSignal);
if (!init)
return nullptr;
@ -1087,7 +1168,7 @@ wasm::Compile(Bytes&& bytecode, CompileArgs&& args, UniqueChars* error)
return nullptr;
ImportNameVector importNames;
if (!DecodeImportSection(d, init.get(), &importNames))
if (!DecodeImportSection(d, newFormat, init.get(), &importNames))
return nullptr;
if (!DecodeFunctionSection(d, init.get()))
@ -1100,10 +1181,10 @@ wasm::Compile(Bytes&& bytecode, CompileArgs&& args, UniqueChars* error)
if (!mg.init(Move(init), Move(args)))
return nullptr;
if (!DecodeMemorySection(d, mg))
if (!DecodeMemorySection(d, newFormat, mg))
return nullptr;
if (!DecodeExportSection(d, mg))
if (!DecodeExportSection(d, newFormat, mg))
return nullptr;
if (!DecodeCodeSection(d, mg))

Просмотреть файл

@ -324,6 +324,8 @@ ModuleConstructor(JSContext* cx, unsigned argc, Value* vp)
if (!compileArgs.init(cx))
return true;
compileArgs.assumptions.newFormat = true;
JS::AutoFilename af;
if (DescribeScriptedCaller(cx, &af)) {
compileArgs.filename = DuplicateString(cx, af.get());

Просмотреть файл

@ -2433,10 +2433,6 @@ ParseExport(WasmParseContext& c)
case WasmToken::Name:
return new(c.lifo) AstExport(name.text(), AstRef(exportee.name(), AstNoIndex));
case WasmToken::Memory:
if (name.text() != AstName(MOZ_UTF16("memory"), 6)) {
c.ts.generateError(exportee, c.error);
return nullptr;
}
return new(c.lifo) AstExport(name.text());
default:
break;
@ -2978,7 +2974,7 @@ ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
}
for (AstExport* export_ : module->exports()) {
if (export_->kind() != AstExportKind::Func)
if (export_->kind() != DefinitionKind::Function)
continue;
if (!r.resolveFunction(export_->func()))
return false;
@ -3397,10 +3393,20 @@ EncodeBytes(Encoder& e, AstName wasmName)
}
static bool
EncodeImport(Encoder& e, AstImport& imp)
EncodeImport(Encoder& e, bool newFormat, AstImport& imp)
{
if (!e.writeVarU32(imp.sig().index()))
return false;
if (!newFormat) {
if (!e.writeVarU32(imp.sig().index()))
return false;
if (!EncodeBytes(e, imp.module()))
return false;
if (!EncodeBytes(e, imp.func()))
return false;
return true;
}
if (!EncodeBytes(e, imp.module()))
return false;
@ -3408,11 +3414,17 @@ EncodeImport(Encoder& e, AstImport& imp)
if (!EncodeBytes(e, imp.func()))
return false;
if (!e.writeVarU32(uint32_t(DefinitionKind::Function)))
return false;
if (!e.writeVarU32(imp.sig().index()))
return false;
return true;
}
static bool
EncodeImportSection(Encoder& e, AstModule& module)
EncodeImportSection(Encoder& e, bool newFormat, AstModule& module)
{
if (module.imports().empty())
return true;
@ -3425,7 +3437,7 @@ EncodeImportSection(Encoder& e, AstModule& module)
return false;
for (AstImport* imp : module.imports()) {
if (!EncodeImport(e, *imp))
if (!EncodeImport(e, newFormat, *imp))
return false;
}
@ -3434,7 +3446,7 @@ EncodeImportSection(Encoder& e, AstModule& module)
}
static bool
EncodeMemorySection(Encoder& e, AstModule& module)
EncodeMemorySection(Encoder& e, bool newFormat, AstModule& module)
{
if (!module.maybeMemory())
return true;
@ -3452,61 +3464,85 @@ EncodeMemorySection(Encoder& e, AstModule& module)
if (!e.writeVarU32(maxSize))
return false;
uint8_t exported = 0;
for (AstExport* exp : module.exports()) {
if (exp->kind() == AstExportKind::Memory) {
exported = 1;
break;
if (!newFormat) {
uint8_t exported = 0;
for (AstExport* exp : module.exports()) {
if (exp->kind() == DefinitionKind::Memory) {
exported = 1;
break;
}
}
}
if (!e.writeFixedU8(exported))
return false;
if (!e.writeFixedU8(exported))
return false;
}
e.finishSection(offset);
return true;
}
static bool
EncodeFunctionExport(Encoder& e, AstExport& exp)
EncodeExport(Encoder& e, bool newFormat, AstExport& exp)
{
if (!e.writeVarU32(exp.func().index()))
return false;
if (!newFormat) {
if (exp.kind() != DefinitionKind::Function)
return true;
if (!e.writeVarU32(exp.func().index()))
return false;
if (!EncodeBytes(e, exp.name()))
return false;
return true;
}
if (!EncodeBytes(e, exp.name()))
return false;
if (!e.writeVarU32(uint32_t(exp.kind())))
return false;
switch (exp.kind()) {
case DefinitionKind::Function:
if (!e.writeVarU32(exp.func().index()))
return false;
break;
case DefinitionKind::Memory:
if (!e.writeVarU32(0))
return false;
break;
}
return true;
}
static bool
EncodeExportSection(Encoder& e, AstModule& module)
EncodeExportSection(Encoder& e, bool newFormat, AstModule& module)
{
uint32_t numFuncExports = 0;
for (AstExport* exp : module.exports()) {
if (exp->kind() == AstExportKind::Func)
numFuncExports++;
uint32_t numExports = 0;
if (newFormat) {
numExports = module.exports().length();
} else {
for (AstExport* exp : module.exports()) {
if (exp->kind() == DefinitionKind::Function)
numExports++;
}
}
if (!numFuncExports)
if (!numExports)
return true;
size_t offset;
if (!e.startSection(ExportSectionId, &offset))
return false;
if (!e.writeVarU32(numFuncExports))
if (!e.writeVarU32(numExports))
return false;
for (AstExport* exp : module.exports()) {
switch (exp->kind()) {
case AstExportKind::Func:
if (!EncodeFunctionExport(e, *exp))
return false;
break;
case AstExportKind::Memory:
continue;
}
if (!EncodeExport(e, newFormat, *exp))
return false;
}
e.finishSection(offset);
@ -3632,7 +3668,7 @@ EncodeDataSection(Encoder& e, AstModule& module)
}
static bool
EncodeModule(AstModule& module, Bytes* bytes)
EncodeModule(AstModule& module, bool newFormat, Bytes* bytes)
{
Encoder e(*bytes);
@ -3645,7 +3681,7 @@ EncodeModule(AstModule& module, Bytes* bytes)
if (!EncodeTypeSection(e, module))
return false;
if (!EncodeImportSection(e, module))
if (!EncodeImportSection(e, newFormat, module))
return false;
if (!EncodeFunctionSection(e, module))
@ -3654,10 +3690,10 @@ EncodeModule(AstModule& module, Bytes* bytes)
if (!EncodeTableSection(e, module))
return false;
if (!EncodeMemorySection(e, module))
if (!EncodeMemorySection(e, newFormat, module))
return false;
if (!EncodeExportSection(e, module))
if (!EncodeExportSection(e, newFormat, module))
return false;
if (!EncodeCodeSection(e, module))
@ -3672,7 +3708,7 @@ EncodeModule(AstModule& module, Bytes* bytes)
/*****************************************************************************/
bool
wasm::TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error)
wasm::TextToBinary(const char16_t* text, bool newFormat, Bytes* bytes, UniqueChars* error)
{
LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
AstModule* module = ParseModule(text, lifo, error);
@ -3682,5 +3718,5 @@ wasm::TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error)
if (!ResolveModule(lifo, module, error))
return false;
return EncodeModule(*module, bytes);
return EncodeModule(*module, newFormat, bytes);
}

Просмотреть файл

@ -28,9 +28,12 @@ namespace wasm {
// Translate the textual representation of a wasm module (given by a
// null-terminated char16_t array) into serialized bytes. If there is an error
// other than out-of-memory an error message string will be stored in 'error'.
// The 'newFormat' argument is transitional and enables the new binary
// format for memory and table imports and exports so that tests can be written
// before the transition is complete.
extern MOZ_MUST_USE bool
TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error);
TextToBinary(const char16_t* text, bool newFormat, Bytes* bytes, UniqueChars* error);
} // namespace wasm
} // namespace js

Просмотреть файл

@ -349,7 +349,8 @@ Assumptions::operator==(const Assumptions& rhs) const
return usesSignal == rhs.usesSignal &&
cpuId == rhs.cpuId &&
buildId.length() == rhs.buildId.length() &&
PodEqual(buildId.begin(), rhs.buildId.begin(), buildId.length());
PodEqual(buildId.begin(), rhs.buildId.begin(), buildId.length()) &&
newFormat == rhs.newFormat;
}
size_t
@ -357,7 +358,8 @@ Assumptions::serializedSize() const
{
return sizeof(usesSignal) +
sizeof(uint32_t) +
SerializedPodVectorSize(buildId);
SerializedPodVectorSize(buildId) +
sizeof(bool);
}
uint8_t*
@ -366,6 +368,7 @@ Assumptions::serialize(uint8_t* cursor) const
cursor = WriteBytes(cursor, &usesSignal, sizeof(usesSignal));
cursor = WriteScalar<uint32_t>(cursor, cpuId);
cursor = SerializePodVector(cursor, buildId);
cursor = WriteScalar<bool>(cursor, newFormat);
return cursor;
}
@ -374,7 +377,8 @@ Assumptions::deserialize(const uint8_t* cursor)
{
(cursor = ReadBytes(cursor, &usesSignal, sizeof(usesSignal))) &&
(cursor = ReadScalar<uint32_t>(cursor, &cpuId)) &&
(cursor = DeserializePodVector(cursor, &buildId));
(cursor = DeserializePodVector(cursor, &buildId)) &&
(cursor = ReadScalar<bool>(cursor, &newFormat));
return cursor;
}

Просмотреть файл

@ -794,7 +794,9 @@ struct Assumptions
SignalUsage usesSignal;
uint32_t cpuId;
JS::BuildIdCharVector buildId;
bool newFormat;
Assumptions() : cpuId(0), newFormat(false) {}
MOZ_MUST_USE bool init(SignalUsage usesSignal, JS::BuildIdOp buildIdOp);
bool operator==(const Assumptions& rhs) const;

Просмотреть файл

@ -517,10 +517,8 @@ WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp)
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
if (args.length() != 1) {
ReportUsageError(cx, callee, "Wrong number of arguments");
if (!args.requireAtLeast(cx, "wasmTextToBinary", 1))
return false;
}
if (!args[0].isString()) {
ReportUsageError(cx, callee, "First argument must be a String");
@ -531,9 +529,28 @@ WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp)
if (!twoByteChars.initTwoByte(cx, args[0].toString()))
return false;
bool newFormat = false;
if (args.hasDefined(1)) {
if (!args[1].isString()) {
ReportUsageError(cx, callee, "Second argument, if present, must be a String");
return false;
}
JSLinearString* str = args[1].toString()->ensureLinear(cx);
if (!str)
return false;
if (!StringEqualsAscii(str, "new-format")) {
ReportUsageError(cx, callee, "Unknown string value for second argument");
return false;
}
newFormat = true;
}
wasm::Bytes bytes;
UniqueChars error;
if (!wasm::TextToBinary(twoByteChars.twoByteChars(), &bytes, &error)) {
if (!wasm::TextToBinary(twoByteChars.twoByteChars(), newFormat, &bytes, &error)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
error.get() ? error.get() : "out of memory");
return false;

Просмотреть файл

@ -0,0 +1,74 @@
load(libdir + 'wasm.js');
load(libdir + 'asserts.js');
const Module = WebAssembly.Module;
const Instance = WebAssembly.Instance;
// Explicitly opt into the new binary format for imports and exports until it
// is used by default everywhere.
const textToBinary = str => wasmTextToBinary(str, 'new-format');
const m1 = new Module(textToBinary('(module (import "foo" "bar") (import "baz" "quux"))'));
assertErrorMessage(() => new Instance(m1), TypeError, /no import object given/);
// Import order:
var arr = [];
var importObj = {
get foo() { arr.push("foo") },
get baz() { arr.push("bad") },
};
assertErrorMessage(() => new Instance(m1, importObj), TypeError, /import object field is not an Object/);
assertEq(arr.join(), "foo");
var arr = [];
var importObj = {
get foo() {
arr.push("foo");
return { get bar() { arr.push("bar"); return null } }
},
get baz() { arr.push("bad") },
};
assertErrorMessage(() => new Instance(m1, importObj), TypeError, /import object field is not a Function/);
assertEq(arr.join(), "foo,bar");
var arr = [];
var importObj = {
get foo() {
arr.push("foo");
return { get bar() { arr.push("bar"); return () => arr.push("bad") } }
},
get baz() {
arr.push("baz");
return { get quux() { arr.push("quux"); return () => arr.push("bad") } }
}
};
assertEq(new Instance(m1, importObj) instanceof Instance, true);
assertEq(arr.join(), "foo,bar,baz,quux");
// Export key order:
var code = textToBinary('(module)');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).length, 0);
var code = textToBinary('(module (func) (export "foo" 0))');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).join(), "foo");
assertEq(e.foo(), undefined);
var code = textToBinary('(module (func) (export "foo" 0) (export "bar" 0))');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).join(), "foo,bar");
assertEq(e.foo(), undefined);
assertEq(e.bar(), undefined);
assertEq(e.foo, e.bar);
var code = textToBinary('(module (memory 1 1) (export "memory" memory))');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).join(), "memory");
var code = textToBinary('(module (memory 1 1) (export "foo" memory) (export "bar" memory))');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).join(), "foo,bar");
assertEq(e.foo, e.bar);

Просмотреть файл

@ -1,7 +1,11 @@
load(libdir + 'wasm.js');
load(libdir + 'asserts.js');
const emptyModule = wasmTextToBinary('(module)');
// Explicitly opt into the new binary format for imports and exports until it
// is used by default everywhere.
const textToBinary = str => wasmTextToBinary(str, 'new-format');
const emptyModule = textToBinary('(module)');
// 'WebAssembly' property on global object
const wasmDesc = Object.getOwnPropertyDescriptor(this, 'WebAssembly');
@ -98,23 +102,4 @@ assertEq(exportsDesc.writable, true);
assertEq(exportsDesc.enumerable, true);
assertEq(exportsDesc.configurable, true);
// Exports object:
// Note: at some point the exports object should become an ES6 module namespace
// exotic object. For now, don't probe too hard on the property descriptors or
// the exports object itself.
const e1 = i1.exports;
assertEq(e1, exportsDesc.value);
assertEq(Object.keys(e1).length, 0);
var code = wasmTextToBinary('(module (func) (export "foo" 0))');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).join(), "foo");
assertEq(e.foo(), undefined);
var code = wasmTextToBinary('(module (func) (export "foo" 0) (export "bar" 0))');
var e = new Instance(new Module(code)).exports;
assertEq(Object.keys(e).join(), "foo,bar");
assertEq(e.foo(), undefined);
assertEq(e.bar(), undefined);
assertEq(e.foo, e.bar);
// TODO: test export object objects are ES6 module namespace objects.