Skip to content

Commit b48c24f

Browse files
authored
[ctor-eval] Eval functions with a return value (#4443)
This is necessary for e.g. main() which returns an i32.
1 parent b63fea1 commit b48c24f

File tree

4 files changed

+146
-36
lines changed

4 files changed

+146
-36
lines changed

src/tools/wasm-ctor-eval.cpp

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -489,27 +489,36 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
489489
}
490490
};
491491

492+
struct EvalCtorOutcome {
493+
// Whether we completely evalled the function (that is, we did not fail, and
494+
// we did not only partially eval it).
495+
bool evalledCompletely;
496+
497+
// If the function was evalled completely, and it returns something, that
498+
// value is given here.
499+
Literals results;
500+
501+
static EvalCtorOutcome incomplete() { return {false, Literals()}; }
502+
503+
static EvalCtorOutcome complete(Literals results) { return {true, results}; }
504+
};
505+
492506
// Eval a single ctor function. Returns whether we succeeded to completely
493-
// evaluate the ctor, which means that the caller can proceed to try to eval
494-
// further ctors if there are any.
495-
bool evalCtor(EvallingModuleInstance& instance,
496-
CtorEvalExternalInterface& interface,
497-
Name funcName,
498-
Name exportName) {
507+
// evaluate the ctor (which means that the caller can proceed to try to eval
508+
// further ctors if there are any), and if we did, the results if the function
509+
// returns any.
510+
EvalCtorOutcome evalCtor(EvallingModuleInstance& instance,
511+
CtorEvalExternalInterface& interface,
512+
Name funcName,
513+
Name exportName) {
499514
auto& wasm = instance.wasm;
500515
auto* func = wasm.getFunction(funcName);
501516

502517
// We don't know the values of parameters, so give up if there are any.
503518
// TODO: Maybe use ignoreExternalInput?
504519
if (func->getNumParams() > 0) {
505520
std::cout << " ...stopping due to params\n";
506-
return false;
507-
}
508-
509-
// TODO: Handle a return value by emitting a proper constant.
510-
if (func->getResults() != Type::none) {
511-
std::cout << " ...stopping due to results\n";
512-
return false;
521+
return EvalCtorOutcome::incomplete();
513522
}
514523

515524
// We want to handle the form of the global constructor function in LLVM. That
@@ -523,10 +532,10 @@ bool evalCtor(EvallingModuleInstance& instance,
523532
//
524533
// Some of those ctors may be inlined, however, which would mean that the
525534
// function could have locals, control flow, etc. However, we assume for now
526-
// that it does not have parameters at least (whose values we can't tell),
527-
// or results. And for now we look for a toplevel block and process its
528-
// children one at a time. This allows us to eval some of the $ctor.*
529-
// functions (or their inlined contents) even if not all.
535+
// that it does not have parameters at least (whose values we can't tell).
536+
// And for now we look for a toplevel block and process its children one at a
537+
// time. This allows us to eval some of the $ctor.* functions (or their
538+
// inlined contents) even if not all.
530539
//
531540
// TODO: Support complete partial evalling, that is, evaluate parts of an
532541
// arbitrary function, and not just a sequence in a single toplevel
@@ -546,6 +555,7 @@ bool evalCtor(EvallingModuleInstance& instance,
546555
// an item in the block that we only partially evalled.
547556
EvallingModuleInstance::FunctionScope appliedScope(func, LiteralList());
548557

558+
Literals results;
549559
Index successes = 0;
550560
for (auto* curr : block->list) {
551561
Flow flow;
@@ -568,6 +578,10 @@ bool evalCtor(EvallingModuleInstance& instance,
568578
appliedScope = scope;
569579
successes++;
570580

581+
// Note the values here, if any. If we are exiting the function now then
582+
// these will be returned.
583+
results = flow.values;
584+
571585
if (flow.breaking()) {
572586
// We are returning out of the function (either via a return, or via a
573587
// break to |block|, which has the same outcome. That means we don't
@@ -627,22 +641,27 @@ bool evalCtor(EvallingModuleInstance& instance,
627641

628642
// Return true if we evalled the entire block. Otherwise, even if we evalled
629643
// some of it, the caller must stop trying to eval further things.
630-
return successes == block->list.size();
644+
if (successes == block->list.size()) {
645+
return EvalCtorOutcome::complete(results);
646+
} else {
647+
return EvalCtorOutcome::incomplete();
648+
}
631649
}
632650

633651
// Otherwise, we don't recognize a pattern that allows us to do partial
634652
// evalling. So simply call the entire function at once and see if we can
635653
// optimize that.
654+
Literals results;
636655
try {
637-
instance.callFunction(funcName, LiteralList());
656+
results = instance.callFunction(funcName, LiteralList());
638657
} catch (FailToEvalException& fail) {
639658
std::cout << " ...stopping since could not eval: " << fail.why << "\n";
640-
return false;
659+
return EvalCtorOutcome::incomplete();
641660
}
642661

643662
// Success! Apply the results.
644663
interface.applyToModule();
645-
return true;
664+
return EvalCtorOutcome::complete(results);
646665
}
647666

648667
// Eval all ctors in a module.
@@ -677,12 +696,13 @@ void evalCtors(Module& wasm,
677696
Fatal() << "export not found: " << ctor;
678697
}
679698
auto funcName = ex->value;
680-
if (!evalCtor(instance, interface, funcName, ctor)) {
699+
auto outcome = evalCtor(instance, interface, funcName, ctor);
700+
if (!outcome.evalledCompletely) {
681701
std::cout << " ...stopping\n";
682702
return;
683703
}
684704

685-
// Success! Remove the export, and continue.
705+
// Success! And we can continue to try more.
686706
std::cout << " ...success on " << ctor << ".\n";
687707

688708
// Remove the export if we should.
@@ -695,7 +715,12 @@ void evalCtors(Module& wasm,
695715
auto* func = wasm.getFunction(exp->value);
696716
auto copyName = Names::getValidFunctionName(wasm, func->name);
697717
auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName);
698-
copyFunc->body = Builder(wasm).makeNop();
718+
if (func->getResults() == Type::none) {
719+
copyFunc->body = Builder(wasm).makeNop();
720+
} else {
721+
copyFunc->body =
722+
Builder(wasm).makeConstantExpression(outcome.results);
723+
}
699724
wasm.getExport(exp->name)->value = copyName;
700725
}
701726
}

test/ctor-eval/results.wast

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
(module
2+
(import "import" "import" (func $import))
3+
24
(global $global1 (mut i32) (i32.const 1))
35
(global $global2 (mut i32) (i32.const 2))
46
(global $global3 (mut i32) (i32.const 3))
7+
(global $global4 (mut i32) (i32.const 4))
8+
(global $global5 (mut i32) (i32.const 5))
59

610
(func $test1 (export "test1")
711
;; This function can be evalled. But in this test we keep this export,
@@ -24,14 +28,41 @@
2428
)
2529

2630
(func $test3 (export "test3") (result i32)
27-
;; The presence of a result stops us from evalling this function (at least
28-
;; for now). Not even the global set will be evalled.
31+
;; The global.set can be evalled. We must then keep returning the 42.
2932
(global.set $global3
3033
(i32.const 13)
3134
)
3235
(i32.const 42)
3336
)
3437

38+
(func $test4 (export "test4") (result i32)
39+
;; Similar to the above, but not in a toplevel block format that we can
40+
;; eval one item at a time. We will eval this entire function at once, and
41+
;; we should succeed. After that we should keep returning the constant 55
42+
(if (result i32)
43+
(i32.const 1)
44+
(block (result i32)
45+
(global.set $global4
46+
(i32.const 14)
47+
)
48+
(i32.const 55)
49+
)
50+
(i32.const 99)
51+
)
52+
)
53+
54+
(func $test5 (export "test5") (result i32)
55+
;; Tests partial evalling with a return value at the end. We never reach
56+
;; that return value, but we should eval the global.set.
57+
(global.set $global5
58+
(i32.const 15)
59+
)
60+
61+
(call $import)
62+
63+
(i32.const 100)
64+
)
65+
3566
(func "keepalive" (result i32)
3667
;; Keep everything alive to see the changes.
3768

@@ -43,12 +74,27 @@
4374
(drop
4475
(call $test3)
4576
)
77+
(drop
78+
(call $test4)
79+
)
80+
(drop
81+
(call $test5)
82+
)
4683

4784
;; Keeping these alive should show the changes to the globals (that should
4885
;; contain 11, 12, and 3).
4986
(i32.add
50-
(global.get $global1)
51-
(global.get $global2)
87+
(i32.add
88+
(global.get $global1)
89+
(global.get $global2)
90+
)
91+
(i32.add
92+
(global.get $global3)
93+
(i32.add
94+
(global.get $global4)
95+
(global.get $global5)
96+
)
97+
)
5298
)
5399
)
54100
)

test/ctor-eval/results.wast.ctors

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

test/ctor-eval/results.wast.out

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
(module
2-
(type $none_=>_none (func))
32
(type $none_=>_i32 (func (result i32)))
3+
(type $none_=>_none (func))
4+
(import "import" "import" (func $import))
45
(global $global1 (mut i32) (i32.const 11))
56
(global $global2 (mut i32) (i32.const 12))
6-
(global $global3 (mut i32) (i32.const 3))
7+
(global $global3 (mut i32) (i32.const 13))
8+
(global $global4 (mut i32) (i32.const 14))
9+
(global $global5 (mut i32) (i32.const 15))
710
(export "test1" (func $test1_0))
8-
(export "test3" (func $test3))
9-
(export "keepalive" (func $3))
11+
(export "test3" (func $test3_0))
12+
(export "test5" (func $test5_0))
13+
(export "keepalive" (func $5))
1014
(func $test1
1115
(global.set $global1
1216
(i32.const 11)
@@ -23,18 +27,53 @@
2327
)
2428
(i32.const 42)
2529
)
26-
(func $3 (result i32)
30+
(func $test4 (result i32)
31+
(global.set $global4
32+
(i32.const 14)
33+
)
34+
(i32.const 55)
35+
)
36+
(func $test5 (result i32)
37+
(global.set $global5
38+
(i32.const 15)
39+
)
40+
(call $import)
41+
(i32.const 100)
42+
)
43+
(func $5 (result i32)
2744
(call $test1)
2845
(call $test2)
2946
(drop
3047
(call $test3)
3148
)
49+
(drop
50+
(call $test4)
51+
)
52+
(drop
53+
(call $test5)
54+
)
3255
(i32.add
33-
(global.get $global1)
34-
(global.get $global2)
56+
(i32.add
57+
(global.get $global1)
58+
(global.get $global2)
59+
)
60+
(i32.add
61+
(global.get $global3)
62+
(i32.add
63+
(global.get $global4)
64+
(global.get $global5)
65+
)
66+
)
3567
)
3668
)
3769
(func $test1_0
3870
(nop)
3971
)
72+
(func $test3_0 (result i32)
73+
(i32.const 42)
74+
)
75+
(func $test5_0 (result i32)
76+
(call $import)
77+
(i32.const 100)
78+
)
4079
)

0 commit comments

Comments
 (0)