Skip to content

Commit f738f5c

Browse files
authored
Very simple module linking in wasm-shell (#3792)
This is a rewrite of the wasm-shell tool, with the goal of improved compatibility with the reference interpreter and the spec test suite. To facilitate that, module instances are provided with a list of linked instances, and imported objects are looked up in the correct instance. The new shell can: - register and link modules using the (register ...) command. - parse binary modules with the syntax (module binary ...). - provide the "spectest" module defined in the reference interpreter - assert instantiation traps with assert_trap - better check linkability by looking up the linked instances in - assert_unlinkable It cannot call external function references that are not direct imports. That would require bigger changes.
1 parent bd2e866 commit f738f5c

File tree

6 files changed

+624
-415
lines changed

6 files changed

+624
-415
lines changed

src/binaryen-c.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3907,7 +3907,7 @@ BinaryenModuleRef BinaryenModuleRead(char* input, size_t inputSize) {
39073907

39083908
void BinaryenModuleInterpret(BinaryenModuleRef module) {
39093909
ShellExternalInterface interface;
3910-
ModuleInstance instance(*(Module*)module, &interface);
3910+
ModuleInstance instance(*(Module*)module, &interface, {});
39113911
}
39123912

39133913
BinaryenIndex BinaryenModuleAddDebugInfoFileName(BinaryenModuleRef module,

src/shell-interface.h

Lines changed: 28 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -95,68 +95,41 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
9595
} memory;
9696

9797
std::unordered_map<Name, std::vector<Literal>> tables;
98+
std::map<Name, std::shared_ptr<ModuleInstance>> linkedInstances;
9899

99-
ShellExternalInterface() : memory() {}
100+
ShellExternalInterface(
101+
std::map<Name, std::shared_ptr<ModuleInstance>> linkedInstances_ = {})
102+
: memory() {
103+
linkedInstances.swap(linkedInstances_);
104+
}
100105
virtual ~ShellExternalInterface() = default;
101106

102-
void init(Module& wasm, ModuleInstance& instance) override {
103-
memory.resize(wasm.memory.initial * wasm::Memory::kPageSize);
107+
ModuleInstance* getImportInstance(Importable* import) {
108+
auto it = linkedInstances.find(import->module);
109+
if (it == linkedInstances.end()) {
110+
Fatal() << "importGlobals: unknown import: " << import->module.str << "."
111+
<< import->base.str;
112+
}
113+
return it->second.get();
114+
}
104115

105-
if (wasm.tables.size() > 0) {
106-
for (auto& table : wasm.tables) {
107-
tables[table->name].resize(table->initial);
108-
}
116+
void init(Module& wasm, ModuleInstance& instance) override {
117+
if (wasm.memory.exists && !wasm.memory.imported()) {
118+
memory.resize(wasm.memory.initial * wasm::Memory::kPageSize);
109119
}
120+
ModuleUtils::iterDefinedTables(
121+
wasm, [&](Table* table) { tables[table->name].resize(table->initial); });
110122
}
111123

112124
void importGlobals(std::map<Name, Literals>& globals, Module& wasm) override {
113-
// add spectest globals
114125
ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
115-
if (import->module == SPECTEST && import->base.startsWith("global_")) {
116-
TODO_SINGLE_COMPOUND(import->type);
117-
switch (import->type.getBasic()) {
118-
case Type::i32:
119-
globals[import->name] = {Literal(int32_t(666))};
120-
break;
121-
case Type::i64:
122-
globals[import->name] = {Literal(int64_t(666))};
123-
break;
124-
case Type::f32:
125-
globals[import->name] = {Literal(float(666.6))};
126-
break;
127-
case Type::f64:
128-
globals[import->name] = {Literal(double(666.6))};
129-
break;
130-
case Type::v128:
131-
WASM_UNREACHABLE("v128 not implemented yet");
132-
case Type::funcref:
133-
case Type::externref:
134-
case Type::anyref:
135-
case Type::eqref:
136-
globals[import->name] = {Literal::makeNull(import->type)};
137-
break;
138-
case Type::i31ref:
139-
WASM_UNREACHABLE("TODO: i31ref");
140-
case Type::dataref:
141-
WASM_UNREACHABLE("TODO: dataref");
142-
case Type::none:
143-
case Type::unreachable:
144-
WASM_UNREACHABLE("unexpected type");
145-
}
146-
}
147-
});
148-
if (wasm.memory.imported() && wasm.memory.module == SPECTEST &&
149-
wasm.memory.base == MEMORY) {
150-
// imported memory has initial 1 and max 2
151-
wasm.memory.initial = 1;
152-
wasm.memory.max = 2;
153-
}
154-
155-
ModuleUtils::iterImportedTables(wasm, [&](Table* table) {
156-
if (table->module == SPECTEST && table->base == TABLE) {
157-
table->initial = 10;
158-
table->max = 20;
126+
auto inst = getImportInstance(import);
127+
auto* exportedGlobal = inst->wasm.getExportOrNull(import->base);
128+
if (!exportedGlobal) {
129+
Fatal() << "importGlobals: unknown import: " << import->module.str
130+
<< "." << import->name.str;
159131
}
132+
globals[import->name] = inst->globals[exportedGlobal->value];
160133
});
161134
}
162135

@@ -170,6 +143,8 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
170143
// XXX hack for torture tests
171144
std::cout << "exit()\n";
172145
throw ExitException();
146+
} else if (auto* inst = getImportInstance(import)) {
147+
return inst->callExport(import->base, arguments);
173148
}
174149
Fatal() << "callImport: unknown import: " << import->module.str << "."
175150
<< import->name.str;
@@ -254,7 +229,7 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
254229
if (addr >= table.size()) {
255230
trap("out of bounds table access");
256231
} else {
257-
table.emplace(table.begin() + addr, entry);
232+
table[addr] = entry;
258233
}
259234
}
260235

src/tools/wasm-ctor-eval.cpp

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,11 @@ static Index STACK_UPPER_LIMIT = STACK_START + STACK_SIZE;
127127
class EvallingModuleInstance
128128
: public ModuleInstanceBase<EvallingGlobalManager, EvallingModuleInstance> {
129129
public:
130-
EvallingModuleInstance(Module& wasm, ExternalInterface* externalInterface)
131-
: ModuleInstanceBase(wasm, externalInterface) {
130+
EvallingModuleInstance(Module& wasm,
131+
ExternalInterface* externalInterface,
132+
std::map<Name, std::shared_ptr<EvallingModuleInstance>>
133+
linkedInstances_ = {})
134+
: ModuleInstanceBase(wasm, externalInterface, linkedInstances_) {
132135
// if any global in the module has a non-const constructor, it is using a
133136
// global import, which we don't have, and is illegal to use
134137
ModuleUtils::iterDefinedGlobals(wasm, [&](Global* global) {
@@ -165,41 +168,103 @@ class EvallingModuleInstance
165168
}
166169
};
167170

171+
// Build an artificial `env` module based on a module's imports, so that the
172+
// interpreter can use correct object instances. It initializes usable global
173+
// imports, and fills the rest with fake values since those are dangerous to
174+
// use. we will fail if dangerous globals are used.
175+
std::unique_ptr<Module> buildEnvModule(Module& wasm) {
176+
auto env = std::make_unique<Module>();
177+
env->name = "env";
178+
179+
// create empty functions with similar signature
180+
ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) {
181+
if (func->module == "env") {
182+
Builder builder(*env);
183+
auto* copied = ModuleUtils::copyFunction(func, *env);
184+
copied->module = Name();
185+
copied->base = Name();
186+
copied->body = builder.makeUnreachable();
187+
env->addExport(
188+
builder.makeExport(func->base, copied->name, ExternalKind::Function));
189+
}
190+
});
191+
192+
// create tables with similar initial and max values
193+
ModuleUtils::iterImportedTables(wasm, [&](Table* table) {
194+
if (table->module == "env") {
195+
auto* copied = ModuleUtils::copyTable(table, *env);
196+
copied->module = Name();
197+
copied->base = Name();
198+
env->addExport(Builder(*env).makeExport(
199+
table->base, copied->name, ExternalKind::Table));
200+
}
201+
});
202+
203+
ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) {
204+
if (global->module == "env") {
205+
auto* copied = ModuleUtils::copyGlobal(global, *env);
206+
copied->module = Name();
207+
copied->base = Name();
208+
209+
Builder builder(*env);
210+
if (global->base == STACKTOP || global->base == STACK_MAX) {
211+
copied->init = builder.makeConst(STACK_START);
212+
} else {
213+
copied->init = builder.makeConst(Literal::makeZero(global->type));
214+
}
215+
env->addExport(
216+
builder.makeExport(global->base, copied->name, ExternalKind::Global));
217+
}
218+
});
219+
220+
// create an exported memory with the same initial and max size
221+
ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) {
222+
if (memory->module == "env") {
223+
env->memory.name = wasm.memory.name;
224+
env->memory.exists = true;
225+
env->memory.initial = memory->initial;
226+
env->memory.max = memory->max;
227+
env->memory.shared = memory->shared;
228+
env->memory.indexType = memory->indexType;
229+
env->addExport(Builder(*env).makeExport(
230+
wasm.memory.base, wasm.memory.name, ExternalKind::Memory));
231+
}
232+
});
233+
234+
return env;
235+
}
236+
168237
struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
169238
Module* wasm;
170239
EvallingModuleInstance* instance;
240+
std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances;
241+
242+
CtorEvalExternalInterface(
243+
std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances_ =
244+
{}) {
245+
linkedInstances.swap(linkedInstances_);
246+
}
171247

