From a3b88c8fca8b02efcb5614be1f977c88f258d49a Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Fri, 5 May 2023 09:12:19 +0200 Subject: [PATCH 01/10] Simplify top-level Makefile. --- Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 01eecae3a..8a452f136 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,10 @@ all: build .PHONY: build doc build: - @if [ -e build/Makefile ]; then $(MAKE) -C build; else true; fi - @if [ -e build/build.ninja ]; then ninja -C build; else true; fi + cmake --build build/ install: - @if [ -e build/Makefile ]; then $(MAKE) -C build install; else true; fi - @if [ -e build/build.ninja ]; then ninja -C build install; else true; fi + cmake --build build/ --target install doc: $(MAKE) -C doc From 84556c3046d7b43102caa9c4ba399fc5a85771be Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Fri, 5 May 2023 12:30:23 +0200 Subject: [PATCH 02/10] Add optimizer pass to remove dead stores. --- hilti/toolchain/src/compiler/optimizer.cc | 165 ++++++++++++++++++ .../hilti.optimization.dead_store/noopt.hlt | 24 +++ .../hilti.optimization.dead_store/opt.hlt | 23 +++ .../hilti.optimization.dead_store/opt.log | 4 + tests/hilti/optimization/dead_store.hlt | 37 ++++ 5 files changed, 253 insertions(+) create mode 100644 tests/Baseline/hilti.optimization.dead_store/noopt.hlt create mode 100644 tests/Baseline/hilti.optimization.dead_store/opt.hlt create mode 100644 tests/Baseline/hilti.optimization.dead_store/opt.log create mode 100644 tests/hilti/optimization/dead_store.hlt diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index dd90c01ac..53d6b6a4b 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -13,16 +13,21 @@ #include #include #include +#include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -1416,6 +1421,165 @@ struct MemberVisitor : OptimizerVisitor, visitor::PreOrder } }; +// Optimizer pass which removes dead store and variable declarations. +struct DeadStoreVisitor : OptimizerVisitor, visitor::PreOrder { + // We use decl identities here instead of their canonical IDs since for + // variables canonical IDs can be ambiguous, #1440. This means that we need + // to track names for logging separately. + std::unordered_map used; + std::unordered_map names; + + void collect(Node& node) override { + _stage = Stage::COLLECT; + + for ( auto i : this->walk(&node) ) + dispatch(i); + + if ( logger().isEnabled(logging::debug::OptimizerCollect) ) { + HILTI_DEBUG(logging::debug::OptimizerCollect, "dead stores:"); + for ( const auto& [id, read] : used ) { + if ( ! read ) { + assert(names.count(id)); + HILTI_DEBUG(logging::debug::OptimizerCollect, util::fmt(" %s", names[id])); + } + } + } + } + + bool prune(Node& node) { + bool any_modification = false; + + while ( true ) { + bool modified = false; + for ( auto i : this->walk(&node) ) { + if ( auto x = dispatch(i) ) + modified = modified || *x; + } + + if ( ! modified ) + break; + + any_modification = true; + } + + return any_modification; + } + + bool prune_decls(Node& node) override { + _stage = OptimizerVisitor::Stage::PRUNE_DECLS; + return prune(node); + } + + bool prune_uses(Node& node) override { + _stage = Stage::PRUNE_USES; + return prune(node); + } + + result_t operator()(const Module& m, position_t p) { + _current_module = &p.node.as(); + return false; + } + + result_t operator()(const expression::Assign& x, position_t p) { + const auto& target_ = x.target(); + const auto& target = target_.tryAs(); + if ( ! target ) + return false; + + const auto& decl = target->declaration(); + if ( ! decl.isA() && ! decl.isA() ) + return false; + + // FIXME(bbannier): do not remove dead stores to struct type variables. + + switch ( _stage ) { + case OptimizerVisitor::Stage::PRUNE_USES: { + // If the id was not used, drop the assignment. + if ( used.count(decl.identity()) && ! used.at(decl.identity()) ) { + HILTI_DEBUG(logging::debug::Optimizer, + util::fmt("removing dead store to '%s' since it is not read (%s)", decl.id(), + x.meta().location())); + replaceNode(p, x.source()); + return true; + } + + return false; + } + case Stage::COLLECT: [[fallthrough]]; + case OptimizerVisitor::Stage::PRUNE_DECLS: break; + } + + return false; + } + + result_t operator()(const expression::ResolvedID& x, position_t p) { + switch ( _stage ) { + case Stage::COLLECT: { + const auto& decl = x.declaration(); + if ( ! decl.isA() && ! decl.isA() ) + return false; + + auto is_assignment_rhs = [&](const Node& n, position_t p) { + // The node is a LHS in an assignment `x = ...`. + if ( auto parent = p.parent().tryAs(); parent && parent->target() == x ) + return false; + + // The ID is not directly used as a LHS in an assignment, but e.g., as a RHS or in some other + // context. We also ignore cases where `x` is in the LHS of a tuple assignment since such + // assignments cannot be removed as easily. + // + // TODO(bbannier): Look into removing tuple assignments if all values on the LHS are dead. + + return true; + }; + + used[decl.identity()] |= is_assignment_rhs(x, p); + names.insert({decl.identity(), decl.canonicalID()}); + + break; + } + case OptimizerVisitor::Stage::PRUNE_USES: + case OptimizerVisitor::Stage::PRUNE_DECLS: break; + } + + return false; + } + + result_t operator()(const statement::Declaration& x, position_t p) { + switch ( _stage ) { + case OptimizerVisitor::Stage::COLLECT: + case OptimizerVisitor::Stage::PRUNE_USES: return false; + case OptimizerVisitor::Stage::PRUNE_DECLS: { + const auto& decl = x.declaration(); + + if ( const auto& local = decl.tryAs() ) { + // Do not attempt to remove declarations of structs since + // they might have additional state attached via hooks. + if ( local->type().isA() ) + break; + + // If the local is initialized do not remove it to still + // trigger potential RHS side effects. + // + // TODO(bbannier): We should be able to remove at + // side-effect free initializations from e.g., literals. + if ( local->init() ) + break; + } + + if ( ! used.count(decl.identity()) || ! used[decl.identity()] ) { + HILTI_DEBUG(logging::debug::Optimizer, util::fmt("removing variable '%s' since it is unused (%s)", + decl.id(), x.meta().location())); + removeNode(p); + return true; + } + } + } + + return false; + } +}; + void Optimizer::run() { util::timing::Collector _("hilti/compiler/optimizer"); @@ -1461,6 +1625,7 @@ void Optimizer::run() { {{"constant_folding", []() { return std::make_unique(); }}, {"functions", []() { return std::make_unique(); }}, {"members", []() { return std::make_unique(); }}, + {"dead_store", []() { return std::make_unique(); }}, {"types", []() { return std::make_unique(); }}}; // If no user-specified passes are given enable all of them. diff --git a/tests/Baseline/hilti.optimization.dead_store/noopt.hlt b/tests/Baseline/hilti.optimization.dead_store/noopt.hlt new file mode 100644 index 000000000..7250ed6c8 --- /dev/null +++ b/tests/Baseline/hilti.optimization.dead_store/noopt.hlt @@ -0,0 +1,24 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +module foo { + +import hilti; + +global uint<8> b; +global uint<64> x1; +global uint<64> x2; +global uint<8> c1 = 0; +global uint<8> c2; + +public function uint<8> fun() { + local uint<8> a; + a = uint8(10); + return uint8(42); +} + +hilti::print(fun(), True); +b = uint8(10); +(x1, x2) = (11, 12); +c2 = c1; +c1 = c2; + +} diff --git a/tests/Baseline/hilti.optimization.dead_store/opt.hlt b/tests/Baseline/hilti.optimization.dead_store/opt.hlt new file mode 100644 index 000000000..93d3fd16f --- /dev/null +++ b/tests/Baseline/hilti.optimization.dead_store/opt.hlt @@ -0,0 +1,23 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +module foo { + +import hilti; + +global uint<8> b; +global uint<64> x1; +global uint<64> x2; +global uint<8> c1 = 0; +global uint<8> c2; + +public function uint<8> fun() { + uint8(10); + return uint8(42); +} + +hilti::print(fun(), True); +uint8(10); +(x1, x2) = (11, 12); +c2 = c1; +c1 = c2; + +} diff --git a/tests/Baseline/hilti.optimization.dead_store/opt.log b/tests/Baseline/hilti.optimization.dead_store/opt.log new file mode 100644 index 000000000..c2eaa2c4f --- /dev/null +++ b/tests/Baseline/hilti.optimization.dead_store/opt.log @@ -0,0 +1,4 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +[debug/optimizer] removing dead store to 'b' since it is not read (<...>/dead_store.hlt:23:1) +[debug/optimizer] removing dead store to 'a' since it is not read (<...>/dead_store.hlt:15:5) +[debug/optimizer] removing variable 'a' since it is unused (<...>/dead_store.hlt:14:5) diff --git a/tests/hilti/optimization/dead_store.hlt b/tests/hilti/optimization/dead_store.hlt new file mode 100644 index 000000000..ed201560a --- /dev/null +++ b/tests/hilti/optimization/dead_store.hlt @@ -0,0 +1,37 @@ +# @TEST-DOC: Checks that dead stores are removed. +# +# @TEST-EXEC: hiltic -pg %INPUT -o noopt.hlt +# @TEST-EXEC: btest-diff noopt.hlt +# +# @TEST-EXEC: HILTI_OPTIMIZER_PASSES=dead_store hiltic -p %INPUT -o opt.hlt -D optimizer >opt.log 2>&1 +# @TEST-EXEC: btest-diff opt.hlt +# @TEST-EXEC: btest-diff opt.log + +module foo { +import hilti; + +public function uint<8> fun() { + local uint<8> a; + a = uint8(10); + + return uint8(42); +} + +hilti::print(fun(), True); + +global uint<8> b; +b = uint8(10); + +# TODO(bbannier): For now tuple assignments are not recognized +# since they cannot be simplified if not all LHSs are dead. +global uint<64> x1; +global uint<64> x2; +(x1, x2) = (11, 12); + +# If an ID appears on the RHS as well the LHS, the RHS is a use. +global uint<8> c1 = 0; +global uint<8> c2; +c2 = c1; +c1 = c2; + +} From 210c11de0e48c81a6e91ac3d5c95e5e9768b02e5 Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Fri, 5 May 2023 16:49:53 +0200 Subject: [PATCH 03/10] Implement short-circuiting of logical OR and AND in optimizer. --- hilti/toolchain/src/compiler/optimizer.cc | 14 ++++++++++++++ tests/Baseline/hilti.optimization.const/noopt.hlt | 10 ++++++++++ tests/Baseline/hilti.optimization.const/opt.hlt | 10 ++++++++++ tests/hilti/optimization/const.hlt | 10 ++++++++++ 4 files changed, 44 insertions(+) diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index 53d6b6a4b..3cc4c53ce 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -858,10 +858,17 @@ struct ConstantFoldingVisitor : OptimizerVisitor, visitor::PreOrder Date: Fri, 5 May 2023 17:13:51 +0200 Subject: [PATCH 04/10] Constant-fold some expressions involving variables. --- hilti/toolchain/src/compiler/optimizer.cc | 24 +++++++++++++++++++ .../Baseline/hilti.optimization.const/opt.hlt | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index 3cc4c53ce..aa0f88b8d 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -869,6 +869,18 @@ struct ConstantFoldingVisitor : OptimizerVisitor, visitor::PreOrder(); + lhs && + (lhs->declaration().isA() || + lhs->declaration().isA()) && + rhs && rhs.value() ) { + replaceNode(p, builder::bool_(true)); + return true; + } } }; @@ -894,6 +906,18 @@ struct ConstantFoldingVisitor : OptimizerVisitor, visitor::PreOrder(); + lhs && + (lhs->declaration().isA() || + lhs->declaration().isA()) && + rhs && ! rhs.value() ) { + replaceNode(p, builder::bool_(false)); + return true; + } } }; diff --git a/tests/Baseline/hilti.optimization.const/opt.hlt b/tests/Baseline/hilti.optimization.const/opt.hlt index cdd763409..ab42c3126 100644 --- a/tests/Baseline/hilti.optimization.const/opt.hlt +++ b/tests/Baseline/hilti.optimization.const/opt.hlt @@ -31,10 +31,10 @@ False; True; 1; 1; -a || True; +True; a || False; a && True; -a && False; +False; True; False || a; True && a; From 06970d4d83fa108512c58b38ee213ece82be71d4 Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Wed, 10 May 2023 14:13:16 +0200 Subject: [PATCH 05/10] Add optimizer pass removing dead code. --- hilti/toolchain/src/compiler/optimizer.cc | 55 +++++++++++++++++++ tests/Baseline/hilti.optimization.const/log | 10 ---- .../hilti.optimization.dead_code/opt.hlt | 4 ++ .../hilti.optimization.unimplemented_hook/log | 2 + .../opt.hlt | 2 - .../log | 15 +++++ .../opt.hlt | 10 ---- .../log | 31 +++++++++++ .../opt.hlt | 13 ----- .../spicy.optimization.unused-functions/log | 25 +++++++++ .../opt.hlt | 15 ----- .../spicy.optimization.unused-types/log | 47 ++++++++++++++++ .../spicy.optimization.unused-types/opt.hlt | 26 --------- tests/Baseline/spicy.rt.debug-trace/.stderr | 3 +- .../output | 2 +- tests/hilti/optimization/const.hlt | 4 +- tests/hilti/optimization/dead_code.hlt | 10 ++++ 17 files changed, 193 insertions(+), 81 deletions(-) create mode 100644 tests/Baseline/hilti.optimization.dead_code/opt.hlt create mode 100644 tests/hilti/optimization/dead_code.hlt diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index aa0f88b8d..d139875a8 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -24,10 +24,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -1618,6 +1621,57 @@ struct DeadStoreVisitor : OptimizerVisitor, visitor::PreOrder { + bool prune_uses(Node& node) override { + bool any_modification = false; + + while ( true ) { + bool modified = false; + + for ( auto i : this->walk(&node) ) { + if ( auto x = dispatch(i) ) + modified = modified || *x; + } + + if ( ! modified ) + break; + + any_modification = true; + } + + return any_modification; + } + + result_t operator()(const Module& m, position_t p) { + _current_module = &p.node.as(); + return false; + } + + result_t operator()(const statement::Expression& x, position_t p) { + if ( auto ctor = x.expression().tryAs(); + ctor && (ctor->isConstant() || ! hasChildWithSideEffects(x)) ) { + HILTI_DEBUG(logging::debug::Optimizer, + util::fmt("removing side-effect free dead code (%s)", x.meta().location())); + removeNode(p); + return true; + } + + return false; + } + + bool hasChildWithSideEffects(const Node& node) const { + auto v = visitor::PreOrder<>(); + + // NOLINTNEXTLINE(readability-use-anyofallof) + for ( const auto& child : v.walk(node) ) { + if ( child.node.isA() || child.node.isA() ) + return true; + } + + return false; + } +}; + void Optimizer::run() { util::timing::Collector _("hilti/compiler/optimizer"); @@ -1663,6 +1717,7 @@ void Optimizer::run() { {{"constant_folding", []() { return std::make_unique(); }}, {"functions", []() { return std::make_unique(); }}, {"members", []() { return std::make_unique(); }}, + {"dead_code", []() { return std::make_unique(); }}, {"dead_store", []() { return std::make_unique(); }}, {"types", []() { return std::make_unique(); }}}; diff --git a/tests/Baseline/hilti.optimization.const/log b/tests/Baseline/hilti.optimization.const/log index 09aae25e6..955edcc03 100644 --- a/tests/Baseline/hilti.optimization.const/log +++ b/tests/Baseline/hilti.optimization.const/log @@ -7,13 +7,3 @@ [debug/optimizer] inlining constant 'Foo::t' [debug/optimizer] inlining constant 'Foo::t' [debug/optimizer] inlining constant 'Foo::t' -[debug/optimizer] removing declaration for unused function hilti::abort -[debug/optimizer] removing declaration for unused function hilti::current_time -[debug/optimizer] removing declaration for unused function hilti::debug -[debug/optimizer] removing declaration for unused function hilti::debugDedent -[debug/optimizer] removing declaration for unused function hilti::debugIndent -[debug/optimizer] removing declaration for unused function hilti::exception_what -[debug/optimizer] removing declaration for unused function hilti::mktime -[debug/optimizer] removing declaration for unused function hilti::printValues -[debug/optimizer] removing declaration for unused function hilti::profiler_start -[debug/optimizer] removing declaration for unused function hilti::profiler_stop diff --git a/tests/Baseline/hilti.optimization.dead_code/opt.hlt b/tests/Baseline/hilti.optimization.dead_code/opt.hlt new file mode 100644 index 000000000..7bdb67860 --- /dev/null +++ b/tests/Baseline/hilti.optimization.dead_code/opt.hlt @@ -0,0 +1,4 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +module foo { + +} diff --git a/tests/Baseline/hilti.optimization.unimplemented_hook/log b/tests/Baseline/hilti.optimization.unimplemented_hook/log index 10ea52fe9..e723ec389 100644 --- a/tests/Baseline/hilti.optimization.unimplemented_hook/log +++ b/tests/Baseline/hilti.optimization.unimplemented_hook/log @@ -6,5 +6,7 @@ [debug/optimizer] removing declaration for unused hook function Foo::global_unimplemented_void [debug/optimizer] removing declaration for unused hook function Foo::global_unimplemented_int64 [debug/optimizer] removing field for unused method Foo::X::unimplemented +[debug/optimizer] removing side-effect free dead code (<...>/unimplemented_hook.hlt:29:1) +[debug/optimizer] removing side-effect free dead code (<...>/unimplemented_hook.hlt:35:1) [debug/optimizer] removing field for unused method Foo::X::unimplemented_void [debug/optimizer] removing field for unused method Foo::X::unimplemented_int64 diff --git a/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt b/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt index 90d108357..8a2af793e 100644 --- a/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt +++ b/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt @@ -18,8 +18,6 @@ method hook void X::implemented() { } global_implemented(); -default(); x.implemented(); -default(); } diff --git a/tests/Baseline/spicy.optimization.default-parser-functions/log b/tests/Baseline/spicy.optimization.default-parser-functions/log index 1f978e85f..7d32ccff4 100644 --- a/tests/Baseline/spicy.optimization.default-parser-functions/log +++ b/tests/Baseline/spicy.optimization.default-parser-functions/log @@ -201,6 +201,21 @@ [debug/optimizer] removing field for unused method foo::__register_foo_P2::::parse1 [debug/optimizer] removing field for unused method foo::__register_foo_P2::::parse2 [debug/optimizer] removing field for unused method foo::__register_foo_P2::::parse3 +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:12:11) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:12:11) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:12:11) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:12:11) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:12:11) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:14:18) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:14:18) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:14:18) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:14:18) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:14:18) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:16:18-21:2) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:16:18-21:2) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:16:18-21:2) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:16:18-21:2) +[debug/optimizer] removing side-effect free dead code (<...>/default-parser-functions.spicy:17:5) [debug/optimizer] removing unused member 'foo::P0::__begin' [debug/optimizer] removing unused member 'foo::P0::__filters' [debug/optimizer] removing unused member 'foo::P0::__offset' diff --git a/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt b/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt index 126d39702..3bb246cb5 100644 --- a/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt +++ b/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt @@ -56,7 +56,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -65,15 +64,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -82,7 +78,6 @@ method method tuple, int<64>, iterator, optional/default-parser-functions.spicy:14:18" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -164,7 +159,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -175,13 +169,11 @@ method method tuple, int<64>, iterator, optional(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -201,7 +193,6 @@ method method tuple, int<64>, iterator, optional uint<8> (*self).__error = __error; - default(); __error = (*self).__error; # "<...>/default-parser-functions.spicy:18:8" @@ -218,7 +209,6 @@ method method tuple, int<64>, iterator, optional(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); diff --git a/tests/Baseline/spicy.optimization.feature_requirements/log b/tests/Baseline/spicy.optimization.feature_requirements/log index 97adeb951..484201bd2 100644 --- a/tests/Baseline/spicy.optimization.feature_requirements/log +++ b/tests/Baseline/spicy.optimization.feature_requirements/log @@ -421,6 +421,37 @@ [debug/optimizer] removing field for unused method foo::__register_foo_X6::::parse1 [debug/optimizer] removing field for unused method foo::__register_foo_X6::::parse2 [debug/optimizer] removing field for unused method foo::__register_foo_X6::::parse3 +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:13:11-15:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:13:11-15:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:13:11-15:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:13:11-15:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:18:11-20:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:18:11-20:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:18:11-20:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:18:11-20:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:23:11) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:23:11) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:23:11) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:23:11) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:23:11) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:26:11-28:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:26:11-28:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:26:11-28:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:26:11-28:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:26:11-28:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:32:11-34:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:32:11-34:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:32:11-34:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:32:11-34:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:32:11-34:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:36:18-40:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:36:18-40:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:36:18-40:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:36:18-40:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:43:11-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:43:11-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:43:11-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/feature_requirements.spicy:43:11-46:2) [debug/optimizer] removing unused member 'foo::X0::__begin' [debug/optimizer] removing unused member 'foo::X0::__filters' [debug/optimizer] removing unused member 'foo::X0::__parser' diff --git a/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt b/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt index e6da506e2..900e8f2a4 100644 --- a/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt +++ b/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt @@ -129,7 +129,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -138,18 +137,15 @@ method method tuple, int<64>, iterator, optional(); spicy_rt::filter_forward_eod(self); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -158,7 +154,6 @@ method method tuple, int<64>, iterator, optional/feature_requirements.spicy:32:11-34:2" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; spicy_rt::filter_forward_eod(self); @@ -267,18 +262,15 @@ method method tuple, int<64>, iterator, optional(); spicy_rt::filter_disconnect(self); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -287,7 +279,6 @@ method method tuple, int<64>, iterator, optional/feature_requirements.spicy:36:18-40:2" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; spicy_rt::filter_disconnect(self); @@ -379,16 +370,13 @@ method method tuple, int<64>, iterator, optional(); (*(*self).data).close(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -397,7 +385,6 @@ method method tuple, int<64>, iterator, optional/feature_requirements.spicy:43:11-46:2" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; (*(*self).data).close(); hilti::debugDedent("spicy"); diff --git a/tests/Baseline/spicy.optimization.unused-functions/log b/tests/Baseline/spicy.optimization.unused-functions/log index cdc5d3257..5ed9e6f8b 100644 --- a/tests/Baseline/spicy.optimization.unused-functions/log +++ b/tests/Baseline/spicy.optimization.unused-functions/log @@ -314,6 +314,31 @@ [debug/optimizer] removing field for unused method foo::__register_foo_D::::parse1 [debug/optimizer] removing field for unused method foo::__register_foo_D::::parse2 [debug/optimizer] removing field for unused method foo::__register_foo_D::::parse3 +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:18:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:18:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:18:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:18:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:18:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:21:17) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:21:17) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:21:17) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:21:17) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:21:17) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:24:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:24:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:24:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:24:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:24:10) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:25:17-27:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:25:17-27:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:25:17-27:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:25:17-27:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:25:17-27:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:30:10-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:30:10-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:30:10-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:30:10-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-functions.spicy:30:10-32:2) [debug/optimizer] removing unused member 'foo::A::__begin' [debug/optimizer] removing unused member 'foo::A::__filters' [debug/optimizer] removing unused member 'foo::A::__offset' diff --git a/tests/Baseline/spicy.optimization.unused-functions/opt.hlt b/tests/Baseline/spicy.optimization.unused-functions/opt.hlt index 5f70b771e..6f6bbbe2a 100644 --- a/tests/Baseline/spicy.optimization.unused-functions/opt.hlt +++ b/tests/Baseline/spicy.optimization.unused-functions/opt.hlt @@ -70,7 +70,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -79,15 +78,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -96,7 +92,6 @@ method method tuple, int<64>, iterator, optional/unused-functions.spicy:21:17" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -172,7 +167,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -181,15 +175,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -198,7 +189,6 @@ method method tuple, int<64>, iterator, optional/unused-functions.spicy:24:10" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -215,7 +205,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -224,15 +213,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -249,7 +235,6 @@ method method tuple, int<64>, iterator, optional (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); diff --git a/tests/Baseline/spicy.optimization.unused-types/log b/tests/Baseline/spicy.optimization.unused-types/log index 0d544376c..252080ad9 100644 --- a/tests/Baseline/spicy.optimization.unused-types/log +++ b/tests/Baseline/spicy.optimization.unused-types/log @@ -554,6 +554,53 @@ [debug/optimizer] removing field for unused method foo::__register_foo_Pub3::::parse1 [debug/optimizer] removing field for unused method foo::__register_foo_Pub3::::parse2 [debug/optimizer] removing field for unused method foo::__register_foo_Pub3::::parse3 +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:13:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:13:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:13:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:13:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:13:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:16:20) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:16:20) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:16:20) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:16:20) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:16:20) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:19:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:19:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:19:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:19:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:19:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:20:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:20:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:20:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:20:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:20:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:21:14-24:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:21:14-24:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:21:14-24:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:21:14-24:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:21:14-24:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:23:5) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:27:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:27:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:27:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:27:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:27:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:28:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:28:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:28:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:28:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:28:14) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:29:20-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:29:20-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:29:20-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:29:20-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:29:20-32:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:31:5) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:43:22-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:43:22-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:43:22-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:43:22-46:2) +[debug/optimizer] removing side-effect free dead code (<...>/unused-types.spicy:43:22-46:2) [debug/optimizer] removing unused member 'foo::Priv10::__begin' [debug/optimizer] removing unused member 'foo::Priv10::__filters' [debug/optimizer] removing unused member 'foo::Priv10::__offset' diff --git a/tests/Baseline/spicy.optimization.unused-types/opt.hlt b/tests/Baseline/spicy.optimization.unused-types/opt.hlt index 039f08d01..f1d3b901e 100644 --- a/tests/Baseline/spicy.optimization.unused-types/opt.hlt +++ b/tests/Baseline/spicy.optimization.unused-types/opt.hlt @@ -119,7 +119,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -128,15 +127,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -145,7 +141,6 @@ method method tuple, int<64>, iterator, optional/unused-types.spicy:16:20" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -230,7 +225,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -239,15 +233,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -256,7 +247,6 @@ method method tuple, int<64>, iterator, optional/unused-types.spicy:27:14" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -273,7 +263,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -282,15 +271,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -299,7 +285,6 @@ method method tuple, int<64>, iterator, optional/unused-types.spicy:28:14" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -316,7 +301,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -325,15 +309,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -357,10 +338,8 @@ method method tuple, int<64>, iterator, optional (*self).__error = __error; - default(); __error = (*self).__error; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); @@ -436,7 +415,6 @@ method method tuple, int<64>, iterator, optional __begin = begin(__cur); (*self).__error = __error; - default(); __error = (*self).__error; local strong_ref filtered = Null; @@ -445,15 +423,12 @@ method method tuple, int<64>, iterator, optional(); (*self).__error = __error; - default(); __error = (*self).__error; throw; } (*self).__error = __error; - default(); __error = (*self).__error; return __result; } @@ -462,7 +437,6 @@ method method tuple, int<64>, iterator, optional/unused-types.spicy:43:22-46:2" local tuple, int<64>, iterator, optional> __result; (*self).__error = __error; - default(); __error = (*self).__error; hilti::debugDedent("spicy"); __result = (__cur, __lah, __lahe, __error); diff --git a/tests/Baseline/spicy.rt.debug-trace/.stderr b/tests/Baseline/spicy.rt.debug-trace/.stderr index c2e57efd7..261a25030 100644 --- a/tests/Baseline/spicy.rt.debug-trace/.stderr +++ b/tests/Baseline/spicy.rt.debug-trace/.stderr @@ -16,7 +16,7 @@ [hilti-trace] : (ncur, lahead, lahead_end, error) = (*unit).__parse_stage1(data, begin(ncur), ncur, True, lahead, lahead_end, error); [hilti-trace] : # "<...>/debug-trace.spicy:8:20-13:2" [hilti-trace] : local tuple, int<64>, iterator, optional> __result; -[hilti-trace] : try { hilti::debug("spicy", "Mini::Test"); hilti::debugIndent("spicy"); local iterator __begin = begin(__cur); (*self).__error = __error; (*self).__on_0x25_init(); __error = (*self).__error; local strong_ref filtered = Null; if ( ! filtered ) __result = (*self).__parse_Mini_Test_stage2(__data, __begin, __cur, __trim, __lah, __lahe, __error); } catch ( hilti::SystemException __except ) { default(); (*self).__error = __error; default(); __error = (*self).__error; throw; } +[hilti-trace] : try { hilti::debug("spicy", "Mini::Test"); hilti::debugIndent("spicy"); local iterator __begin = begin(__cur); (*self).__error = __error; (*self).__on_0x25_init(); __error = (*self).__error; local strong_ref filtered = Null; if ( ! filtered ) __result = (*self).__parse_Mini_Test_stage2(__data, __begin, __cur, __trim, __lah, __lahe, __error); } catch ( hilti::SystemException __except ) { (*self).__error = __error; __error = (*self).__error; throw; } [hilti-trace] : hilti::debug("spicy", "Mini::Test"); [hilti-trace] : hilti::debugIndent("spicy"); [hilti-trace] : local iterator __begin = begin(__cur); @@ -73,7 +73,6 @@ [hilti-trace] : __result = (__cur, __lah, __lahe, __error); [hilti-trace] : return __result; [hilti-trace] : (*self).__error = __error; -[hilti-trace] <...>/debug-trace.spicy:8:20-13:2: default(); [hilti-trace] : __error = (*self).__error; [hilti-trace] : return __result; [hilti-trace] : hilti::debugDedent("spicy-verbose"); diff --git a/tests/Baseline/spicy.types.sink.reassembler.wrong-init-seq/output b/tests/Baseline/spicy.types.sink.reassembler.wrong-init-seq/output index 6e78158b3..97c9b9207 100644 --- a/tests/Baseline/spicy.types.sink.reassembler.wrong-init-seq/output +++ b/tests/Baseline/spicy.types.sink.reassembler.wrong-init-seq/output @@ -1,2 +1,2 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. -[error] terminating with uncaught exception of type spicy::rt::SinkError: sink cannot update initial sequence number after activity has already been seen (<...>/wrong-init-seq.spicy:20:19-38:2) +[error] terminating with uncaught exception of type spicy::rt::SinkError: sink cannot update initial sequence number after activity has already been seen (<...>/wrong-init-seq.spicy:13:39-14:51) diff --git a/tests/hilti/optimization/const.hlt b/tests/hilti/optimization/const.hlt index 755667df7..5920a426e 100644 --- a/tests/hilti/optimization/const.hlt +++ b/tests/hilti/optimization/const.hlt @@ -1,7 +1,7 @@ -# @TEST-EXEC: hiltic %INPUT -p -o noopt.hlt -g +# @TEST-EXEC: HILTI_OPTIMIZER_PASSES=constant_folding hiltic %INPUT -p -o noopt.hlt -g # @TEST-EXEC: btest-diff noopt.hlt # -# @TEST-EXEC: hiltic %INPUT -p -o opt.hlt -D optimizer 2>&1 | sort > log +# @TEST-EXEC: HILTI_OPTIMIZER_PASSES=constant_folding hiltic %INPUT -p -o opt.hlt -D optimizer 2>&1 | sort > log # @TEST-EXEC: btest-diff opt.hlt # @TEST-EXEC: btest-diff log # diff --git a/tests/hilti/optimization/dead_code.hlt b/tests/hilti/optimization/dead_code.hlt new file mode 100644 index 000000000..43385933b --- /dev/null +++ b/tests/hilti/optimization/dead_code.hlt @@ -0,0 +1,10 @@ +# @TEST-EXEC: hiltic -p %INPUT -o opt.hlt +# @TEST-EXEC: btest-diff opt.hlt + +module foo { + +uint8(10); +default(); +True; + +} From 39e67fedd96dd66f62c919ae82f83f6ca682b7cb Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Thu, 25 May 2023 12:09:55 +0200 Subject: [PATCH 06/10] Add setter for Function attributes. --- hilti/toolchain/include/ast/function.h | 1 + 1 file changed, 1 insertion(+) diff --git a/hilti/toolchain/include/ast/function.h b/hilti/toolchain/include/ast/function.h index 5f09e50dc..f87fac4f3 100644 --- a/hilti/toolchain/include/ast/function.h +++ b/hilti/toolchain/include/ast/function.h @@ -64,6 +64,7 @@ class Function : public NodeBase { attributes() == other.attributes() && callingConvention() == other.callingConvention(); } + void setAttributes(const std::optional& attributes) { children()[3] = attributes; } void setBody(const Statement& b) { children()[2] = b; } void setID(const ID& id) { children()[0] = id; } void setFunctionType(const type::Function& ftype) { children()[1] = ftype; } From 68c8fc2666d899bd2311d9625b81cc63d778be15 Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Thu, 11 May 2023 10:57:33 +0200 Subject: [PATCH 07/10] Add optimizer pass detecting pure functions and if possible elide calls. --- hilti/toolchain/src/compiler/optimizer.cc | 374 +++++++++++++++++- .../hilti.optimization.dead_code/opt.hlt | 18 + .../hilti.optimization.pure_function/opt.hlt | 42 ++ .../hilti.optimization.pure_function/output | 8 + .../hilti.optimization.unimplemented_hook/log | 2 + .../opt.hlt | 4 +- .../hilti.optimization.unused_function/log | 6 + .../opt.hlt | 10 +- .../log | 3 + .../opt.hlt | 6 +- .../log | 4 + .../opt.hlt | 8 +- .../spicy.optimization.unused-functions/log | 3 + .../opt.hlt | 6 +- .../spicy.optimization.unused-types/log | 6 + .../spicy.optimization.unused-types/opt.hlt | 12 +- tests/hilti/optimization/dead_code.hlt | 23 ++ tests/hilti/optimization/pure_function.hlt | 51 +++ tests/hilti/rt/profiler.hlt | 5 + tests/hilti/types/regexp/fold-prevention.hlt | 2 +- tests/hilti/types/struct/finally.hlt | 2 +- 21 files changed, 566 insertions(+), 29 deletions(-) create mode 100644 tests/Baseline/hilti.optimization.pure_function/opt.hlt create mode 100644 tests/Baseline/hilti.optimization.pure_function/output create mode 100644 tests/hilti/optimization/pure_function.hlt diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index d139875a8..ff9059cae 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -10,21 +10,31 @@ #include +#include #include #include +#include +#include +#include #include #include #include #include +#include #include #include #include +#include +#include #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -34,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -1531,7 +1542,11 @@ struct DeadStoreVisitor : OptimizerVisitor, visitor::PreOrder() && ! decl.isA() ) return false; - // FIXME(bbannier): do not remove dead stores to struct type variables. + // Do not work on assignments to values of struct types since structs + // can have hooks attached which could make the assignment result + // visible non-locally. + if ( target->type().isA() ) + return false; switch ( _stage ) { case OptimizerVisitor::Stage::PRUNE_USES: { @@ -1649,7 +1664,23 @@ struct DeadCodeVisitor : OptimizerVisitor, visitor::PreOrder(); - ctor && (ctor->isConstant() || ! hasChildWithSideEffects(x)) ) { + ctor && (ctor->isConstant() || ! hasChildWithSideEffects(*ctor, p)) ) { + HILTI_DEBUG(logging::debug::Optimizer, + util::fmt("removing side-effect free dead code (%s)", x.meta().location())); + removeNode(p); + return true; + } + + if ( auto call = x.expression().tryAs(); + call && ! hasChildWithSideEffects(*call, p) ) { + HILTI_DEBUG(logging::debug::Optimizer, + util::fmt("removing side-effect free dead code (%s)", x.meta().location())); + removeNode(p); + return true; + } + + if ( auto call = x.expression().tryAs(); + call && ! hasChildWithSideEffects(*call, p) ) { HILTI_DEBUG(logging::debug::Optimizer, util::fmt("removing side-effect free dead code (%s)", x.meta().location())); removeNode(p); @@ -1659,12 +1690,138 @@ struct DeadCodeVisitor : OptimizerVisitor, visitor::PreOrder().result().type().isA() ) + return false; + + // If the function is not pure we need to keep its body. + if ( const auto& attrs = function.attributes(); ! attrs || ! attrs->find("&pure") ) + return false; + + // If we have a pure function which returns void its body is irrelevant and can be removed. + auto new_function = Function(function.id(), function.type(), statement::Block(), function.callingConvention(), + function.attributes(), function.meta()); + + p.node.as().setFunction(new_function); + return true; + } + + bool hasChildWithSideEffects(const Node& node, position_t p) const { + auto potentiallyHasSideEffects = [](const Node& it, position_t p) { + const type::Function* fn = nullptr; + const AttributeSet* attributes = nullptr; + + if ( const auto& call = it.tryAs() ) { + const auto& decl = call->op0().as().declaration(); + const auto& function = decl.as().function(); + + fn = &function.type().as(); + if ( const auto& attrs = function.attributes() ) + attributes = &attrs.value(); + } + + else if ( const auto& call = it.tryAs() ) { + const auto& field_id = call->op1().as().id(); + + // If we access the self pointer the argument will be `*self` + // so we need to strip away the additional dereferencing. + const auto* op0 = &call->op0(); + if ( const auto& deref = op0->tryAs() ) + op0 = &deref->op0(); + + const auto& value = op0->tryAs(); + if ( ! value ) + return true; + + const auto& decl = value->declaration(); + + std::optional type; + std::optional struct_; + if ( const auto& x = decl.tryAs() ) { + type = x->type(); + struct_ = type->tryAs(); + } + else if ( const auto& x = decl.tryAs() ) { + type = x->type(); + struct_ = type->tryAs(); + } + else + return true; + + if ( ! struct_ ) + return true; + + assert(type); + + const auto& field = struct_->field(field_id); + if ( ! field ) + return true; + + // Method declared inline. + if ( const auto& functions = field->childRefsOfType(); ! functions.empty() ) { + const auto& function = &functions[0]->as(); + fn = &function->ftype(); + + if ( const auto& attrs = function->attributes() ) + attributes = &attrs.value(); + } + + // Method not declared inline. + else if ( const auto& function_ = + scope::lookupID(ID(util::fmt("%s::%s", *type->typeID(), field_id)), + p, "function") ) { + const auto& function = function_->first->as().function(); + + fn = &function.ftype(); + + if ( const auto& attrs = function.attributes() ) + attributes = &attrs.value(); + } + + else + // Could not resolve method, assume it has side-effects. + // + // TODO(bbannier): This code path is triggered by e.g., the test + // `hilti.types.function.hook-methods-across-units`. We + // should really handle such cases. + return true; + } + + if ( fn ) { + // Do not remove calls to hooks since they might have + // implementations with side-effects elsewhere. + if ( fn->flavor() == type::function::Flavor::Hook ) + return true; + + // If the function returns a struct type there might be hooks + // like `finally` attached to the type so calling the function + // has side effects. + if ( fn->result().type().isA() ) + return true; + + // If the function is not marked pure it has side effects. + if ( ! attributes || ! attributes->find("&pure") ) + return true; + } + + return false; + }; + + if ( potentiallyHasSideEffects(node, p) ) + return true; + auto v = visitor::PreOrder<>(); // NOLINTNEXTLINE(readability-use-anyofallof) for ( const auto& child : v.walk(node) ) { - if ( child.node.isA() || child.node.isA() ) + if ( potentiallyHasSideEffects(child.node, p) ) return true; } @@ -1672,6 +1829,214 @@ struct DeadCodeVisitor : OptimizerVisitor, visitor::PreOrder { + bool prune_decls(Node& node) override { + bool any_modification = false; + + while ( true ) { + bool modified = false; + for ( auto i : this->walk(&node) ) { + if ( auto x = dispatch(i) ) + modified = modified || *x; + } + + if ( ! modified ) + break; + + any_modification = true; + } + + return any_modification; + } + + result_t operator()(const declaration::Function& x, position_t p) { + if ( auto new_function = pureFunction(x.function(), p) ) { + HILTI_DEBUG(logging::debug::Optimizer, util::fmt("marking function '%s' as pure", x.function().id())); + + p.node.as().setFunction(*new_function); + return true; + } + + return false; + } + + result_t operator()(const declaration::Field& x, position_t p) { + const Function* fn = nullptr; + auto path = p.path; + + if ( const auto& inline_ = x.inlineFunction() ) { + // Field already marked pure. + if ( const auto& attrs = inline_->attributes(); attrs && attrs->find("&pure") ) + return false; + + fn = &*inline_; + path.emplace_back(const_cast(p.node.children()[3])); + } + + else { + if ( const auto& type = p.parent(2).tryAs() ) { + if ( auto function_ = + scope::lookupID(ID(util::fmt("%s::%s", type->canonicalID(), x.id())), p, + "function") ) { + const auto& function = function_->first->as(); + + fn = &function.function(); + path.emplace_back(const_cast(function.children()[0])); + } + } + } + + if ( fn ) { + assert(! path.empty()); + auto& node = path.rbegin()->node; + auto pn = position_t{node, path}; + + if ( auto new_function = pureFunction(*fn, pn) ) { + HILTI_DEBUG(logging::debug::Optimizer, + util::fmt("marking member '%s' as pure (%s)", fn->id(), fn->meta().location())); + + node = new_function.value(); + return true; + } + } + + // Methods not declared inline are handled via their function declaration. + + return false; + } + + // Helper function which either computes a pure version of the given + // input function, or nothing if it cannot detect that the functions is + // pure. + std::optional pureFunction(const Function& x, position_t p) const { + if ( const auto& attrs = x.attributes() ) { + // The function is already marked pure. + if ( attrs->find("&pure") ) + return {}; + + // We cannot analyze functions implemented in C++ so they cannot be pure. + if ( attrs->find("&cxxname") ) + return {}; + } + + // We cannot analyze functions for which we have no body. + if ( ! x.body() ) + return {}; + + // We determine whether the function can be pure by looking at all the + // IDs it references and members it calls. We assume this function is + // pure unless we can find a reason it should not be. + auto v = visitor::PreOrder<>(); + for ( const auto& child : v.walk(p.node) ) { + if ( auto id = child.node.tryAs() ) { + const auto& decl = id->declaration(); + + if ( auto local = decl.tryAs() ) { + const auto& type = local->type(); + + // References provide interior mutability regardless of the + // mutability of the param so mark such accesses not as pure. + // + // TODO(bbannier): Trace origin of the data. If it comes from + // inside the function we could still mark such functions pure. + if ( type::isReferenceType(type) ) + return {}; + + continue; + } + + else if ( decl.isA() ) + // Functions accessing global variables cannot be pure for now. + // + // TODO(bbannier): Relax this so we only reject globals + // used in the following contexts: + // + // - global of reference type since e.g., any operator + // could mutate externally visible internal state + // - LHS in an assignment + // - used as `inout` parameter to any function. + // - used with an operator (which could mutate internal global state) + return {}; + + else if ( auto fn = decl.tryAs() ) { + // If we reference a non-pure function the function is unlikely to be pure. + if ( const auto& attrs = fn->function().attributes(); ! attrs || ! attrs->find("&pure") ) + return {}; + } + + // FIXME(bbannier): If we construct a struct we can trigger life-cycle + // hooks making such functions not pure. Reject them here. + + // Accessing keywords is side-effect free. + else if ( const auto& e = decl.tryAs(); + e && e->expression().isA() ) { + continue; + } + + else + // Do not mark this function pure if it references any other kinds of IDs. + return {}; + } + + else if ( auto call = child.node.tryAs() ) { + const auto& op0 = call->op0(); + + const auto& callee = op0.type().tryAs(); + if ( ! callee ) + return {}; + + auto typeID = op0.type().typeID(); + if ( ! typeID ) + return {}; + + const auto& method = call->op1().tryAs(); + if ( ! method ) + return {}; + + auto field = callee->field(method->id()); + if ( ! field ) + return {}; + + // Method declared inline. + if ( const auto& functions = field->childRefsOfType(); ! functions.empty() ) { + if ( const auto& attrs = functions[0]->as().attributes(); + ! attrs || ! attrs->find("&pure") ) + // Called method not marked pure. + return {}; + } + + // Method not declared inline. + else if ( const auto& function = + scope::lookupID(ID(util::fmt("%s::%s", *typeID, field->id())), p, + "function"); + function ) { + if ( const auto& attrs = function->first->as().function().attributes(); + ! attrs || ! attrs->find("&pure") ) + // Called method not marked pure. + return {}; + } + else { + return {}; + } + } + + else if ( child.node.isA() ) + return {}; + } + + auto new_attrs = AttributeSet({Attribute("&pure")}); + if ( auto attrs = x.attributes() ) + for ( const auto& attr : attrs->attributes() ) + new_attrs = AttributeSet::add(std::move(new_attrs), attr); + + auto f = Node(x)._clone().as(); + f.setAttributes(new_attrs); + return {std::move(f)}; + } +}; + void Optimizer::run() { util::timing::Collector _("hilti/compiler/optimizer"); @@ -1719,6 +2084,7 @@ void Optimizer::run() { {"members", []() { return std::make_unique(); }}, {"dead_code", []() { return std::make_unique(); }}, {"dead_store", []() { return std::make_unique(); }}, + {"pure_function", []() { return std::make_unique(); }}, {"types", []() { return std::make_unique(); }}}; // If no user-specified passes are given enable all of them. diff --git a/tests/Baseline/hilti.optimization.dead_code/opt.hlt b/tests/Baseline/hilti.optimization.dead_code/opt.hlt index 7bdb67860..9784133ee 100644 --- a/tests/Baseline/hilti.optimization.dead_code/opt.hlt +++ b/tests/Baseline/hilti.optimization.dead_code/opt.hlt @@ -1,4 +1,22 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. module foo { +type X = struct { +}; + +global uint<64> num_calls = 0; + +function X fn_pure3() &pure { + local X x; + return x; +} + +function uint<64> fn_not_pure() { + num_calls += 1; + return 0; +} + +fn_pure3(); +fn_not_pure(); + } diff --git a/tests/Baseline/hilti.optimization.pure_function/opt.hlt b/tests/Baseline/hilti.optimization.pure_function/opt.hlt new file mode 100644 index 000000000..126009b1e --- /dev/null +++ b/tests/Baseline/hilti.optimization.pure_function/opt.hlt @@ -0,0 +1,42 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +module foo { + +type X = struct { + hook void ~finally() {} +}; +type T = struct { + method void pure() {} + method int<64> pure2() { return 0; } + method X pure3() { + local X x; + return x; + } + method void pure4(); +}; + +global uint<64> num_calls = 0; +global T t; + +function uint<64> fn_pure() &pure { + local uint<64> x = 0; + x += 1; + fn_pure2(); + return 0; +} + +function void fn_pure2() &pure { +} + +function uint<64> fn_not_pure() { + num_calls += 1; + fn_pure(); + return 0; +} + +method method void T::pure4() &pure { + self; +} + +t.pure4(); + +} diff --git a/tests/Baseline/hilti.optimization.pure_function/output b/tests/Baseline/hilti.optimization.pure_function/output new file mode 100644 index 000000000..93cc43187 --- /dev/null +++ b/tests/Baseline/hilti.optimization.pure_function/output @@ -0,0 +1,8 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +[debug/optimizer] marking function 'fn_pure2' as pure +[debug/optimizer] marking member '~finally' as pure (<...>/pure_function.hlt:27:5) +[debug/optimizer] marking member 'pure' as pure (<...>/pure_function.hlt:31:5) +[debug/optimizer] marking member 'pure2' as pure (<...>/pure_function.hlt:32:5) +[debug/optimizer] marking member 'pure3' as pure (<...>/pure_function.hlt:35:5-38:6) +[debug/optimizer] marking member 'T::pure4' as pure (<...>/pure_function.hlt:43:7-46:2) +[debug/optimizer] marking function 'fn_pure' as pure diff --git a/tests/Baseline/hilti.optimization.unimplemented_hook/log b/tests/Baseline/hilti.optimization.unimplemented_hook/log index e723ec389..ac77e1819 100644 --- a/tests/Baseline/hilti.optimization.unimplemented_hook/log +++ b/tests/Baseline/hilti.optimization.unimplemented_hook/log @@ -6,6 +6,8 @@ [debug/optimizer] removing declaration for unused hook function Foo::global_unimplemented_void [debug/optimizer] removing declaration for unused hook function Foo::global_unimplemented_int64 [debug/optimizer] removing field for unused method Foo::X::unimplemented +[debug/optimizer] marking function 'global_implemented' as pure +[debug/optimizer] marking member 'X::implemented' as pure (<...>/unimplemented_hook.hlt:25:5) [debug/optimizer] removing side-effect free dead code (<...>/unimplemented_hook.hlt:29:1) [debug/optimizer] removing side-effect free dead code (<...>/unimplemented_hook.hlt:35:1) [debug/optimizer] removing field for unused method Foo::X::unimplemented_void diff --git a/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt b/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt index 8a2af793e..4c0320859 100644 --- a/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt +++ b/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt @@ -11,10 +11,10 @@ global optional> j = default>>(); declare public function hook void global_implemented(); -function hook void global_implemented() { +function hook void global_implemented() &pure { } -method hook void X::implemented() { +method hook void X::implemented() &pure { } global_implemented(); diff --git a/tests/Baseline/hilti.optimization.unused_function/log b/tests/Baseline/hilti.optimization.unused_function/log index faae315a8..e638dda15 100644 --- a/tests/Baseline/hilti.optimization.unused_function/log +++ b/tests/Baseline/hilti.optimization.unused_function/log @@ -1,4 +1,10 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +[debug/optimizer] marking function 'extern_unused1' as pure +[debug/optimizer] marking function 'extern_unused2' as pure +[debug/optimizer] marking function 'private_unused' as pure +[debug/optimizer] marking function 'private_used' as pure +[debug/optimizer] marking function 'test_init' as pure +[debug/optimizer] marking function 'test_preinit' as pure [debug/optimizer] removing declaration for unused function Foo::private_unused [debug/optimizer] removing declaration for unused function Foo::private_unused1 [debug/optimizer] removing declaration for unused function Foo::public_unused diff --git a/tests/Baseline/hilti.optimization.unused_function/opt.hlt b/tests/Baseline/hilti.optimization.unused_function/opt.hlt index cbeec9093..6838cd8af 100644 --- a/tests/Baseline/hilti.optimization.unused_function/opt.hlt +++ b/tests/Baseline/hilti.optimization.unused_function/opt.hlt @@ -3,22 +3,22 @@ module Foo { global bool x = private_used(); -function bool private_used() { +function bool private_used() &pure { return False; } -public function extern bool extern_unused1() { +public function extern bool extern_unused1() &pure { return False; } -function extern bool extern_unused2() { +function extern bool extern_unused2() &pure { return False; } -init function void test_init() { +init function void test_init() &pure { } -preinit function void test_preinit() { +preinit function void test_preinit() &pure { } } diff --git a/tests/Baseline/spicy.optimization.default-parser-functions/log b/tests/Baseline/spicy.optimization.default-parser-functions/log index 7d32ccff4..a3c11efa9 100644 --- a/tests/Baseline/spicy.optimization.default-parser-functions/log +++ b/tests/Baseline/spicy.optimization.default-parser-functions/log @@ -126,6 +126,9 @@ [debug/optimizer] inlining constant 'foo::__feat%foo@@P2%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@P2%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@P2%uses_random_access' +[debug/optimizer] marking function '__register_foo_P0' as pure +[debug/optimizer] marking member 'foo::P2::__on_0x25_error' as pure (<...>/default-parser-functions.spicy:20:5) +[debug/optimizer] marking member 'foo::P2::__on_y' as pure (<...>/default-parser-functions.spicy:18:13) [debug/optimizer] removing declaration for unused function foo::P0::__parse_foo_P0_stage2 [debug/optimizer] removing declaration for unused function foo::P0::__parse_stage1 [debug/optimizer] removing declaration for unused function foo::P0::parse1 diff --git a/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt b/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt index 3bb246cb5..c007b80c9 100644 --- a/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt +++ b/tests/Baseline/spicy.optimization.default-parser-functions/opt.hlt @@ -46,7 +46,7 @@ const bool __feat%foo@@P2%supports_filters = False; const bool __feat%foo@@P2%supports_sinks = False; const bool __feat%foo@@P2%synchronization = False; -init function void __register_foo_P0() { +init function void __register_foo_P0() &pure { } method method tuple, int<64>, iterator, optional> foo::P1::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { @@ -146,10 +146,10 @@ init function void __register_foo_P1() { spicy_rt::registerParser(foo::P1::__parser, $scope, Null); } -method hook void foo::P2::__on_y(uint<8> __dd) { +method hook void foo::P2::__on_y(uint<8> __dd) &pure { } -method hook void foo::P2::__on_0x25_error(string __except) { +method hook void foo::P2::__on_0x25_error(string __except) &pure { } method method tuple, int<64>, iterator, optional> foo::P2::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { diff --git a/tests/Baseline/spicy.optimization.feature_requirements/log b/tests/Baseline/spicy.optimization.feature_requirements/log index 484201bd2..e87858a51 100644 --- a/tests/Baseline/spicy.optimization.feature_requirements/log +++ b/tests/Baseline/spicy.optimization.feature_requirements/log @@ -271,6 +271,10 @@ [debug/optimizer] inlining constant 'foo::__feat%foo@@X6%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@X6%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@X6%uses_random_access' +[debug/optimizer] marking function '__register_foo_X0' as pure +[debug/optimizer] marking function '__register_foo_X1' as pure +[debug/optimizer] marking function '__register_foo_X2' as pure +[debug/optimizer] marking function '__register_foo_X3' as pure [debug/optimizer] removing declaration for unused function foo::X0::__parse_foo_X0_stage2 [debug/optimizer] removing declaration for unused function foo::X0::__parse_stage1 [debug/optimizer] removing declaration for unused function foo::X0::parse1 diff --git a/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt b/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt index 900e8f2a4..c127a1314 100644 --- a/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt +++ b/tests/Baseline/spicy.optimization.feature_requirements/opt.hlt @@ -106,20 +106,20 @@ method hook void foo::X0::__on_0x25_init() { self.__offset; } -init function void __register_foo_X0() { +init function void __register_foo_X0() &pure { } method hook void foo::X1::__on_0x25_init() { self.__begin; } -init function void __register_foo_X1() { +init function void __register_foo_X1() &pure { } -init function void __register_foo_X2() { +init function void __register_foo_X2() &pure { } -init function void __register_foo_X3() { +init function void __register_foo_X3() &pure { } method method tuple, int<64>, iterator, optional> foo::X4::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { diff --git a/tests/Baseline/spicy.optimization.unused-functions/log b/tests/Baseline/spicy.optimization.unused-functions/log index 5ed9e6f8b..dfd72b9a5 100644 --- a/tests/Baseline/spicy.optimization.unused-functions/log +++ b/tests/Baseline/spicy.optimization.unused-functions/log @@ -195,6 +195,9 @@ [debug/optimizer] inlining constant 'foo::__feat%foo@@F%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@F%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@F%uses_random_access' +[debug/optimizer] marking function '__register_foo_A' as pure +[debug/optimizer] marking function '__register_foo_C' as pure +[debug/optimizer] marking function '__register_foo_F' as pure [debug/optimizer] removing declaration for unused function foo::A::__parse_foo_A_stage2 [debug/optimizer] removing declaration for unused function foo::A::__parse_stage1 [debug/optimizer] removing declaration for unused function foo::A::parse1 diff --git a/tests/Baseline/spicy.optimization.unused-functions/opt.hlt b/tests/Baseline/spicy.optimization.unused-functions/opt.hlt index 6f6bbbe2a..53c016cfd 100644 --- a/tests/Baseline/spicy.optimization.unused-functions/opt.hlt +++ b/tests/Baseline/spicy.optimization.unused-functions/opt.hlt @@ -60,7 +60,7 @@ const bool __feat%foo@@F%supports_filters = False; const bool __feat%foo@@F%supports_sinks = False; const bool __feat%foo@@F%synchronization = False; -init function void __register_foo_A() { +init function void __register_foo_A() &pure { } method method tuple, int<64>, iterator, optional> foo::B::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { @@ -195,7 +195,7 @@ method method tuple, int<64>, iterator, optional, int<64>, iterator, optional> foo::D::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { @@ -303,7 +303,7 @@ init function void __register_foo_D() { spicy_rt::registerParser(foo::D::__parser, $scope, Null); } -init function void __register_foo_F() { +init function void __register_foo_F() &pure { } } diff --git a/tests/Baseline/spicy.optimization.unused-types/log b/tests/Baseline/spicy.optimization.unused-types/log index 252080ad9..0e29530c7 100644 --- a/tests/Baseline/spicy.optimization.unused-types/log +++ b/tests/Baseline/spicy.optimization.unused-types/log @@ -360,6 +360,12 @@ [debug/optimizer] inlining constant 'foo::__feat%foo@@Pub3%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@Pub3%uses_random_access' [debug/optimizer] inlining constant 'foo::__feat%foo@@Pub3%uses_random_access' +[debug/optimizer] marking function '__register_foo_Priv1' as pure +[debug/optimizer] marking function '__register_foo_Priv2' as pure +[debug/optimizer] marking function '__register_foo_Priv3' as pure +[debug/optimizer] marking function '__register_foo_Priv4' as pure +[debug/optimizer] marking function '__register_foo_Priv5' as pure +[debug/optimizer] marking function '__register_foo_Priv6' as pure [debug/optimizer] removing declaration for unused function foo::Priv1::__parse_foo_Priv1_stage2 [debug/optimizer] removing declaration for unused function foo::Priv1::__parse_stage1 [debug/optimizer] removing declaration for unused function foo::Priv1::parse1 diff --git a/tests/Baseline/spicy.optimization.unused-types/opt.hlt b/tests/Baseline/spicy.optimization.unused-types/opt.hlt index f1d3b901e..88a8dc806 100644 --- a/tests/Baseline/spicy.optimization.unused-types/opt.hlt +++ b/tests/Baseline/spicy.optimization.unused-types/opt.hlt @@ -109,7 +109,7 @@ const bool __feat%foo@@Priv10%synchronization = False; global Priv11 en; global Priv12 em = Priv12::A; -init function void __register_foo_Priv1() { +init function void __register_foo_Priv1() &pure { } method method tuple, int<64>, iterator, optional> foo::Pub2::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { @@ -209,13 +209,13 @@ init function void __register_foo_Pub2() { spicy_rt::registerParser(foo::Pub2::__parser, $scope, Null); } -init function void __register_foo_Priv2() { +init function void __register_foo_Priv2() &pure { } -init function void __register_foo_Priv3() { +init function void __register_foo_Priv3() &pure { } -init function void __register_foo_Priv4() { +init function void __register_foo_Priv4() &pure { } method method tuple, int<64>, iterator, optional> foo::Priv5::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { @@ -253,7 +253,7 @@ method method tuple, int<64>, iterator, optional, int<64>, iterator, optional> foo::Priv6::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { @@ -291,7 +291,7 @@ method method tuple, int<64>, iterator, optional, int<64>, iterator, optional> foo::Pub3::__parse_stage1(inout value_ref __data, iterator __begin, copy view __cur, copy bool __trim, copy int<64> __lah, copy iterator __lahe, copy optional __error) { diff --git a/tests/hilti/optimization/dead_code.hlt b/tests/hilti/optimization/dead_code.hlt index 43385933b..25b29c364 100644 --- a/tests/hilti/optimization/dead_code.hlt +++ b/tests/hilti/optimization/dead_code.hlt @@ -3,8 +3,31 @@ module foo { +function uint<64> fn_pure1() { + return 0; +} + +function void fn_pure2() {} + +type X = struct {}; +function X fn_pure3() { + local X x; + return x; +} + +global uint<64> num_calls = 0; +function uint<64> fn_not_pure() { + num_calls += 1; + return 0; +} + uint8(10); default(); True; +fn_pure1(); +fn_pure2(); +fn_pure3(); +fn_not_pure(); + } diff --git a/tests/hilti/optimization/pure_function.hlt b/tests/hilti/optimization/pure_function.hlt new file mode 100644 index 000000000..9ec4710dd --- /dev/null +++ b/tests/hilti/optimization/pure_function.hlt @@ -0,0 +1,51 @@ +# @TEST-DOC: Checks marking of functions as pure. +# +# @TEST-EXEC: HILTI_OPTIMIZER_PASSES=pure_function hiltic -p %INPUT -o opt.hlt -D optimizer >output 2>&1 +# @TEST-EXEC: btest-diff output +# @TEST-EXEC: btest-diff opt.hlt + +module foo { + +function uint<64> fn_pure() { + local x = 0; + x += 1; + fn_pure2(); + return 0; +} + +function void fn_pure2() {} + +global uint<64> num_calls = 0; + +function uint<64> fn_not_pure() { + num_calls += 1; + fn_pure(); + return 0; +} + +type X = struct { + hook void ~finally() {} +}; + +type T = struct { + method void pure() {} + method int<64> pure2() { return 0; } + + # Calls of this method cannot be elided since it returns a struct with lifetime hooks. + method X pure3() { + local X x; + return x; + } + + method void pure4(); +}; + +method void T::pure4() { + # This should be recognized as side-effect free. + self; +} + +global T t; +t.pure4(); + +} diff --git a/tests/hilti/rt/profiler.hlt b/tests/hilti/rt/profiler.hlt index c261d40b6..64145a951 100644 --- a/tests/hilti/rt/profiler.hlt +++ b/tests/hilti/rt/profiler.hlt @@ -7,7 +7,12 @@ module Foo { +import hilti; + +global num_called_y = 0; function void y() { + # Trigger a side effect so this function is not optimized out. + ++num_called_y; } function int<64> fibo(int<64> n) { diff --git a/tests/hilti/types/regexp/fold-prevention.hlt b/tests/hilti/types/regexp/fold-prevention.hlt index abe0bd9ce..925a78fa8 100644 --- a/tests/hilti/types/regexp/fold-prevention.hlt +++ b/tests/hilti/types/regexp/fold-prevention.hlt @@ -1,4 +1,4 @@ -# @TEST-EXEC: hiltic -c %INPUT | grep -q 'my_regex(.*::Test::re)' +# @TEST-EXEC: hiltic -gc %INPUT | grep -q 'my_regex(.*::Test::re)' # # @TEST-DOC: Test that direct references to regexp constants aren't folded into local temporaries. Regression test for #1396. diff --git a/tests/hilti/types/struct/finally.hlt b/tests/hilti/types/struct/finally.hlt index 4cb4bbc9f..df6c43e86 100644 --- a/tests/hilti/types/struct/finally.hlt +++ b/tests/hilti/types/struct/finally.hlt @@ -1,4 +1,4 @@ -# @TEST-EXEC: hiltic -j %INPUT | sort >output +# @TEST-EXEC: hiltic -gj %INPUT | sort >output # @TEST-EXEC: btest-diff output # # @TEST-DOC: Test that struct finalizers execute as expected. From 1b3842bfb04cb80ea08379633fd6c24f40e41904 Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Tue, 16 May 2023 23:04:24 +0200 Subject: [PATCH 08/10] Functions with locals of structs with lifecycle hooks are not pure. --- hilti/toolchain/src/compiler/optimizer.cc | 40 ++++++++++++------- .../hilti.optimization.dead_code/opt.hlt | 15 +++---- .../hilti.optimization.pure_function/output | 1 - tests/hilti/optimization/dead_code.hlt | 17 ++++++-- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index ff9059cae..42b3195bd 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -1473,6 +1473,18 @@ struct MemberVisitor : OptimizerVisitor, visitor::PreOrder } }; +// Helper function to detect whether a struct has lifecycle hooks attached. +bool hasLifecycleHooks(const type::Struct& s) { + // NOLINTNEXTLINE(readability-use-anyofallof) + for ( const auto& f : s.fields() ) { + // Currently the only lifecycle hook we have is `~finally`. + if ( f.id().str() == "~finally" ) + return true; + } + + return false; +}; + // Optimizer pass which removes dead store and variable declarations. struct DeadStoreVisitor : OptimizerVisitor, visitor::PreOrder { // We use decl identities here instead of their canonical IDs since for @@ -1542,10 +1554,9 @@ struct DeadStoreVisitor : OptimizerVisitor, visitor::PreOrder() && ! decl.isA() ) return false; - // Do not work on assignments to values of struct types since structs - // can have hooks attached which could make the assignment result - // visible non-locally. - if ( target->type().isA() ) + // Do not work on assignments to values of struct types which have + // lifecycle hooks attached. + if ( auto s = target->type().tryAs(); s && hasLifecycleHooks(*s) ) return false; switch ( _stage ) { @@ -1609,9 +1620,9 @@ struct DeadStoreVisitor : OptimizerVisitor, visitor::PreOrder() ) { - // Do not attempt to remove declarations of structs since - // they might have additional state attached via hooks. - if ( local->type().isA() ) + // Do not attempt to remove declarations of structs with + // lifecycle hooks. + if ( auto s = local->type().tryAs(); s && hasLifecycleHooks(*s) ) break; // If the local is initialized do not remove it to still @@ -1800,10 +1811,8 @@ struct DeadCodeVisitor : OptimizerVisitor, visitor::PreOrderflavor() == type::function::Flavor::Hook ) return true; - // If the function returns a struct type there might be hooks - // like `finally` attached to the type so calling the function - // has side effects. - if ( fn->result().type().isA() ) + // If the function returns a struct type with lifecycle hooks it has side effects. + if ( auto s = fn->result().type().tryAs(); s && hasLifecycleHooks(*s) ) return true; // If the function is not marked pure it has side effects. @@ -1944,6 +1953,12 @@ struct PureFunctionVisitor : OptimizerVisitor, visitor::PreOrder(); s && hasLifecycleHooks(*s) ) + return {}; + continue; } @@ -1966,9 +1981,6 @@ struct PureFunctionVisitor : OptimizerVisitor, visitor::PreOrder(); e && e->expression().isA() ) { diff --git a/tests/Baseline/hilti.optimization.dead_code/opt.hlt b/tests/Baseline/hilti.optimization.dead_code/opt.hlt index 9784133ee..725ff0a96 100644 --- a/tests/Baseline/hilti.optimization.dead_code/opt.hlt +++ b/tests/Baseline/hilti.optimization.dead_code/opt.hlt @@ -1,22 +1,23 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. module foo { -type X = struct { +type Y = struct { + hook void ~finally() {} }; global uint<64> num_calls = 0; -function X fn_pure3() &pure { - local X x; - return x; +function Y fn_pure4() { + local Y y; + return y; } -function uint<64> fn_not_pure() { +function uint<64> fn_not_pure2() { num_calls += 1; return 0; } -fn_pure3(); -fn_not_pure(); +fn_pure4(); +fn_not_pure2(); } diff --git a/tests/Baseline/hilti.optimization.pure_function/output b/tests/Baseline/hilti.optimization.pure_function/output index 93cc43187..b2e19dfe9 100644 --- a/tests/Baseline/hilti.optimization.pure_function/output +++ b/tests/Baseline/hilti.optimization.pure_function/output @@ -3,6 +3,5 @@ [debug/optimizer] marking member '~finally' as pure (<...>/pure_function.hlt:27:5) [debug/optimizer] marking member 'pure' as pure (<...>/pure_function.hlt:31:5) [debug/optimizer] marking member 'pure2' as pure (<...>/pure_function.hlt:32:5) -[debug/optimizer] marking member 'pure3' as pure (<...>/pure_function.hlt:35:5-38:6) [debug/optimizer] marking member 'T::pure4' as pure (<...>/pure_function.hlt:43:7-46:2) [debug/optimizer] marking function 'fn_pure' as pure diff --git a/tests/hilti/optimization/dead_code.hlt b/tests/hilti/optimization/dead_code.hlt index 25b29c364..dc50a261f 100644 --- a/tests/hilti/optimization/dead_code.hlt +++ b/tests/hilti/optimization/dead_code.hlt @@ -10,13 +10,21 @@ function uint<64> fn_pure1() { function void fn_pure2() {} type X = struct {}; -function X fn_pure3() { +function X fn_not_pure1() { local X x; return x; } +type Y = struct { + hook void ~finally() {} +}; +function Y fn_pure4() { + local Y y; + return y; +} + global uint<64> num_calls = 0; -function uint<64> fn_not_pure() { +function uint<64> fn_not_pure2() { num_calls += 1; return 0; } @@ -27,7 +35,8 @@ True; fn_pure1(); fn_pure2(); -fn_pure3(); -fn_not_pure(); +fn_not_pure1(); +fn_pure4(); +fn_not_pure2(); } From 70c35023ba5346ed33e6a089b59e039985ddc10f Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Wed, 24 May 2023 14:18:36 +0200 Subject: [PATCH 09/10] Take parameter kind into account when deciding function purity. --- hilti/toolchain/src/compiler/optimizer.cc | 19 +++++++++++++++ .../hilti.optimization.pure_function/opt.hlt | 24 +++++++++++++++++++ .../hilti.optimization.pure_function/output | 2 ++ tests/hilti/optimization/pure_function.hlt | 10 ++++++++ 4 files changed, 55 insertions(+) diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index 42b3195bd..50197b9e2 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1987,6 +1988,24 @@ struct PureFunctionVisitor : OptimizerVisitor, visitor::PreOrder() ) { + // Reference provide interior mutability regardless of the + // mutability of the param so mark such accesses not as pure. + if ( type::isReferenceType(param->type()) ) + return {}; + + switch ( param->kind() ) { + // Access to `copy` or `in` parameters is side-effect free. + case declaration::parameter::Kind::Copy: [[fallthrough]]; + case declaration::parameter::Kind::In: + continue; + + // Any other access could have side-effects. + case declaration::parameter::Kind::InOut: [[fallthrough]]; + case declaration::parameter::Kind::Unknown: return {}; + } + } + else // Do not mark this function pure if it references any other kinds of IDs. return {}; diff --git a/tests/Baseline/hilti.optimization.pure_function/opt.hlt b/tests/Baseline/hilti.optimization.pure_function/opt.hlt index 126009b1e..9b5854613 100644 --- a/tests/Baseline/hilti.optimization.pure_function/opt.hlt +++ b/tests/Baseline/hilti.optimization.pure_function/opt.hlt @@ -37,6 +37,30 @@ method method void T::pure4() &pure { self; } +function void fn_pure5(uint<64> in_param) &pure { + in_param += 42; +} + +function void fn_pure6(copy uint<64> copy_param) &pure { + copy_param += 42; +} + +function void fn_not_pure2(inout uint<64> inout_param) { + inout_param += 42; +} + +function void fn_not_pure3(strong_ref> in_param) { + (*in_param) += 42; +} + +function void fn_not_pure4(copy strong_ref> copy_param) { + (*copy_param) += 42; +} + +function void fn_not_pure2(inout strong_ref> inout_param) { + (*inout_param) += 42; +} + t.pure4(); } diff --git a/tests/Baseline/hilti.optimization.pure_function/output b/tests/Baseline/hilti.optimization.pure_function/output index b2e19dfe9..78f15968c 100644 --- a/tests/Baseline/hilti.optimization.pure_function/output +++ b/tests/Baseline/hilti.optimization.pure_function/output @@ -4,4 +4,6 @@ [debug/optimizer] marking member 'pure' as pure (<...>/pure_function.hlt:31:5) [debug/optimizer] marking member 'pure2' as pure (<...>/pure_function.hlt:32:5) [debug/optimizer] marking member 'T::pure4' as pure (<...>/pure_function.hlt:43:7-46:2) +[debug/optimizer] marking function 'fn_pure5' as pure +[debug/optimizer] marking function 'fn_pure6' as pure [debug/optimizer] marking function 'fn_pure' as pure diff --git a/tests/hilti/optimization/pure_function.hlt b/tests/hilti/optimization/pure_function.hlt index 9ec4710dd..d759a06f0 100644 --- a/tests/hilti/optimization/pure_function.hlt +++ b/tests/hilti/optimization/pure_function.hlt @@ -48,4 +48,14 @@ method void T::pure4() { global T t; t.pure4(); +# Parameter access is side-effect free for `in` and `copy` parameters if they are not of reference type. +function void fn_pure5(uint<64> in_param) { in_param += 42; } +function void fn_pure6(copy uint<64> copy_param) { copy_param += 42; } +function void fn_not_pure2(inout uint<64> inout_param) { inout_param += 42; } + +function void fn_not_pure3(strong_ref> in_param) { *in_param += 42; } +function void fn_not_pure4(copy strong_ref> copy_param) { *copy_param += 42; } +function void fn_not_pure2(inout strong_ref> inout_param) { *inout_param += 42; } + + } From 234b1561ca752bffb1bb75f1d9be6993263ced07 Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Wed, 24 May 2023 15:20:53 +0200 Subject: [PATCH 10/10] Mark runtime functions `&pure` were possible. --- hilti/lib/hilti.hlt | 6 +++--- spicy/lib/spicy_rt.hlt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hilti/lib/hilti.hlt b/hilti/lib/hilti.hlt index 018e5e6fd..6bb65f30a 100644 --- a/hilti/lib/hilti.hlt +++ b/hilti/lib/hilti.hlt @@ -24,8 +24,8 @@ declare public void debug(string dbg_stream, any obj) &cxxname="hilti::rt::debug declare public void debugIndent(string dbg_stream) &cxxname="hilti::rt::debug::indent" &have_prototype; declare public void debugDedent(string dbg_stream) &cxxname="hilti::rt::debug::dedent" &have_prototype; -declare public time current_time() &cxxname="hilti::rt::time::current_time" &have_prototype; -declare public time mktime(uint<64> y, uint<64> m, uint<64> d, uint<64> H, uint<64> M, uint<64> S) &cxxname="hilti::rt::time::mktime" &have_prototype; +declare public time current_time() &cxxname="hilti::rt::time::current_time" &have_prototype &pure; +declare public time mktime(uint<64> y, uint<64> m, uint<64> d, uint<64> H, uint<64> M, uint<64> S) &cxxname="hilti::rt::time::mktime" &have_prototype &pure; declare public void abort() &cxxname="hilti::rt::abort_with_backtrace" &have_prototype; @@ -49,5 +49,5 @@ public type RecoverableFailure = exception &cxxname="hilti::rt::RecoverableFailu public type MissingData = exception &cxxname="hilti::rt::MissingData"; # Returns the message associated with an exception. -declare public string exception_what(SystemException excpt) &cxxname="hilti::rt::exception::what" &have_prototype; +declare public string exception_what(SystemException excpt) &cxxname="hilti::rt::exception::what" &have_prototype &pure; } diff --git a/spicy/lib/spicy_rt.hlt b/spicy/lib/spicy_rt.hlt index 5741bddb2..7a67d712e 100644 --- a/spicy/lib/spicy_rt.hlt +++ b/spicy/lib/spicy_rt.hlt @@ -29,7 +29,7 @@ public type Sink = struct { method void set_auto_trim(bool enable); method void set_initial_sequence_number(uint<64> seq); method void set_policy(any policy); - method uint<64> size(); + method uint<64> size() &pure; method void skip(uint<64> seq); method void trim(uint<64> seq); method void write(bytes data, optional> seq = Null, optional> len = Null);