Skip to content

Commit cc36ffd

Browse files
authored
[ctor-eval] Add an option to keep some exports (#4441)
By default wasm-ctor-eval removes exports that it manages to completely eval (if it just partially evals then the export remains, but points to a function with partially-evalled contents). However, in some cases we do want to keep the export around even so, for example during fuzzing (as the fuzzer wants to call the same exports before and after wasm-ctor-eval runs) and also if there is an ABI we need to preserve (like if we manage to eval all of main()), or if the function returns a value (which we don't support yet, but this is a PR to prepare for that). Specifically, there is now a new option: --kept-exports foo,bar That is a list of exports to keep around. Note that when we keep around an export after evalling the ctor we make the export point to a new function. That new function just contains a nop, so that nothing happens when it is called. But the original function is kept around as it may have other callers, who we do not want to modify.
1 parent 1e7e248 commit cc36ffd

File tree

7 files changed

+132
-20
lines changed

7 files changed

+132
-20
lines changed

auto_update_tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ def update_ctor_eval_tests():
8989
cmd = shared.WASM_CTOR_EVAL + [t, '-all', '-o', 'a.wast', '-S', '--ctors', ctors]
9090
if 'ignore-external-input' in t:
9191
cmd += ['--ignore-external-input']
92+
if 'results' in t:
93+
cmd += ['--kept-exports', 'test1,test3']
9294
support.run_command(cmd)
9395
actual = open('a.wast').read()
9496
out = t + '.out'

check.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ def run_ctor_eval_tests():
118118
cmd = shared.WASM_CTOR_EVAL + [t, '-all', '-o', 'a.wat', '-S', '--ctors', ctors]
119119
if 'ignore-external-input' in t:
120120
cmd += ['--ignore-external-input']
121+
if 'results' in t:
122+
cmd += ['--kept-exports', 'test1,test3']
121123
support.run_command(cmd)
122124
actual = open('a.wat').read()
123125
out = t + '.out'

src/tools/wasm-ctor-eval.cpp

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "pass.h"
3434
#include "support/colors.h"
3535
#include "support/file.h"
36+
#include "support/string.h"
3637
#include "tool-options.h"
3738
#include "wasm-builder.h"
3839
#include "wasm-interpreter.h"
@@ -645,7 +646,12 @@ bool evalCtor(EvallingModuleInstance& instance,
645646
}
646647

647648
// Eval all ctors in a module.
648-
void evalCtors(Module& wasm, std::vector<std::string> ctors) {
649+
void evalCtors(Module& wasm,
650+
std::vector<std::string>& ctors,
651+
std::vector<std::string>& keptExports) {
652+
std::unordered_set<std::string> keptExportsSet(keptExports.begin(),
653+
keptExports.end());
654+
649655
std::map<Name, std::shared_ptr<EvallingModuleInstance>> linkedInstances;
650656

651657
// build and link the env module
@@ -683,7 +689,20 @@ void evalCtors(Module& wasm, std::vector<std::string> ctors) {
683689

684690
// Success! Remove the export, and continue.
685691
std::cout << " ...success on " << ctor << ".\n";
686-
wasm.removeExport(ctor);
692+
693+
// Remove the export if we should.
694+
auto* exp = wasm.getExport(ctor);
695+
if (!keptExportsSet.count(ctor)) {
696+
wasm.removeExport(exp->name);
697+
} else {
698+
// We are keeping around the export, which should now refer to an
699+
// empty function since calling the export should do nothing.
700+
auto* func = wasm.getFunction(exp->value);
701+
auto copyName = Names::getValidFunctionName(wasm, func->name);
702+
auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName);
703+
copyFunc->body = Builder(wasm).makeNop();
704+
wasm.getExport(exp->name)->value = copyName;
705+
}
687706
}
688707
} catch (FailToEvalException& fail) {
689708
// that's it, we failed to even create the instance
@@ -704,7 +723,8 @@ int main(int argc, const char* argv[]) {
704723
std::vector<std::string> passes;
705724
bool emitBinary = true;
706725
bool debugInfo = false;
707-
std::string ctorsString;
726+
String::Split ctors;
727+
String::Split keptExports;
708728

709729
const std::string WasmCtorEvalOption = "wasm-ctor-eval options";
710730

@@ -732,13 +752,24 @@ int main(int argc, const char* argv[]) {
732752
WasmCtorEvalOption,
733753
Options::Arguments::Zero,
734754
[&](Options* o, const std::string& arguments) { debugInfo = true; })
755+
.add("--ctors",
756+
"-c",
757+
"Comma-separated list of global constructor functions to evaluate",
758+
WasmCtorEvalOption,
759+
Options::Arguments::One,
760+
[&](Options* o, const std::string& argument) {
761+
ctors = String::Split(argument, ",");
762+
})
735763
.add(
736-
"--ctors",
737-
"-c",
738-
"Comma-separated list of global constructor functions to evaluate",
764+
"--kept-exports",
765+
"-ke",
766+
"Comma-separated list of ctors whose exports we keep around even if we "
767+
"eval those ctors",
739768
WasmCtorEvalOption,
740769
Options::Arguments::One,
741-
[&](Options* o, const std::string& argument) { ctorsString = argument; })
770+
[&](Options* o, const std::string& argument) {
771+
keptExports = String::Split(argument, ",");
772+
})
742773
.add("--ignore-external-input",
743774
"-ipi",
744775
"Assumes no env vars are to be read, stdin is empty, etc.",
@@ -777,14 +808,7 @@ int main(int argc, const char* argv[]) {
777808
Fatal() << "error in validating input";
778809
}
779810

780-
// get list of ctors, and eval them
781-
std::vector<std::string> ctors;
782-
std::istringstream stream(ctorsString);
783-
std::string temp;
784-
while (std::getline(stream, temp, ',')) {
785-
ctors.push_back(temp);
786-
}
787-
evalCtors(wasm, ctors);
811+
evalCtors(wasm, ctors, keptExports);
788812

789813
// Do some useful optimizations after the evalling
790814
{

test/ctor-eval/results.wast

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,54 @@
11
(module
2-
(func "test1" (result i32)
2+
(global $global1 (mut i32) (i32.const 1))
3+
(global $global2 (mut i32) (i32.const 2))
4+
(global $global3 (mut i32) (i32.const 3))
5+
6+
(func $test1 (export "test1")
7+
;; This function can be evalled. But in this test we keep this export,
8+
;; so we should still see an export, but the export should do nothing since
9+
;; the code has already run.
10+
;;
11+
;; In comparison, test3 below, with a result, will still contain a
12+
;; (constant) result in the remaining export once we can handle results.
13+
14+
(global.set $global1
15+
(i32.const 11)
16+
)
17+
)
18+
19+
(func $test2 (export "test2")
20+
;; As the above function, but the export is *not* kept.
21+
(global.set $global2
22+
(i32.const 12)
23+
)
24+
)
25+
26+
(func $test3 (export "test3") (result i32)
327
;; The presence of a result stops us from evalling this function (at least
4-
;; for now).
28+
;; for now). Not even the global set will be evalled.
29+
(global.set $global3
30+
(i32.const 13)
31+
)
532
(i32.const 42)
633
)
34+
35+
(func "keepalive" (result i32)
36+
;; Keep everything alive to see the changes.
37+
38+
;; These should call the original $test1, not the one that is nopped out
39+
;; after evalling.
40+
(call $test1)
41+
(call $test2)
42+
43+
(drop
44+
(call $test3)
45+
)
46+
47+
;; Keeping these alive should show the changes to the globals (that should
48+
;; contain 11, 12, and 3).
49+
(i32.add
50+
(global.get $global1)
51+
(global.get $global2)
52+
)
53+
)
754
)

test/ctor-eval/results.wast.ctors

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
test1
1+
test1,test2,test3

test/ctor-eval/results.wast.out

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,40 @@
11
(module
2+
(type $none_=>_none (func))
23
(type $none_=>_i32 (func (result i32)))
3-
(export "test1" (func $0))
4-
(func $0 (result i32)
4+
(global $global1 (mut i32) (i32.const 11))
5+
(global $global2 (mut i32) (i32.const 12))
6+
(global $global3 (mut i32) (i32.const 3))
7+
(export "test1" (func $test1_0))
8+
(export "test3" (func $test3))
9+
(export "keepalive" (func $3))
10+
(func $test1
11+
(global.set $global1
12+
(i32.const 11)
13+
)
14+
)
15+
(func $test2
16+
(global.set $global2
17+
(i32.const 12)
18+
)
19+
)
20+
(func $test3 (result i32)
21+
(global.set $global3
22+
(i32.const 13)
23+
)
524
(i32.const 42)
625
)
26+
(func $3 (result i32)
27+
(call $test1)
28+
(call $test2)
29+
(drop
30+
(call $test3)
31+
)
32+
(i32.add
33+
(global.get $global1)
34+
(global.get $global2)
35+
)
36+
)
37+
(func $test1_0
38+
(nop)
39+
)
740
)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
;; CHECK-NEXT: --ctors,-c Comma-separated list of global
2020
;; CHECK-NEXT: constructor functions to evaluate
2121
;; CHECK-NEXT:
22+
;; CHECK-NEXT: --kept-exports,-ke Comma-separated list of ctors whose
23+
;; CHECK-NEXT: exports we keep around even if we eval
24+
;; CHECK-NEXT: those ctors
25+
;; CHECK-NEXT:
2226
;; CHECK-NEXT: --ignore-external-input,-ipi Assumes no env vars are to be read, stdin
2327
;; CHECK-NEXT: is empty, etc.
2428
;; CHECK-NEXT:

0 commit comments

Comments
 (0)