Skip to content

Commit 29604f1

Browse files
authored
[ctor-eval] Eval functions with params if ignoring external input (#4446)
When ignoring external input, assume params have a value of 0. This makes it possible to eval main(argc, argv) if one is careful and does not actually use those values. This is basically a workaround for main always receiving argc/argv, even if the C code has no args (in that case the compiler emits __original_main for the user's main, and wraps it with a main that adds the args, hence the problem). This is similar to the existing support for handling wasi_args_get when ignoring external input, although it just sets values of zeros for the params. Perhaps it could check for main() specifically and return 1 for argc and a proper buffer for argv somehow, but I think if a program wants to use --ignore-external-input it can avoid actually reading argc/argv.
1 parent 10b1ad7 commit 29604f1

8 files changed

+95
-15
lines changed

src/tools/wasm-ctor-eval.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -510,13 +510,30 @@ EvalCtorOutcome evalCtor(EvallingModuleInstance& instance,
510510
auto& wasm = instance.wasm;
511511
auto* func = wasm.getFunction(funcName);
512512

513-
// We don't know the values of parameters, so give up if there are any.
514-
// TODO: Maybe use ignoreExternalInput?
515-
if (func->getNumParams() > 0) {
513+
// We don't know the values of parameters, so give up if there are any, unless
514+
// we are ignoring them.
515+
if (func->getNumParams() > 0 && !ignoreExternalInput) {
516516
std::cout << " ...stopping due to params\n";
517+
std::cout << RECOMMENDATION "consider --ignore-external-input";
517518
return EvalCtorOutcome();
518519
}
519520

521+
// If there are params, we are ignoring them (or we would have quit earlier);
522+
// set those up with zeros.
523+
// TODO: Have a safer option here, either
524+
// 1. Statically or dynamically stop evalling when a param is actually
525+
// used, or
526+
// 2. Split out --ignore-external-input into separate flags.
527+
LiteralList params;
528+
for (Index i = 0; i < func->getNumParams(); i++) {
529+
auto type = func->getLocalType(i);
530+
if (!LiteralUtils::canMakeZero(type)) {
531+
std::cout << " ...stopping due to non-zeroable param\n";
532+
return EvalCtorOutcome();
533+
}
534+
params.push_back(Literal::makeZero(type));
535+
}
536+
520537
// We want to handle the form of the global constructor function in LLVM. That
521538
// looks like this:
522539
//
@@ -540,7 +557,7 @@ EvalCtorOutcome evalCtor(EvallingModuleInstance& instance,
540557
if (auto* block = func->body->dynCast<Block>()) {
541558
// Go through the items in the block and try to execute them. We do all this
542559
// in a single function scope for all the executions.
543-
EvallingModuleInstance::FunctionScope scope(func, LiteralList());
560+
EvallingModuleInstance::FunctionScope scope(func, params);
544561

545562
EvallingModuleInstance::RuntimeExpressionRunner expressionRunner(
546563
instance, scope, instance.maxDepth);
@@ -549,7 +566,7 @@ EvalCtorOutcome evalCtor(EvallingModuleInstance& instance,
549566
// the same idea as applyToModule() - we must only do it after an entire
550567
// atomic "chunk" has been processed, we do not want partial updates from
551568
// an item in the block that we only partially evalled.
552-
EvallingModuleInstance::FunctionScope appliedScope(func, LiteralList());
569+
EvallingModuleInstance::FunctionScope appliedScope(func, params);
553570

554571
Literals results;
555572
Index successes = 0;
@@ -647,9 +664,10 @@ EvalCtorOutcome evalCtor(EvallingModuleInstance& instance,
647664
// Otherwise, we don't recognize a pattern that allows us to do partial
648665
// evalling. So simply call the entire function at once and see if we can
649666
// optimize that.
667+
650668
Literals results;
651669
try {
652-
results = instance.callFunction(funcName, LiteralList());
670+
results = instance.callFunction(funcName, params);
653671
} catch (FailToEvalException& fail) {
654672
std::cout << " ...stopping since could not eval: " << fail.why << "\n";
655673
return EvalCtorOutcome();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(module
2+
(global $global1 (mut i32) (i32.const 10))
3+
(global $global2 (mut i32) (i32.const 20))
4+
5+
(func "test1" (param $any (ref null any))
6+
;; This is ok to call: when ignoring external input we assume 0 for the
7+
;; parameters, and this parameter is nullable.
8+
(drop
9+
(local.get $any)
10+
)
11+
(global.set $global1
12+
(i32.const 11)
13+
)
14+
)
15+
16+
(func "test2" (param $any (ref any))
17+
;; This is *not* ok to call: when ignoring external input we assume 0 for
18+
;; the parameters, and this parameter is not nullable.
19+
(drop
20+
(local.get $any)
21+
)
22+
(global.set $global2
23+
(i32.const 22)
24+
)
25+
)
26+
27+
(func "keepalive" (result i32)
28+
(i32.add
29+
(global.get $global1)
30+
(global.get $global2)
31+
)
32+
)
33+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test1,test2
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(module
2+
(type $ref|any|_=>_none (func (param (ref any))))
3+
(type $none_=>_i32 (func (result i32)))
4+
(global $global1 (mut i32) (i32.const 11))
5+
(global $global2 (mut i32) (i32.const 20))
6+
(export "test2" (func $1))
7+
(export "keepalive" (func $2))
8+
(func $1 (param $any (ref any))
9+
(global.set $global2
10+
(i32.const 22)
11+
)
12+
)
13+
(func $2 (result i32)
14+
(i32.add
15+
(global.get $global1)
16+
(global.get $global2)
17+
)
18+
)
19+
)

test/ctor-eval/ignore-external-input.wast

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
(import "wasi_snapshot_preview1" "something_else" (func $wasi_something_else (result i32)))
99

1010
(memory 256 256)
11-
(data (i32.const 0) "aaaaaaaaaaaaaaaaaaaaaaaaaaaa") ;; the final 4 'a's will remain
11+
(data (i32.const 0) "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") ;; the final 4 'a's will remain
1212

1313
(func "test1"
1414
;; This is ok to call: when ignoring external input we assume there is no
@@ -48,6 +48,15 @@
4848
)
4949
)
5050

51+
(func "test2b" (param $x i32)
52+
;; This is also ok to call: when ignoring external input we assume the
53+
;; args are zeros.
54+
(i32.store
55+
(i32.const 24) ;; the result (0) will be written to address 24
56+
(local.get $x)
57+
)
58+
)
59+
5160
(func "test3"
5261
;; This is *not* ok to call, and we will *not* reach the final store after
5362
;; this call. This function will not be evalled and will remain in the
@@ -56,7 +65,7 @@
5665
(call $wasi_something_else)
5766
)
5867
(i32.store
59-
(i32.const 24)
68+
(i32.const 28)
6069
(i32.const 100)
6170
)
6271
)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
test1,test2,test3
1+
test1,test2,test2b,test3

test/ctor-eval/ignore-external-input.wast.out

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
(type $none_=>_none (func))
44
(import "wasi_snapshot_preview1" "something_else" (func $wasi_something_else (result i32)))
55
(memory $0 256 256)
6-
(data (i32.const 24) "aaaa")
7-
(export "test3" (func $2))
8-
(func $2
6+
(data (i32.const 28) "aaaa")
7+
(export "test3" (func $3))
8+
(func $3
99
(drop
1010
(call $wasi_something_else)
1111
)
1212
(i32.store
13-
(i32.const 24)
13+
(i32.const 28)
1414
(i32.const 100)
1515
)
1616
)

test/ctor-eval/params.wast

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(module
22
(func "test1" (param $x i32)
3-
;; The presence of params stops us from evalling this function (at least
4-
;; for now).
3+
;; The presence of params stops us from evalling this function, at least
4+
;; not with --ignore-external-input (see ignore-external-input.wast)
55
(nop)
66
)
77
)

0 commit comments

Comments
 (0)