Skip to content

Commit 8c0f53d

Browse files
authored
[ctor-eval] Add --ignore-external-input option (#4428)
This is meant to address one of the main limitations of wasm-ctor-eval in emscripten atm, that libc++ global ctors will read env vars, which means they call an import, which stops us from evalling, emscripten-core/emscripten#15403 (comment) To handle that, this adds an option to ignore external input. When set, we can assume that no env vars will be read, no reading from stdin, no arguments to main(), etc. Perhaps these could each be separate options, but I think keeping it simple for now might be good enough.
1 parent 5049f07 commit 8c0f53d

File tree

8 files changed

+161
-11
lines changed

8 files changed

+161
-11
lines changed

auto_update_tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def update_ctor_eval_tests():
8787
print('..', os.path.basename(t))
8888
ctors = open(t + '.ctors').read().strip()
8989
cmd = shared.WASM_CTOR_EVAL + [t, '-all', '-o', 'a.wast', '-S', '--ctors', ctors]
90+
if 'ignore-external-input' in t:
91+
cmd += ['--ignore-external-input']
9092
support.run_command(cmd)
9193
actual = open('a.wast').read()
9294
out = t + '.out'

check.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ def run_ctor_eval_tests():
116116
print('..', os.path.basename(t))
117117
ctors = open(t + '.ctors').read().strip()
118118
cmd = shared.WASM_CTOR_EVAL + [t, '-all', '-o', 'a.wat', '-S', '--ctors', ctors]
119+
if 'ignore-external-input' in t:
120+
cmd += ['--ignore-external-input']
119121
support.run_command(cmd)
120122
actual = open('a.wat').read()
121123
out = t + '.out'

src/tools/wasm-ctor-eval.cpp

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ std::unique_ptr<Module> buildEnvModule(Module& wasm) {
142142

143143
// create empty functions with similar signature
144144
ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) {
145-
if (func->module == "env") {
145+
if (func->module == env->name) {
146146
Builder builder(*env);
147147
auto* copied = ModuleUtils::copyFunction(func, *env);
148148
copied->module = Name();
@@ -155,7 +155,7 @@ std::unique_ptr<Module> buildEnvModule(Module& wasm) {
155155

156156
// create tables with similar initial and max values
157157
ModuleUtils::iterImportedTables(wasm, [&](Table* table) {
158-
if (table->module == "env") {
158+
if (table->module == env->name) {
159159
auto* copied = ModuleUtils::copyTable(table, *env);
160160
copied->module = Name();
161161
copied->base = Name();
@@ -165,7 +165,7 @@ std::unique_ptr<Module> buildEnvModule(Module& wasm) {
165165
});
166166

167167
ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) {
168-
if (global->module == "env") {
168+
if (global->module == env->name) {
169169
auto* copied = ModuleUtils::copyGlobal(global, *env);
170170
copied->module = Name();
171171
copied->base = Name();
@@ -179,7 +179,7 @@ std::unique_ptr<Module> buildEnvModule(Module& wasm) {
179179

180180
// create an exported memory with the same initial and max size
181181
ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) {
182-
if (memory->module == "env") {
182+
if (memory->module == env->name) {
183183
env->memory.name = wasm.memory.name;
184184
env->memory.exists = true;
185185
env->memory.initial = memory->initial;
@@ -194,6 +194,11 @@ std::unique_ptr<Module> buildEnvModule(Module& wasm) {
194194
return env;
195195
}
196196

197+
// Whether to ignore external input to the program as it runs. If set, we will
198+
// assume that stdin is empty, that any env vars we try to read are not set,
199+
// that there are not arguments passed to main, etc.
200+
static bool ignoreExternalInput = false;
201+
197202
struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
198203
Module* wasm;
199204
EvallingModuleInstance* instance;
@@ -242,10 +247,63 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
242247
}
243248

244249
Literals callImport(Function* import, LiteralList& arguments) override {
250+
Name WASI("wasi_snapshot_preview1");
251+
252+
if (ignoreExternalInput) {
253+
if (import->module == WASI) {
254+
if (import->base == "environ_sizes_get") {
255+
if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
256+
import->getResults() != Type::i32) {
257+
throw FailToEvalException("wasi environ_sizes_get has wrong sig");
258+
}
259+
260+
// Write out a count of i32(0) and return __WASI_ERRNO_SUCCESS (0).
261+
store32(arguments[0].geti32(), 0);
262+
return {Literal(int32_t(0))};
263+
}
264+
265+
if (import->base == "environ_get") {
266+
if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
267+
import->getResults() != Type::i32) {
268+
throw FailToEvalException("wasi environ_get has wrong sig");
269+
}
270+
271+
// Just return __WASI_ERRNO_SUCCESS (0).
272+
return {Literal(int32_t(0))};
273+
}
274+
275+
if (import->base == "args_sizes_get") {
276+
if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
277+
import->getResults() != Type::i32) {
278+
throw FailToEvalException("wasi args_sizes_get has wrong sig");
279+
}
280+
281+
// Write out an argc of i32(0) and return a __WASI_ERRNO_SUCCESS (0).
282+
store32(arguments[0].geti32(), 0);
283+
return {Literal(int32_t(0))};
284+
}
285+
286+
if (import->base == "args_get") {
287+
if (arguments.size() != 2 || arguments[0].type != Type::i32 ||
288+
import->getResults() != Type::i32) {
289+
throw FailToEvalException("wasi args_get has wrong sig");
290+
}
291+
292+
// Just return __WASI_ERRNO_SUCCESS (0).
293+
return {Literal(int32_t(0))};
294+
}
295+
296+
// Otherwise, we don't recognize this import; continue normally to
297+
// error.
298+
}
299+
}
300+
245301
std::string extra;
246302
if (import->module == ENV && import->base == "___cxa_atexit") {
247303
extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that calls "
248304
"to atexit are not emitted";
305+
} else if (import->module == WASI && !ignoreExternalInput) {
306+
extra = "\nrecommendation: consider --ignore-external-input";
249307
}
250308
throw FailToEvalException(std::string("call import: ") +
251309
import->module.str + "." + import->base.str +
@@ -416,14 +474,14 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
416474
};
417475

418476
void evalCtors(Module& wasm, std::vector<std::string> ctors) {
477+
std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances;
478+
419479
// build and link the env module
420480
auto envModule = buildEnvModule(wasm);
421481
CtorEvalExternalInterface envInterface;
422482
auto envInstance =
423483
std::make_shared<EvallingModuleInstance>(*envModule, &envInterface);
424-
425-
std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances;
426-
linkedInstances["env"] = envInstance;
484+
linkedInstances[envModule->name] = envInstance;
427485

428486
CtorEvalExternalInterface interface(linkedInstances);
429487
try {
@@ -525,6 +583,14 @@ int main(int argc, const char* argv[]) {
525583
WasmCtorEvalOption,
526584
Options::Arguments::One,
527585
[&](Options* o, const std::string& argument) { ctorsString = argument; })
586+
.add("--ignore-external-input",
587+
"-ipi",
588+
"Assumes no env vars are to be read, stdin is empty, etc.",
589+
WasmCtorEvalOption,
590+
Options::Arguments::Zero,
591+
[&](Options* o, const std::string& argument) {
592+
ignoreExternalInput = true;
593+
})
528594
.add_positional("INFILE",
529595
Options::Arguments::One,
530596
[](Options* o, const std::string& argument) {

src/wasm-interpreter.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
#include "wasm-traversal.h"
3636
#include "wasm.h"
3737

38-
#ifdef WASM_INTERPRETER_DEBUG
39-
#include "wasm-printing.h"
40-
#endif
41-
4238
namespace wasm {
4339

4440
struct WasmException {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(module
2+
(import "wasi_snapshot_preview1" "environ_sizes_get" (func $wasi_environ_sizes_get (param i32 i32) (result i32)))
3+
(import "wasi_snapshot_preview1" "environ_get" (func $wasi_environ_get (param i32 i32) (result i32)))
4+
5+
(import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi_args_sizes_get (param i32 i32) (result i32)))
6+
(import "wasi_snapshot_preview1" "args_get" (func $wasi_args_get (param i32 i32) (result i32)))
7+
8+
(import "wasi_snapshot_preview1" "something_else" (func $wasi_something_else (result i32)))
9+
10+
(memory 256 256)
11+
(data (i32.const 0) "aaaaaaaaaaaaaaaaaaaaaaaaaaaa") ;; the final 4 'a's will remain
12+
13+
(func "test1"
14+
;; This is ok to call: when ignoring external input we assume there is no
15+
;; environment to read.
16+
(i32.store
17+
(i32.const 0) ;; the result (0) will be written to address 0
18+
(call $wasi_environ_sizes_get
19+
(i32.const 4) ;; count (0) will be written to address 4
20+
(i32.const 0)
21+
)
22+
)
23+
(i32.store
24+
(i32.const 8) ;; the result (0) will be written to address 8
25+
(call $wasi_environ_get
26+
(i32.const 0)
27+
(i32.const 0)
28+
)
29+
)
30+
)
31+
32+
(func "test2"
33+
;; This is also ok to call: when ignoring external input we assume there are
34+
;; not args passed to main.
35+
(i32.store
36+
(i32.const 12) ;; the result (0) will be written to address 12
37+
(call $wasi_args_sizes_get
38+
(i32.const 16) ;; argc (0) will be written to address 16
39+
(i32.const 0)
40+
)
41+
)
42+
(i32.store
43+
(i32.const 20) ;; the result (0) will be written to address 20
44+
(call $wasi_args_get
45+
(i32.const 0)
46+
(i32.const 0)
47+
)
48+
)
49+
)
50+
51+
(func "test3"
52+
;; This is *not* ok to call, and we will *not* reach the final store after
53+
;; this call. This function will not be evalled and will remain in the
54+
;; output.
55+
(drop
56+
(call $wasi_something_else)
57+
)
58+
(i32.store
59+
(i32.const 24)
60+
(i32.const 100)
61+
)
62+
)
63+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test1,test2,test3
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
(module
2+
(type $none_=>_i32 (func (result i32)))
3+
(type $none_=>_none (func))
4+
(import "wasi_snapshot_preview1" "something_else" (func $wasi_something_else (result i32)))
5+
(memory $0 256 256)
6+
(data (i32.const 24) "aaaa")
7+
(export "test3" (func $2))
8+
(func $2
9+
(drop
10+
(call $wasi_something_else)
11+
)
12+
(i32.store
13+
(i32.const 24)
14+
(i32.const 100)
15+
)
16+
)
17+
)

test/lit/help/wasm-ctor-eval.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
;; CHECK-NEXT: --ctors,-c Comma-separated list of global
2020
;; CHECK-NEXT: constructor functions to evaluate
2121
;; CHECK-NEXT:
22+
;; CHECK-NEXT: --ignore-external-input,-ipi Assumes no env vars are to be read, stdin
23+
;; CHECK-NEXT: is empty, etc.
24+
;; CHECK-NEXT:
2225
;; CHECK-NEXT:
2326
;; CHECK-NEXT: Tool options:
2427
;; CHECK-NEXT: -------------

0 commit comments

Comments
 (0)