Skip to content

Commit d955b5a

Browse files
authored
[NFC] Model imported and exported function values (#7952)
Update ExternalInterface to provide imported functions as values rather than just exposing an interface to call them. Similarly, update a ModuleRunner instance to provide exported functions as values rather than just providing an interface to call them. Dealing with imported and exported functions as values like this is a prerequisite for fixing our interpretation of function reference casts to reflect that fact that the runtime type of a reference to an imported function may be a nontrivial subtype of the type of the corresponding function import. Actually fixing this is left to a follow-on PR.
1 parent 24ce8c2 commit d955b5a

File tree

5 files changed

+235
-185
lines changed

5 files changed

+235
-185
lines changed

src/shell-interface.h

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,21 +133,36 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
133133
});
134134
}
135135

136-
Flow callImport(Function* import, const Literals& arguments) override {
136+
Literal getImportedFunction(Function* import) override {
137+
// TODO: We should perhaps restrict the types with which the well-known
138+
// functions can be imported.
137139
if (import->module == SPECTEST && import->base.startsWith(PRINT)) {
138-
for (auto argument : arguments) {
139-
std::cout << argument << " : " << argument.type << '\n';
140-
}
141-
return {};
140+
// Use a null instance because these are host functions.
141+
return Literal(
142+
std::make_shared<FuncData>(import->name,
143+
nullptr,
144+
[](const Literals& arguments) -> Flow {
145+
for (auto argument : arguments) {
146+
std::cout << argument << " : "
147+
<< argument.type << '\n';
148+
}
149+
return Flow();
150+
}),
151+
import->type);
142152
} else if (import->module == ENV && import->base == EXIT) {
143-
// XXX hack for torture tests
144-
std::cout << "exit()\n";
145-
throw ExitException();
153+
return Literal(std::make_shared<FuncData>(import->name,
154+
nullptr,
155+
[](const Literals&) -> Flow {
156+
// XXX hack for torture tests
157+
std::cout << "exit()\n";
158+
throw ExitException();
159+
}),
160+
import->type);
146161
} else if (auto* inst = getImportInstance(import)) {
147-
return inst->callExport(import->base, arguments);
162+
return inst->getExportedFunction(import->base);
148163
}
149-
Fatal() << "callImport: unknown import: " << import->module.str << "."
150-
<< import->name.str;
164+
Fatal() << "getImportedFunction: unknown import: " << import->module.str
165+
<< "." << import->name.str;
151166
}
152167

153168
int8_t load8s(Address addr, Name memoryName) override {

src/tools/execution-results.h

Lines changed: 118 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -78,124 +78,131 @@ struct LoggingExternalInterface : public ShellExternalInterface {
7878
}
7979
}
8080

81-
Flow callImport(Function* import, const Literals& arguments) override {
82-
if (import->module == "fuzzing-support") {
83-
if (import->base.startsWith("log")) {
84-
// This is a logging function like log-i32 or log-f64
85-
std::cout << "[LoggingExternalInterface ";
86-
if (import->base == "log-branch") {
87-
// Report this as a special logging, so we can differentiate it from
88-
// the others in the fuzzer.
89-
std::cout << "log-branch";
90-
} else {
91-
// All others are just reported as loggings.
92-
std::cout << "logging";
93-
}
94-
loggings.push_back(Literal()); // buffer with a None between calls
95-
for (auto argument : arguments) {
96-
if (argument.type == Type::i64) {
97-
// To avoid JS legalization changing logging results, treat a
98-
// logging of an i64 as two i32s (which is what legalization would
99-
// turn us into).
100-
auto low = Literal(int32_t(argument.getInteger()));
101-
auto high = Literal(int32_t(argument.getInteger() >> int32_t(32)));
102-
std::cout << ' ' << low;
103-
loggings.push_back(low);
104-
std::cout << ' ' << high;
105-
loggings.push_back(high);
81+
Literal getImportedFunction(Function* import) override {
82+
if (linkedInstances.count(import->module)) {
83+
return getImportInstance(import)->getExportedFunction(import->base);
84+
}
85+
auto f = [import, this](const Literals& arguments) -> Flow {
86+
if (import->module == "fuzzing-support") {
87+
if (import->base.startsWith("log")) {
88+
// This is a logging function like log-i32 or log-f64
89+
std::cout << "[LoggingExternalInterface ";
90+
if (import->base == "log-branch") {
91+
// Report this as a special logging, so we can differentiate it
92+
// from the others in the fuzzer.
93+
std::cout << "log-branch";
10694
} else {
107-
std::cout << ' ' << argument;
108-
loggings.push_back(argument);
95+
// All others are just reported as loggings.
96+
std::cout << "logging";
10997
}
110-
}
111-
std::cout << "]\n";
112-
return {};
113-
} else if (import->base == "throw") {
114-
// Throw something, depending on the value of the argument. 0 means we
115-
// should throw a JS exception, and any other value means we should
116-
// throw a wasm exception (with that value as the payload).
117-
if (arguments[0].geti32() == 0) {
118-
throwJSException();
119-
} else {
120-
auto payload = std::make_shared<ExnData>(wasmTag, arguments);
121-
throwException(WasmException{Literal(payload)});
122-
}
123-
} else if (import->base == "table-get") {
124-
// Check for errors here, duplicating tableLoad(), because that will
125-
// trap, and we just want to throw an exception (the same as JS would).
126-
if (!exportedTable) {
127-
throwJSException();
128-
}
129-
auto index = arguments[0].getUnsigned();
130-
if (index >= tables[exportedTable].size()) {
131-
throwJSException();
132-
}
133-
return {tableLoad(exportedTable, index)};
134-
} else if (import->base == "table-set") {
135-
if (!exportedTable) {
136-
throwJSException();
137-
}
138-
auto index = arguments[0].getUnsigned();
139-
if (index >= tables[exportedTable].size()) {
140-
throwJSException();
141-
}
142-
tableStore(exportedTable, index, arguments[1]);
143-
return {};
144-
} else if (import->base == "call-export") {
145-
callExportAsJS(arguments[0].geti32());
146-
// The second argument determines if we should catch and rethrow
147-
// exceptions. There is no observable difference in those two modes in
148-
// the binaryen interpreter, so we don't need to do anything.
149-
150-
// Return nothing. If we wanted to return a value we'd need to have
151-
// multiple such functions, one for each signature.
152-
return {};
153-
} else if (import->base == "call-export-catch") {
154-
try {
98+
loggings.push_back(Literal()); // buffer with a None between calls
99+
for (auto argument : arguments) {
100+
if (argument.type == Type::i64) {
101+
// To avoid JS legalization changing logging results, treat a
102+
// logging of an i64 as two i32s (which is what legalization
103+
// would turn us into).
104+
auto low = Literal(int32_t(argument.getInteger()));
105+
auto high =
106+
Literal(int32_t(argument.getInteger() >> int32_t(32)));
107+
std::cout << ' ' << low;
108+
loggings.push_back(low);
109+
std::cout << ' ' << high;
110+
loggings.push_back(high);
111+
} else {
112+
std::cout << ' ' << argument;
113+
loggings.push_back(argument);
114+
}
115+
}
116+
std::cout << "]\n";
117+
return {};
118+
} else if (import->base == "throw") {
119+
// Throw something, depending on the value of the argument. 0 means
120+
// we should throw a JS exception, and any other value means we
121+
// should throw a wasm exception (with that value as the payload).
122+
if (arguments[0].geti32() == 0) {
123+
throwJSException();
124+
} else {
125+
auto payload = std::make_shared<ExnData>(wasmTag, arguments);
126+
throwException(WasmException{Literal(payload)});
127+
}
128+
} else if (import->base == "table-get") {
129+
// Check for errors here, duplicating tableLoad(), because that will
130+
// trap, and we just want to throw an exception (the same as JS
131+
// would).
132+
if (!exportedTable) {
133+
throwJSException();
134+
}
135+
auto index = arguments[0].getUnsigned();
136+
if (index >= tables[exportedTable].size()) {
137+
throwJSException();
138+
}
139+
return {tableLoad(exportedTable, index)};
140+
} else if (import->base == "table-set") {
141+
if (!exportedTable) {
142+
throwJSException();
143+
}
144+
auto index = arguments[0].getUnsigned();
145+
if (index >= tables[exportedTable].size()) {
146+
throwJSException();
147+
}
148+
tableStore(exportedTable, index, arguments[1]);
149+
return {};
150+
} else if (import->base == "call-export") {
155151
callExportAsJS(arguments[0].geti32());
156-
return {Literal(int32_t(0))};
157-
} catch (const WasmException& e) {
158-
return {Literal(int32_t(1))};
159-
}
160-
} else if (import->base == "call-ref") {
161-
// Similar to call-export*, but with a ref.
162-
callRefAsJS(arguments[0]);
163-
return {};
164-
} else if (import->base == "call-ref-catch") {
165-
try {
152+
// The second argument determines if we should catch and rethrow
153+
// exceptions. There is no observable difference in those two modes
154+
// in the binaryen interpreter, so we don't need to do anything.
155+
156+
// Return nothing. If we wanted to return a value we'd need to have
157+
// multiple such functions, one for each signature.
158+
return {};
159+
} else if (import->base == "call-export-catch") {
160+
try {
161+
callExportAsJS(arguments[0].geti32());
162+
return {Literal(int32_t(0))};
163+
} catch (const WasmException& e) {
164+
return {Literal(int32_t(1))};
165+
}
166+
} else if (import->base == "call-ref") {
167+
// Similar to call-export*, but with a ref.
166168
callRefAsJS(arguments[0]);
167-
return {Literal(int32_t(0))};
168-
} catch (const WasmException& e) {
169-
return {Literal(int32_t(1))};
169+
return {};
170+
} else if (import->base == "call-ref-catch") {
171+
try {
172+
callRefAsJS(arguments[0]);
173+
return {Literal(int32_t(0))};
174+
} catch (const WasmException& e) {
175+
return {Literal(int32_t(1))};
176+
}
177+
} else if (import->base == "sleep") {
178+
// Do not actually sleep, just return the id.
179+
return {arguments[1]};
180+
} else {
181+
WASM_UNREACHABLE("unknown fuzzer import");
170182
}
171-
} else if (import->base == "sleep") {
172-
// Do not actually sleep, just return the id.
173-
return {arguments[1]};
174-
} else {
175-
WASM_UNREACHABLE("unknown fuzzer import");
176-
}
177-
} else if (import->module == ENV) {
178-
if (import->base == "log_execution") {
179-
std::cout << "[LoggingExternalInterface log-execution";
180-
for (auto argument : arguments) {
181-
std::cout << ' ' << argument;
183+
} else if (import->module == ENV) {
184+
if (import->base == "log_execution") {
185+
std::cout << "[LoggingExternalInterface log-execution";
186+
for (auto argument : arguments) {
187+
std::cout << ' ' << argument;
188+
}
189+
std::cout << "]\n";
190+
return {};
191+
} else if (import->base == "setTempRet0") {
192+
state.tempRet0 = arguments[0].geti32();
193+
return {};
194+
} else if (import->base == "getTempRet0") {
195+
return {Literal(state.tempRet0)};
182196
}
183-
std::cout << "]\n";
184-
return {};
185-
} else if (import->base == "setTempRet0") {
186-
state.tempRet0 = arguments[0].geti32();
187-
return {};
188-
} else if (import->base == "getTempRet0") {
189-
return {Literal(state.tempRet0)};
190197
}
191-
} else if (linkedInstances.count(import->module)) {
192-
// This is from a recognized module.
193-
return getImportInstance(import)->callExport(import->base, arguments);
194-
}
195-
// Anything else, we ignore.
196-
std::cerr << "[LoggingExternalInterface ignoring an unknown import "
197-
<< import->module << " . " << import->base << '\n';
198-
return {};
198+
// Anything else, we ignore.
199+
std::cerr << "[LoggingExternalInterface ignoring an unknown import "
200+
<< import->module << " . " << import->base << '\n';
201+
return {};
202+
};
203+
// Use a null instance because this is a host function.
204+
return Literal(std::make_shared<FuncData>(import->name, nullptr, f),
205+
import->type);
199206
}
200207

201208
void throwJSException() {

0 commit comments

Comments
 (0)