172248
void init(Module& wasm_, EvallingModuleInstance& instance_) override {
173249
wasm = &wasm_;
174250
instance = &instance_;
175251
}
176252

177253
void importGlobals(EvallingGlobalManager& globals, Module& wasm_) override {
178-
// fill usable values for stack imports, and globals initialized to them
179-
ImportInfo imports(wasm_);
180-
if (auto* stackTop = imports.getImportedGlobal(ENV, STACKTOP)) {
181-
globals[stackTop->name] = {Literal(int32_t(STACK_START))};
182-
if (auto* stackTop =
183-
GlobalUtils::getGlobalInitializedToImport(wasm_, ENV, STACKTOP)) {
184-
globals[stackTop->name] = {Literal(int32_t(STACK_START))};
185-
}
186-
}
187-
if (auto* stackMax = imports.getImportedGlobal(ENV, STACK_MAX)) {
188-
globals[stackMax->name] = {Literal(int32_t(STACK_START))};
189-
if (auto* stackMax =
190-
GlobalUtils::getGlobalInitializedToImport(wasm_, ENV, STACK_MAX)) {
191-
globals[stackMax->name] = {Literal(int32_t(STACK_START))};
192-
}
193-
}
194-
// fill in fake values for everything else, which is dangerous to use
195-
ModuleUtils::iterDefinedGlobals(wasm_, [&](Global* defined) {
196-
if (globals.find(defined->name) == globals.end()) {
197-
globals[defined->name] = Literal::makeZeros(defined->type);
198-
}
199-
});
200-
ModuleUtils::iterImportedGlobals(wasm_, [&](Global* import) {
201-
if (globals.find(import->name) == globals.end()) {
202-
globals[import->name] = Literal::makeZeros(import->type);
254+
ModuleUtils::iterImportedGlobals(wasm_, [&](Global* global) {
255+
auto it = linkedInstances.find(global->module);
256+
if (it != linkedInstances.end()) {
257+
auto* inst = it->second.get();
258+
auto* globalExport = inst->wasm.getExportOrNull(global->base);
259+
if (!globalExport) {
260+
throw FailToEvalException(std::string("importGlobals: ") +
261+
global->module.str + "." +
262+
global->base.str);
263+
}
264+
globals[global->name] = inst->globals[globalExport->value];
265+
} else {
266+
throw FailToEvalException(std::string("importGlobals: ") +
267+
global->module.str + "." + global->base.str);
203268
}
204269
});
205270
}
@@ -368,14 +433,25 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
368433
};
369434

370435
void evalCtors(Module& wasm, std::vector<std::string> ctors) {
371-
CtorEvalExternalInterface interface;
436+
// build and link the env module
437+
auto envModule = buildEnvModule(wasm);
438+
CtorEvalExternalInterface envInterface;
439+
auto envInstance =
440+
std::make_shared<EvallingModuleInstance>(*envModule, &envInterface);
441+
envInstance->setupEnvironment();
442+
443+
std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances;
444+
linkedInstances["env"] = envInstance;
445+
446+
CtorEvalExternalInterface interface(linkedInstances);
372447
try {
373448
// flatten memory, so we do not depend on the layout of data segments
374449
if (!MemoryUtils::flatten(wasm.memory)) {
375450
Fatal() << " ...stopping since could not flatten memory\n";
376451
}
452+
377453
// create an instance for evalling
378-
EvallingModuleInstance instance(wasm, &interface);
454+
EvallingModuleInstance instance(wasm, &interface, linkedInstances);
379455
// set up the stack area and other environment details
380456
instance.setupEnvironment();
381457
// we should not add new globals from here on; as a result, using

0 commit comments

Comments
 (0)