diff --git a/src/ir/intrinsics.cpp b/src/ir/intrinsics.cpp index b485aa70a64..b2a5e1ff11a 100644 --- a/src/ir/intrinsics.cpp +++ b/src/ir/intrinsics.cpp @@ -15,6 +15,7 @@ */ #include "ir/intrinsics.h" +#include "ir/find_all.h" #include "wasm-builder.h" namespace wasm { @@ -100,4 +101,29 @@ std::vector Intrinsics::getConfigureAllFunctions(Call* call) { return ret; } +std::vector Intrinsics::getConfigureAllFunctions() { + // ConfigureAll in a start function makes its functions callable. + if (module.start) { + auto* start = module.getFunction(module.start); + if (!start->imported()) { + FindAll calls(start->body); + // Look for the (single) configureAll. + Call* configureAll = nullptr; + for (auto* call : calls.list) { + if (isConfigureAll(call)) { + if (configureAll) { + Fatal() << "Multiple configureAlls"; + } else { + configureAll = call; + } + } + } + if (configureAll) { + return getConfigureAllFunctions(configureAll); + } + } + } + return {}; +} + } // namespace wasm diff --git a/src/ir/intrinsics.h b/src/ir/intrinsics.h index c06eb832677..43e080b806b 100644 --- a/src/ir/intrinsics.h +++ b/src/ir/intrinsics.h @@ -109,6 +109,8 @@ class Intrinsics { // // where the segment $seg is of size N. std::vector getConfigureAllFunctions(Call* call); + // As above, but looks through the module to find the configureAll. + std::vector getConfigureAllFunctions(); }; } // namespace wasm diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 0dd3342f0d8..5abbbecc01d 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -15,7 +15,6 @@ */ #include "module-utils.h" -#include "ir/find_all.h" #include "ir/intrinsics.h" #include "ir/manipulation.h" #include "ir/metadata.h" @@ -693,26 +692,6 @@ std::vector getPublicHeapTypes(Module& wasm) { WASM_UNREACHABLE("unexpected export kind"); } - // ConfigureAll in a start function makes its functions callable. They are - // only signature-called, so the heap type does not need to be public - nor - // types referred to - but for now we mark them as public to avoid breakage in - // several passes. - // TODO Specific fixes in those passes could replace this, and allow better - // optimization. - if (wasm.start) { - auto* start = wasm.getFunction(wasm.start); - if (!start->imported()) { - Intrinsics intrinsics(wasm); - for (auto* call : FindAll(start->body).list) { - if (intrinsics.isConfigureAll(call)) { - for (auto func : intrinsics.getConfigureAllFunctions(call)) { - notePublic(wasm.getFunction(func)->type.getHeapType()); - } - } - } - } - } - // Ignorable public types are public. for (auto type : getIgnorablePublicTypes()) { notePublic(type); diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index 73551f3c729..d29cfb893c7 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -243,7 +243,8 @@ struct AbstractTypeRefining : public Pass { for (auto type : subTypes.types) { if (!type.isStruct()) { - // TODO: support arrays and funcs + // TODO: Support arrays and functions (for functions we will need to + // handle configureAll). continue; } diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp index 2330720b622..a867dffa255 100644 --- a/src/passes/SignaturePruning.cpp +++ b/src/passes/SignaturePruning.cpp @@ -176,6 +176,13 @@ struct SignaturePruning : public Pass { allInfo[tag->type].optimizable = false; } + // configureAll functions are signature-called, and must also not be + // modified. + for (auto func : Intrinsics(*module).getConfigureAllFunctions()) { + allInfo[module->getFunction(func)->type.getHeapType()].optimizable = + false; + } + // A type must have the same number of parameters and results as its // supertypes and subtypes, so we only attempt to modify types without // supertypes or subtypes. diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index b3884fa7046..86c8cac8827 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -154,6 +154,12 @@ struct SignatureRefining : public Pass { } } + // configureAll functions are signature-called, and must also not be + // modified. + for (auto func : Intrinsics(*module).getConfigureAllFunctions()) { + allInfo[module->getFunction(func)->type.getHeapType()].canModify = false; + } + // Also skip modifying types used in tags, even private tags, since we don't // analyze exception handling or stack switching instructions. TODO: Analyze // and optimize exception handling and stack switching instructions. diff --git a/src/passes/TypeFinalizing.cpp b/src/passes/TypeFinalizing.cpp index ebf04318af3..083c2a4ab44 100644 --- a/src/passes/TypeFinalizing.cpp +++ b/src/passes/TypeFinalizing.cpp @@ -50,6 +50,8 @@ struct TypeFinalizing : public Pass { subTypes = SubTypes(*module); } + // Note we don't need to worry about signature-called functions here + // (configureAll) because such calls don't care about finality. auto privateTypes = ModuleUtils::getPrivateHeapTypes(*module); for (auto type : privateTypes) { // If we are finalizing types then we can only do that to leaf types. If diff --git a/test/lit/passes/signature-pruning-configureAll.wast b/test/lit/passes/signature-pruning-configureAll.wast new file mode 100644 index 00000000000..ceaadf25938 --- /dev/null +++ b/test/lit/passes/signature-pruning-configureAll.wast @@ -0,0 +1,117 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --signature-pruning --closed-world -all -S -o - | filecheck %s + +;; Test that configureAll is respected: referred functions are not pruned. + +(module + ;; CHECK: (type $externs (array (mut externref))) + (type $externs (array (mut externref))) + + ;; CHECK: (type $funcs (array (mut funcref))) + (type $funcs (array (mut funcref))) + + ;; CHECK: (type $bytes (array (mut i8))) + (type $bytes (array (mut i8))) + + + ;; CHECK: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) + (type $configureAll (func (param (ref null $externs)) (param (ref null $funcs)) (param (ref null $bytes)) (param externref))) + + (type $struct (struct)) + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $any-2 (func)) + + ;; CHECK: (type $any-1 (func (param anyref))) + (type $any-1 (func (param anyref))) + + ;; use brands to allow $any-1/2 to be optimized separately. + (type $brand1 (struct)) + ) + + (rec + (type $any-2 (func (param anyref))) + + (type $brand2 (struct)) + (type $brand3 (struct)) + ) + + ;; CHECK: (type $6 (func (result i32))) + + ;; CHECK: (type $7 (func)) + + ;; CHECK: (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll) (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) + (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll))) + + + ;; CHECK: (data $bytes "12345678") + + ;; CHECK: (elem $externs externref (item (ref.null noextern))) + (elem $externs externref + (ref.null extern) + ) + + ;; CHECK: (elem $funcs func $foo $bar) + (elem $funcs funcref + (ref.func $foo) + (ref.func $bar) + ) + + (data $bytes "12345678") + + ;; CHECK: (start $start) + (start $start) + + ;; CHECK: (func $start (type $7) + ;; CHECK-NEXT: (call $configureAll + ;; CHECK-NEXT: (array.new_elem $externs $externs + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new_elem $funcs $funcs + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new_data $bytes $bytes + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $start + (call $configureAll + (array.new_elem $externs $externs + (i32.const 0) (i32.const 1)) + (array.new_elem $funcs $funcs + (i32.const 0) (i32.const 2)) + (array.new_data $bytes $bytes + (i32.const 0) (i32.const 8)) + (ref.null extern) + ) + ) + + ;; CHECK: (func $foo (type $6) (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $foo (result i32) + ;; Nothing to do here anyhow, but do not error. + (i32.const 42) + ) + + ;; CHECK: (func $bar (type $any-1) (param $x anyref) + ;; CHECK-NEXT: ) + (func $bar (type $any-1) (param $x anyref) + ;; The param is unused, but will not be pruned (turned into a local) due to + ;; configureAll. + ) + + ;; CHECK: (func $unconfigured (type $any-2) + ;; CHECK-NEXT: (local $0 anyref) + ;; CHECK-NEXT: ) + (func $unconfigured (type $any-2) (param $x anyref) + ;; This is not referred to by configureAll, and can be pruned. + ) +) diff --git a/test/lit/passes/signature-refining-configureAll.wast b/test/lit/passes/signature-refining-configureAll.wast index 48ca0dfffd9..4a1b7f2612c 100644 --- a/test/lit/passes/signature-refining-configureAll.wast +++ b/test/lit/passes/signature-refining-configureAll.wast @@ -24,7 +24,11 @@ ;; CHECK: (type $struct (struct)) - ;; CHECK: (type $5 (func)) + ;; CHECK: (type $ret-any-1 (func (result anyref))) + + ;; CHECK: (type $6 (func (result i32))) + + ;; CHECK: (type $7 (func)) ;; CHECK: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) ;; OPEN_WORLD: (rec @@ -32,7 +36,11 @@ ;; OPEN_WORLD: (type $struct (struct)) - ;; OPEN_WORLD: (type $5 (func)) + ;; OPEN_WORLD: (type $ret-any-1 (func (result anyref))) + + ;; OPEN_WORLD: (type $6 (func (result i32))) + + ;; OPEN_WORLD: (type $7 (func)) ;; OPEN_WORLD: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) (type $configureAll (func (param (ref null $externs)) (param (ref null $funcs)) (param (ref null $bytes)) (param externref))) @@ -40,19 +48,9 @@ (type $struct (struct)) (rec - ;; CHECK: (type $7 (func (result i32))) - - ;; CHECK: (rec - ;; CHECK-NEXT: (type $ret-any-1 (func (result anyref))) - ;; OPEN_WORLD: (type $7 (func (result i32))) - - ;; OPEN_WORLD: (rec - ;; OPEN_WORLD-NEXT: (type $ret-any-1 (func (result anyref))) (type $ret-any-1 (func (result anyref))) ;; use brands to allow $ret-any-1/2 to be optimized separately. - ;; CHECK: (type $brand1 (struct)) - ;; OPEN_WORLD: (type $brand1 (struct)) (type $brand1 (struct)) ) @@ -90,7 +88,7 @@ ;; OPEN_WORLD: (start $start) (start $start) - ;; CHECK: (func $start (type $5) + ;; CHECK: (func $start (type $7) ;; CHECK-NEXT: (call $configureAll ;; CHECK-NEXT: (array.new_elem $externs $externs ;; CHECK-NEXT: (i32.const 0) @@ -107,7 +105,7 @@ ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $start (type $5) + ;; OPEN_WORLD: (func $start (type $7) ;; OPEN_WORLD-NEXT: (call $configureAll ;; OPEN_WORLD-NEXT: (array.new_elem $externs $externs ;; OPEN_WORLD-NEXT: (i32.const 0) @@ -136,10 +134,10 @@ ) ) - ;; CHECK: (func $foo (type $7) (result i32) + ;; CHECK: (func $foo (type $6) (result i32) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $foo (type $7) (result i32) + ;; OPEN_WORLD: (func $foo (type $6) (result i32) ;; OPEN_WORLD-NEXT: (i32.const 42) ;; OPEN_WORLD-NEXT: ) (func $foo (result i32)