Skip to content
Merged
13 changes: 13 additions & 0 deletions src/tools/wasm-ctor-eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,22 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
linkedInstances.swap(linkedInstances_);
}

bool firstApplication = true;

// Called when we want to apply the current state of execution to the Module.
// Until this is called the Module is never changed.
void applyToModule() {
if (firstApplication) {
// The first time we apply things to the module, we can remove the start
// function: we evalled it successfully, if we got to here (and we must
// not execute it again later, which would mean it runs twice). We do not
// do this after the first application because we start to build up a new
// start function with the things we need, unrelated to the original one
// (see addStartFixup).
wasm->start = Name();
firstApplication = false;
}

clearApplyState();

// If nothing was ever written to memories then there is nothing to update.
Expand Down
33 changes: 17 additions & 16 deletions test/lit/ctor-eval/gc-cycle.wast
Original file line number Diff line number Diff line change
Expand Up @@ -1158,7 +1158,9 @@
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(module
;; The start function already exists here. We must prepend to it.
;; The start function already exists here. We must *not* prepend to it: it gets
;; evalled away too (we execute it before the first ctor, and we should not
;; eval those contents twice).

;; CHECK: (type $A (struct (field (mut (ref null $A))) (field i32)))
(type $A (struct (field (mut (ref null $A))) (field i32)))
Expand All @@ -1178,11 +1180,6 @@
;; CHECK: (global $b (mut (ref null $A)) (ref.null none))
(global $b (mut (ref null $A)) (ref.null $A))

;; CHECK: (export "test" (func $test_3))

;; CHECK: (export "keepalive" (func $keepalive))

;; CHECK: (start $start)
(start $start)

(func $test (export "test")
Expand All @@ -1201,6 +1198,12 @@
)
)

;; CHECK: (export "test" (func $test_4))

;; CHECK: (export "keepalive" (func $keepalive))

;; CHECK: (start $start_3)

;; CHECK: (func $keepalive (type $2) (result i32)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (struct.get $A 1
Expand All @@ -1222,23 +1225,21 @@
)
)

;; CHECK: (func $start (type $1)
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (global.get $ctor-eval$global_4)
;; CHECK-NEXT: (global.get $ctor-eval$global_4)
;; CHECK-NEXT: )
;; CHECK-NEXT: (global.set $b
;; CHECK-NEXT: (global.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $start
(global.set $b
(global.get $a)
)
)
)

;; CHECK: (func $test_3 (type $1)
;; CHECK: (func $start_3 (type $1)
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (global.get $ctor-eval$global_4)
;; CHECK-NEXT: (global.get $ctor-eval$global_4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )

;; CHECK: (func $test_4 (type $1)
;; CHECK-NEXT: (local $a (ref $A))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
Expand Down
65 changes: 65 additions & 0 deletions test/lit/ctor-eval/start-bad-2.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-ctor-eval --ctors=test --kept-exports=test --quiet -all -S -o - | filecheck %s

;; We fail to eval away test (due to infinite recursion). As a result, we do
;; not update either global - not the one it modifies or even the one that the
;; start function modifies, and the start function remains as the start.
;; TODO: We could perhaps eval away the start in such cases, even when nothing
;; else gets optimized.

(module
;; CHECK: (type $0 (func))

;; CHECK: (type $1 (func (result i32)))

;; CHECK: (global $global1 (mut i32) (i32.const 0))
(global $global1 (mut i32) (i32.const 0))

;; CHECK: (global $global2 (mut i32) (i32.const 0))
(global $global2 (mut i32) (i32.const 0))

;; CHECK: (export "test" (func $test))

;; CHECK: (export "keepalive" (func $keepalive))

;; CHECK: (start $start)
(start $start)

;; CHECK: (func $start (type $0)
;; CHECK-NEXT: (global.set $global2
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $start
(global.set $global2
(i32.const 42)
)
)

;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (call $test)
;; CHECK-NEXT: (global.set $global1
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (export "test")
(call $test)
(global.set $global1
(i32.const 1337)
)
)

;; CHECK: (func $keepalive (type $1) (result i32)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (global.get $global1)
;; CHECK-NEXT: (global.get $global2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $keepalive (export "keepalive") (result i32)
;; Keep the globals alive to show changes.
(i32.add
(global.get $global1)
(global.get $global2)
)
)
)
62 changes: 62 additions & 0 deletions test/lit/ctor-eval/start-bad.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-ctor-eval --ctors=test --kept-exports=test --quiet -all -S -o - | filecheck %s

;; The start function traps here, so we cannot eval anything. None of the
;; globals should change.

(module
;; CHECK: (type $0 (func))

;; CHECK: (type $1 (func (result i32)))

;; CHECK: (global $global1 (mut i32) (i32.const 0))
(global $global1 (mut i32) (i32.const 0))

;; CHECK: (global $global2 (mut i32) (i32.const 0))
(global $global2 (mut i32) (i32.const 0))

;; CHECK: (export "test" (func $test))

;; CHECK: (export "keepalive" (func $keepalive))

;; CHECK: (start $start)
(start $start)

;; CHECK: (func $start (type $0)
;; CHECK-NEXT: (global.set $global2
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $start
(global.set $global2
(i32.const 42)
)
(unreachable)
)

;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (global.set $global1
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (export "test")
(global.set $global1
(i32.const 1337)
)
)

;; CHECK: (func $keepalive (type $1) (result i32)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (global.get $global1)
;; CHECK-NEXT: (global.get $global2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $keepalive (export "keepalive") (result i32)
;; Keep the globals alive to show changes.
(i32.add
(global.get $global1)
(global.get $global2)
)
)
)
89 changes: 89 additions & 0 deletions test/lit/ctor-eval/start-partrun.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-ctor-eval --ctors=ok,trap,s --kept-exports=s,trap --quiet -all -S -o - | filecheck %s

;; Similar to start-rerun.wast, but rather than $s executing twice, the second
;; time fails to eval, so only the usage in the start function ends up baked in.
;; Specifically, the start function evals fine, as does $ok, but $trap stops us
;; before we get to $s. The start function's increment of the global will leave
;; it as 1, and $ok adds 1000, so it ends up at 1001 but not 1002.

(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $A (sub (shared (struct (field (mut (ref null $B)))))))
(type $A (sub (shared (struct (field (mut (ref null $B)))))))
;; CHECK: (type $B (sub (shared (struct (field (mut (ref null (shared any))))))))
(type $B (sub (shared (struct (field (mut (ref null (shared any))))))))
)

(global $global (ref $A) (struct.new $A
(struct.new_default $B)
))

;; A counter to show how $s executes twice (each time adding 1), and $t once
;; (adding 1000).
;; CHECK: (type $2 (func))

;; CHECK: (global $ctor-eval$global (ref (exact $A)) (struct.new $A
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: ))

;; CHECK: (global $ctor-eval$global_3 (ref (exact $B)) (struct.new $B
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: ))

;; CHECK: (global $counter (mut i32) (i32.const 1001))
(global $counter (mut i32) (i32.const 0))

;; CHECK: (export "s" (func $s))
(export "s" (func $s))

(export "ok" (func $ok))

;; CHECK: (export "trap" (func $trap))
(export "trap" (func $trap))

(start $s)

;; CHECK: (start $start)

;; CHECK: (func $s (type $2)
;; CHECK-NEXT: (global.set $counter
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (global.get $counter)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $s
(global.set $counter
(i32.add
(global.get $counter)
(i32.const 1)
)
)
)

(func $ok
(global.set $counter
(i32.add
(global.get $counter)
(i32.const 1000)
)
)
)

;; CHECK: (func $trap (type $2)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $trap
(unreachable)
)
)

;; CHECK: (func $start (type $2)
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (global.get $ctor-eval$global)
;; CHECK-NEXT: (global.get $ctor-eval$global_3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
Loading
Loading