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 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/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; } diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index dd90c01ac..50197b9e2 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -10,22 +10,42 @@ #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 #include #include +#include +#include #include #include #include +#include #include #include #include @@ -853,10 +873,29 @@ struct ConstantFoldingVisitor : OptimizerVisitor, visitor::PreOrder(); + lhs && + (lhs->declaration().isA() || + lhs->declaration().isA()) && + rhs && rhs.value() ) { + replaceNode(p, builder::bool_(true)); + return true; + } } }; @@ -871,10 +910,29 @@ struct ConstantFoldingVisitor : OptimizerVisitor, visitor::PreOrder(); + lhs && + (lhs->declaration().isA() || + lhs->declaration().isA()) && + rhs && ! rhs.value() ) { + replaceNode(p, builder::bool_(false)); + return true; + } } }; @@ -1416,6 +1474,600 @@ 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 + // 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; + + // 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 ) { + 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 with + // lifecycle hooks. + if ( auto s = local->type().tryAs(); s && hasLifecycleHooks(*s) ) + 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; + } +}; + +struct DeadCodeVisitor : 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(*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); + return true; + } + + return false; + } + + result_t operator()(const declaration::Function& x, position_t p) { + const auto& function = x.function(); + + // If the function already has an empty body we cannot simplify it further here. + if ( const auto& body = function.body(); body && *body == statement::Block() ) + return false; + + // If the function does not return `void` we still need to keep its body. + if ( ! function.type().as().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 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. + 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 ( potentiallyHasSideEffects(child.node, p) ) + return true; + } + + return false; + } +}; + +// An optimizer pass which annotates pure functions. This +// information can be used by other passes to remove dead code. +struct PureFunctionVisitor : 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 {}; + + // Functions accessing local variables can be pure if they + // do not construct struct values of types with lifecycle + // hooks attached. + if ( auto s = type.tryAs(); s && hasLifecycleHooks(*s) ) + 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 {}; + } + + // Accessing keywords is side-effect free. + else if ( const auto& e = decl.tryAs(); + e && e->expression().isA() ) { + continue; + } + + else if ( const auto& param = decl.tryAs() ) { + // 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 {}; + } + + 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"); @@ -1461,6 +2113,9 @@ 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(); }}, + {"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/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); 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.const/noopt.hlt b/tests/Baseline/hilti.optimization.const/noopt.hlt index 60df62e0f..305d07eda 100644 --- a/tests/Baseline/hilti.optimization.const/noopt.hlt +++ b/tests/Baseline/hilti.optimization.const/noopt.hlt @@ -6,6 +6,8 @@ import hilti; const bool t = True; const bool f = False; +global bool a = False; + hilti::print(t, True); hilti::print(f, True); @@ -46,5 +48,13 @@ False; True; t ? 1 : 0; f ? 0 : 1; +a || True; +a || False; +a && True; +a && False; +True || a; +False || a; +True && a; +False && a; } diff --git a/tests/Baseline/hilti.optimization.const/opt.hlt b/tests/Baseline/hilti.optimization.const/opt.hlt index 9c0fb5ec3..ab42c3126 100644 --- a/tests/Baseline/hilti.optimization.const/opt.hlt +++ b/tests/Baseline/hilti.optimization.const/opt.hlt @@ -6,6 +6,8 @@ import hilti; const bool t = True; const bool f = False; +global bool a = False; + hilti::print(True, True); hilti::print(False, True); { @@ -29,5 +31,13 @@ False; True; 1; 1; +True; +a || False; +a && True; +False; +True; +False || a; +True && a; +False; } 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..725ff0a96 --- /dev/null +++ b/tests/Baseline/hilti.optimization.dead_code/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 { + +type Y = struct { + hook void ~finally() {} +}; + +global uint<64> num_calls = 0; + +function Y fn_pure4() { + local Y y; + return y; +} + +function uint<64> fn_not_pure2() { + num_calls += 1; + return 0; +} + +fn_pure4(); +fn_not_pure2(); + +} 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/Baseline/hilti.optimization.pure_function/opt.hlt b/tests/Baseline/hilti.optimization.pure_function/opt.hlt new file mode 100644 index 000000000..9b5854613 --- /dev/null +++ b/tests/Baseline/hilti.optimization.pure_function/opt.hlt @@ -0,0 +1,66 @@ +### 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; +} + +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 new file mode 100644 index 000000000..78f15968c --- /dev/null +++ b/tests/Baseline/hilti.optimization.pure_function/output @@ -0,0 +1,9 @@ +### 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 '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/Baseline/hilti.optimization.unimplemented_hook/log b/tests/Baseline/hilti.optimization.unimplemented_hook/log index 10ea52fe9..ac77e1819 100644 --- a/tests/Baseline/hilti.optimization.unimplemented_hook/log +++ b/tests/Baseline/hilti.optimization.unimplemented_hook/log @@ -6,5 +6,9 @@ [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 [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..4c0320859 100644 --- a/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt +++ b/tests/Baseline/hilti.optimization.unimplemented_hook/opt.hlt @@ -11,15 +11,13 @@ 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(); -default(); x.implemented(); -default(); } 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 1f978e85f..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 @@ -201,6 +204,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..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) { @@ -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); @@ -151,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) { @@ -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..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 @@ -421,6 +425,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..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) { @@ -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..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 @@ -314,6 +317,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..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) { @@ -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,14 +189,13 @@ 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); return __result; } -init function void __register_foo_C() { +init function void __register_foo_C() &pure { } method method tuple, 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) { @@ -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); @@ -318,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 0d544376c..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 @@ -554,6 +560,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..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) { @@ -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); @@ -214,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) { @@ -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,14 +247,13 @@ 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); return __result; } -init function void __register_foo_Priv5() { +init function void __register_foo_Priv5() &pure { } method method tuple, 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) { @@ -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,14 +285,13 @@ 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); return __result; } -init function void __register_foo_Priv6() { +init function void __register_foo_Priv6() &pure { } method method tuple, 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) { @@ -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 36a07e58a..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 # @@ -52,4 +52,14 @@ f && f; t ? 1: 0; f ? 0: 1; +global bool a = False; +a || True; +a || False; +a && True; +a && False; +True || a; +False || a; +True && a; +False && a; + } diff --git a/tests/hilti/optimization/dead_code.hlt b/tests/hilti/optimization/dead_code.hlt new file mode 100644 index 000000000..dc50a261f --- /dev/null +++ b/tests/hilti/optimization/dead_code.hlt @@ -0,0 +1,42 @@ +# @TEST-EXEC: hiltic -p %INPUT -o opt.hlt +# @TEST-EXEC: btest-diff opt.hlt + +module foo { + +function uint<64> fn_pure1() { + return 0; +} + +function void fn_pure2() {} + +type X = struct {}; +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_pure2() { + num_calls += 1; + return 0; +} + +uint8(10); +default(); +True; + +fn_pure1(); +fn_pure2(); +fn_not_pure1(); +fn_pure4(); +fn_not_pure2(); + +} 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; + +} diff --git a/tests/hilti/optimization/pure_function.hlt b/tests/hilti/optimization/pure_function.hlt new file mode 100644 index 000000000..d759a06f0 --- /dev/null +++ b/tests/hilti/optimization/pure_function.hlt @@ -0,0 +1,61 @@ +# @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(); + +# 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; } + + +} 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.