diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 68655ce65..181784237 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -49,3 +49,13 @@ check_cxx_compiler_flag("-Wno-changes-meaning" _has_no_changes_meaning_flag) if (_has_no_changes_meaning_flag) set_property(TARGET reproc++ PROPERTY COMPILE_OPTIONS "-Wno-changes-meaning") endif () + +include(FetchContent) +FetchContent_Declare( + cxxgraph + GIT_REPOSITORY https://github.com/ZigRazor/CXXGraph.git + GIT_TAG v3.1.0 +) +FetchContent_MakeAvailable(cxxgraph) +add_library(CXXGraph INTERFACE) +target_include_directories(CXXGraph SYSTEM INTERFACE ${cxxgraph_SOURCE_DIR}/include) diff --git a/hilti/toolchain/CMakeLists.txt b/hilti/toolchain/CMakeLists.txt index 64a0f4c53..fcbe681ac 100644 --- a/hilti/toolchain/CMakeLists.txt +++ b/hilti/toolchain/CMakeLists.txt @@ -105,6 +105,7 @@ set(SOURCES src/compiler/cxx/formatter.cc src/compiler/cxx/linker.cc src/compiler/cxx/unit.cc + src/compiler/cfg.cc src/compiler/driver.cc src/compiler/init.cc src/compiler/jit.cc @@ -134,6 +135,7 @@ target_include_directories(hilti-objects BEFORE PUBLIC $) target_include_directories(hilti-objects BEFORE PUBLIC $) +target_link_libraries(hilti-objects PUBLIC CXXGraph) # Unclear why we need this: Without it, the generated Bison/Flex get a broken # include path on some systems. (Seen on Ubuntu 19.10). @@ -221,8 +223,10 @@ install(CODE "file(REMOVE \"\$ENV\{DESTDIR\}${CMAKE_INSTALL_FULL_INCLUDEDIR}/hil ##### Tests -add_executable(hilti-toolchain-tests tests/main.cc tests/id-base.cc tests/visitor.cc tests/util.cc) +add_executable(hilti-toolchain-tests tests/main.cc tests/id-base.cc tests/visitor.cc tests/util.cc + tests/cfg.cc) hilti_link_executable_in_tree(hilti-toolchain-tests PRIVATE) target_link_libraries(hilti-toolchain-tests PRIVATE doctest) +target_link_libraries(hilti-toolchain-tests PRIVATE CXXGraph) target_compile_options(hilti-toolchain-tests PRIVATE "-Wall") add_test(NAME hilti-toolchain-tests COMMAND ${PROJECT_BINARY_DIR}/bin/hilti-toolchain-tests) diff --git a/hilti/toolchain/include/ast/builder/builder.h b/hilti/toolchain/include/ast/builder/builder.h index f31cf5899..28bd976aa 100644 --- a/hilti/toolchain/include/ast/builder/builder.h +++ b/hilti/toolchain/include/ast/builder/builder.h @@ -612,7 +612,7 @@ class ExtendedBuilderTemplate : public Builder { auto addWhileElse(Expression* cond, const Meta& m = Meta()) { auto body = Builder::statementBlock(); auto else_ = Builder::statementBlock(); - Builder::block()->_add(Builder::context(), statementWhile(cond, body, else_, m)); + Builder::block()->_add(Builder::context(), Builder::statementWhile(cond, body, else_, m)); return std::make_pair(_newBuilder(body), _newBuilder(else_)); } diff --git a/hilti/toolchain/include/ast/node.h b/hilti/toolchain/include/ast/node.h index 55fa740c7..682cca787 100644 --- a/hilti/toolchain/include/ast/node.h +++ b/hilti/toolchain/include/ast/node.h @@ -177,9 +177,6 @@ class Node { public: virtual ~Node(); - /** Returns the node tag associated with the instance's class. */ - auto nodeTag() const { return _node_tags.back(); } - /** Returns true if the node has a parent (i.e., it's part of an AST). */ bool hasParent() const { return _parent; } diff --git a/hilti/toolchain/include/compiler/detail/cfg.h b/hilti/toolchain/include/compiler/detail/cfg.h new file mode 100644 index 000000000..91017e653 --- /dev/null +++ b/hilti/toolchain/include/compiler/detail/cfg.h @@ -0,0 +1,96 @@ +// Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace hilti { + +// Needed for CXXGraph, but left unimplemented. +std::istream& operator>>(std::istream&, Node*); + +namespace node::tag { +enum : uint16_t { + MetaNode = 10000, + Start, + End, + Flow, +}; +} + +namespace detail::cfg { +struct MetaNode : Node { + MetaNode(node::Tags node_tags) : Node(nullptr, node_tags, {}, {}) {} + HILTI_NODE_0(MetaNode, override); +}; + +// A meta node for the start of a control flow. +struct Start : MetaNode { + Start() : MetaNode(NodeTags) {} + HILTI_NODE_1(Start, MetaNode, final); +}; + +// A meta node for the end of a control flow. +struct End : MetaNode { + End() : MetaNode(NodeTags) {} + HILTI_NODE_1(End, MetaNode, final); +}; + +// A meta node joining or splitting control flow with no matching source statement. +struct Flow : MetaNode { + Flow() : MetaNode(NodeTags) {} + HILTI_NODE_1(Flow, MetaNode, final); +}; + +class CFG { +public: + using N = Node*; + using NodeP = std::shared_ptr>; + + CFG(const N& root); + + + template>> + N create_meta_node() { + auto n = std::make_unique(); + auto* r = n.get(); + meta_nodes.insert(std::move(n)); + return r; + } + + NodeP get_or_add_node(const N& n); + void add_edge(NodeP from, NodeP to); + + NodeP add_block(NodeP parent, const Nodes& stmts); + NodeP add_while(NodeP parent, const statement::While& while_); + NodeP add_if(NodeP parent, const statement::If& if_); + NodeP add_try_catch(const NodeP& parent, const statement::Try& try_); + NodeP add_return(const NodeP& parent, const N& expression); + + const auto& edges() const { return g.getEdgeSet(); } + auto nodes() const { return g.getNodeSet(); } + + CXXGraph::T_NodeSet unreachable_nodes() const; + + std::string dot() const; + +private: + CXXGraph::Graph g; + + std::unordered_set> meta_nodes; + NodeP begin; + NodeP end; +}; +} // namespace detail::cfg + +} // namespace hilti diff --git a/hilti/toolchain/src/compiler/cfg.cc b/hilti/toolchain/src/compiler/cfg.cc new file mode 100644 index 000000000..da8b2e15e --- /dev/null +++ b/hilti/toolchain/src/compiler/cfg.cc @@ -0,0 +1,241 @@ +// Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. + +#include "hilti/compiler/detail/cfg.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hilti { +std::istream& operator>>(std::istream&, Node*) { util::cannotBeReached(); } + +std::string node_id(const Node* n) { return util::fmt("%d", n ? n->identity() : 0); } + +namespace detail::cfg { + +CFG::CFG(const N& root) + : begin(get_or_add_node(create_meta_node())), end(get_or_add_node(create_meta_node())) { + assert(root && root->isA() && "only building from blocks currently supported"); + + auto last = add_block(begin, root->children()); + add_edge(last, end); +} + +CFG::NodeP CFG::add_block(NodeP parent, const Nodes& stmts) { + // If `children` directly has any statements which change control flow like + // `throw` or `return` any statements after that are unreachable. To model + // such ASTs we add a flow with all statements up to the "last" semantic + // statement (either the last child or the control flow statement) to the + // CFG under `parent`. Statements after that are added as children without + // parents, and mixed with the previous flow. + + // After this block `last` is the last reachable statement, either end of + // children or a control flow statement. + auto last = std::find_if(stmts.begin(), stmts.end(), [](auto&& c) { + return c && (c->template isA() || c->template isA()); + }); + const bool has_dead_flow = last != stmts.end(); + if ( has_dead_flow ) + last = std::next(last); + + // Add all statements which are part of the normal flow. + for ( auto&& c : (last != stmts.end() ? Nodes(stmts.begin(), last) : stmts) ) { + if ( ! c || ! c->isA() ) + continue; + + if ( auto&& while_ = c->tryAs() ) + parent = add_while(parent, *while_); + + else if ( auto&& if_ = c->tryAs() ) + parent = add_if(parent, *if_); + + else if ( auto&& try_catch = c->tryAs() ) + parent = add_try_catch(parent, *try_catch); + + else if ( auto&& return_ = c->tryAs() ) + parent = add_return(parent, return_->expression()); + + else if ( auto&& throw_ = c->tryAs() ) + parent = add_return(parent, throw_->expression()); + + else { + auto cc = get_or_add_node(c); + + add_edge(parent, cc); + add_block(parent, c->children()); + + // Update `last` so sibling nodes get chained. + parent = std::move(cc); + } + } + + // Add unreachable flows. + if ( has_dead_flow && last != stmts.end() ) { + auto next = add_block(nullptr, Nodes{last, stmts.end()}); + auto mix = get_or_add_node(create_meta_node()); + add_edge(parent, mix); + add_edge(next, mix); + parent = std::move(mix); + } + + return parent; +} + +CFG::NodeP CFG::add_while(NodeP parent, const statement::While& while_) { + auto&& condition = get_or_add_node(while_.condition()); + add_edge(std::move(parent), condition); + + auto body_end = add_block(condition, while_.body()->children()); + add_edge(body_end, condition); + if ( auto&& else_ = while_.else_() ) { + auto&& else_end = add_block(condition, else_->children()); + + auto mix = get_or_add_node(create_meta_node()); + + add_edge(else_end, mix); + add_edge(condition, mix); + + return mix; + } + + return condition; +} + +CFG::NodeP CFG::add_if(NodeP parent, const statement::If& if_) { + auto&& condition = get_or_add_node(if_.condition()); + add_edge(std::move(parent), condition); + + auto true_end = add_block(condition, if_.true_()->children()); + if ( auto false_ = if_.false_() ) { + auto false_end = add_block(condition, false_->children()); + auto mix = get_or_add_node(create_meta_node()); + + add_edge(false_end, mix); + add_edge(true_end, mix); + + return mix; + } + + return true_end; +} + +CFG::NodeP CFG::add_try_catch(const NodeP& parent, const statement::Try& try_catch) { + auto try_ = add_block(parent, try_catch.body()->children()); + auto mix = get_or_add_node(create_meta_node()); + add_edge(try_, mix); + + for ( auto&& catch_ : try_catch.catches() ) { + auto catch_end = add_block(parent, catch_->body()->children()); + add_edge(catch_end, mix); + } + + return mix; +} + +CFG::NodeP CFG::add_return(const NodeP& parent, const N& expression) { + if ( expression ) { + auto r = get_or_add_node(expression); + add_edge(parent, r); + return r; + } + + return parent; +} + +std::shared_ptr> CFG::get_or_add_node(const N& n) { + const auto& id = node_id(n); + if ( auto x = g.getNode(id) ) + return *x; + + auto y = std::make_shared>(id, n); + g.addNode(y); + return y; +} + +void CFG::add_edge(NodeP from, NodeP to) { + if ( ! from || ! to ) + return; + + if ( const auto& xs = g.outEdges(from); + xs.end() != std::find_if(xs.begin(), xs.end(), [&](const auto& e) { return e->getNodePair().second == to; }) ) + return; + else { + auto e = + std::make_shared>(g.getEdgeSet().size(), std::move(from), std::move(to)); + g.addEdge(std::move(e)); + return; + } +} + +std::string CFG::dot() const { + std::stringstream ss; + + ss << "digraph {\n"; + + for ( auto&& n : g.getNodeSet() ) { + auto&& data = n->getData(); + if ( auto&& meta = data->tryAs() ) { + if ( data->isA() ) + ss << util::fmt("\t%s [label=start shape=Mdiamond];\n", n->getId()); + + else if ( data->isA() ) + ss << util::fmt("\t%s [label=end shape=Msquare];\n", n->getId()); + + else if ( data->isA() ) + ss << util::fmt("\t%s [shape=point];\n", n->getId()); + + else + util::cannotBeReached(); + } + + else + ss << util::fmt("\t%s [label=\"%s\"];\n", n->getId(), rt::escapeUTF8(data->print(), true)); + } + + for ( auto&& e : g.getEdgeSet() ) { + auto&& [from, to] = e->getNodePair(); + ss << util::fmt("\t%s -> %s [label=\"%s\"];\n", from->getId(), to->getId(), e->getId()); + } + + ss << "}"; + + return ss.str(); +} + +CXXGraph::T_NodeSet CFG::unreachable_nodes() const { + auto xs = nodes(); + + // We cannot use `inOutEdges` to get a list of unreachable non-meta nodes + // since it is completely broken for directed graphs, + // https://github.com/ZigRazor/CXXGraph/issues/406. + + std::unordered_set has_in_edge; + for ( auto&& e : g.getEdgeSet() ) { + auto&& [_, to] = e->getNodePair(); + has_in_edge.insert(to->getId()); + } + + CXXGraph::T_NodeSet result; + for ( auto&& n : xs ) { + auto&& data = n->getData(); + if ( data && (! has_in_edge.count(n->getId()) && ! data->isA()) ) + result.insert(n); + } + + return result; +} + +} // namespace detail::cfg + +} // namespace hilti diff --git a/hilti/toolchain/src/compiler/optimizer.cc b/hilti/toolchain/src/compiler/optimizer.cc index 9741cd7a3..16887b965 100644 --- a/hilti/toolchain/src/compiler/optimizer.cc +++ b/hilti/toolchain/src/compiler/optimizer.cc @@ -2,7 +2,6 @@ #include "hilti/compiler/detail/optimizer.h" -#include #include #include #include @@ -16,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +36,7 @@ #include #include #include +#include namespace hilti { @@ -89,7 +90,7 @@ class OptimizerVisitor : public visitor::MutatingPreOrder { virtual bool prune_uses(Node*) { return false; } virtual bool prune_decls(Node*) { return false; } - void operator()(declaration::Module* n) final { _current_module = n; } + void operator()(declaration::Module* n) override { _current_module = n; } }; struct FunctionVisitor : OptimizerVisitor { @@ -1392,6 +1393,47 @@ struct MemberVisitor : OptimizerVisitor { } }; +struct FunctionBodyVisitor : OptimizerVisitor { + using OptimizerVisitor::OptimizerVisitor; + + bool prune_uses(Node* node) override { + visitor::visit(*this, node); + return isModified(); + } + + void visit_body(Statement* body) { + auto cfg = detail::cfg::CFG(body); + for ( auto&& n : cfg.unreachable_nodes() ) { + const auto& data = n->getData(); + + if ( data->isA() && data->hasParent() ) + removeNode(data, "unreachable code"); + + else if ( data->isA() ) { + auto* p = data->parent(); + + while ( p && ! p->isA() ) + p = p->parent(); + + if ( p && p->hasParent() ) + removeNode(p, "unreachable code"); + } + } + } + + void operator()(declaration::Function* f) override { + if ( auto&& body = f->function()->body() ) + visit_body(body); + } + + void operator()(declaration::Module* m) override { + OptimizerVisitor::operator()(m); + + if ( auto&& body = m->statements() ) + visit_body(body); + } +}; + void detail::optimizer::optimize(Builder* builder, ASTRoot* root) { util::timing::Collector _("hilti/compiler/optimizer"); @@ -1410,14 +1452,17 @@ void detail::optimizer::optimize(Builder* builder, ASTRoot* root) { v.transform(root); } - const std::map()>> creators = - {{"constant_folding", - [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, - {"functions", - [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, - {"members", - [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, - {"types", [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}}; + const std::map()>> creators = { + {"constant_folding", + [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, + {"functions", + [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, + {"members", + [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, + {"types", [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, + {"cfg", + [&builder]() { return std::make_unique(builder, hilti::logging::debug::Optimizer); }}, + }; // If no user-specified passes are given enable all of them. if ( ! passes ) { diff --git a/hilti/toolchain/tests/cfg.cc b/hilti/toolchain/tests/cfg.cc new file mode 100644 index 000000000..27266e724 --- /dev/null +++ b/hilti/toolchain/tests/cfg.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace hilti; +using namespace hilti::detail::cfg; + +auto sorted_edges(const CFG& g) { + auto edges = std::vector(g.edges().begin(), g.edges().end()); + std::sort(edges.begin(), edges.end(), [](auto&& a, auto&& b) { return a->getId() < b->getId(); }); + return edges; +} + +/// Helper function to dump a cfg to a file. +// FIXME(bbannier): remove this. +void save_as(const CFG& g, const char* filename) { + std::fstream f(filename, std::fstream::out); + f << g.dot(); +} + +TEST_SUITE_BEGIN("cfg"); + +TEST_CASE("unreachable statements") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addReturn(); + builder.addExpression(builder.expression(builder.ctorString("foo1", true))); + builder.addExpression(builder.expression(builder.ctorString("foo2", true))); + + auto&& ast = builder.block(); + + const auto cfg = CFG(ast); + + auto nodes = cfg.nodes(); + auto foo1 = + std::find_if(nodes.begin(), nodes.end(), [](const auto& n) { return n->getData()->print() == "\"foo1\";"; }); + REQUIRE_NE(foo1, nodes.end()); + auto foo2 = + std::find_if(nodes.begin(), nodes.end(), [](const auto& n) { return n->getData()->print() == "\"foo2\";"; }); + REQUIRE_NE(foo2, nodes.end()); + + auto unreachable = cfg.unreachable_nodes(); + CHECK_EQ(unreachable.size(), 1); + // Node `foo1` is unreachable since it has no parent. + CHECK(unreachable.count(*foo1)); + // Node `foo2` is reachable from `foo1`. + CHECK_FALSE(unreachable.count(*foo2)); +} + +TEST_CASE("build empty") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + REQUIRE_EQ(cfg.edges().size(), 1); + auto&& [begin, end] = cfg.edges().begin()->get()->getNodePair(); + CHECK(begin->getData()->isA()); + CHECK(end->getData()->isA()); + + CHECK_EQ(cfg.nodes().size(), 2); +} + +TEST_CASE("single statement") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addExpression(builder.expressionVoid()); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + auto&& edges = sorted_edges(cfg); + REQUIRE_EQ(edges.size(), 2); + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), ";"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), ";"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); + + auto&& nodes = cfg.nodes(); + CHECK_EQ(nodes.size(), 3); +} + +TEST_CASE("two statements") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addExpression(builder.expressionVoid()); + builder.addExpression(builder.expressionCtor(builder.ctorBool(false))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + + REQUIRE_EQ(edges.size(), 3); + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), ";"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), ";"); + CHECK_EQ(to->getData()->print(), "False;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "False;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); + + auto&& nodes = cfg.nodes(); + CHECK_EQ(nodes.size(), 4); +} + +TEST_CASE("three statements") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addExpression(builder.expressionVoid()); + builder.addExpression(builder.expressionCtor(builder.ctorBool(false))); + builder.addExpression(builder.expressionCtor(builder.ctorSignedInteger(0, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + + REQUIRE_EQ(edges.size(), 4); + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), ";"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), ";"); + CHECK_EQ(to->getData()->print(), "False;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "False;"); + CHECK_EQ(to->getData()->print(), "0;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "0;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); + + auto&& nodes = cfg.nodes(); + CHECK_EQ(nodes.size(), 5); +} + +struct TestBuilder : public hilti::ExtendedBuilderTemplate { + using hilti::ExtendedBuilderTemplate::ExtendedBuilderTemplate; +}; + +TEST_CASE("while") { + auto ctx = std::make_unique(nullptr); + auto builder = TestBuilder(ctx.get()); + auto while_ = builder.addWhile(builder.expression(builder.ctorBool(true))); + while_->addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(2, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 5); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "True"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK_EQ(to->getData()->print(), "True"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "2;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("while_else") { + auto ctx = std::make_unique(nullptr); + auto builder = TestBuilder(ctx.get()); + auto [while_, else_] = builder.addWhileElse(builder.expression(builder.ctorBool(true))); + while_->addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + else_->addExpression(builder.expression(builder.ctorUnsignedInteger(0, 64))); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(2, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 8); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "True"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK_EQ(to->getData()->print(), "True"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "0;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "0;"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "2;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("if") { + auto ctx = std::make_unique(nullptr); + auto builder = TestBuilder(ctx.get()); + auto if_ = builder.addIf(builder.expression(builder.ctorBool(true))); + if_->addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(2, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 4); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "True"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK_EQ(to->getData()->print(), "2;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("if_else") { + auto ctx = std::make_unique(nullptr); + auto builder = TestBuilder(ctx.get()); + auto [if_, else_] = builder.addIfElse(builder.expression(builder.ctorBool(true))); + if_->addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + else_->addExpression(builder.expression(builder.ctorUnsignedInteger(0, 64))); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(2, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 7); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "True"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "True"); + CHECK_EQ(to->getData()->print(), "0;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(to->getData()->isA()); + CHECK_EQ(from->getData()->print(), "0;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(to->getData()->isA()); + CHECK_EQ(from->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "2;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("try_catch") { + auto ctx = std::make_unique(nullptr); + auto builder = TestBuilder(ctx.get()); + auto [try_, catch_] = builder.addTry(); + try_->addExpression(builder.expression(builder.ctorString("try", true))); + catch_.addCatch()->addExpression(builder.expression(builder.ctorString("catch1", true))); + catch_.addCatch()->addExpression(builder.expression(builder.ctorString("catch2", true))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 7); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "\"try\";"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "\"try\";"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "\"catch1\";"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "\"catch1\";"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "\"catch2\";"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "\"catch2\";"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("return") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + builder.addReturn(); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 2); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("return nothing") { + // This is a test for the case for flows with `return` with no expression, + // i.e., it returns nothing, not even a void. + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + builder.addReturn(); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(2, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 4); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("return multiple") { + auto ctx = std::make_unique(nullptr); + auto builder = TestBuilder(ctx.get()); + builder.addExpression(builder.expression((builder.ctorUnsignedInteger(1, 64)))); + builder.addReturn(builder.ctorString("return1", true)); + builder.addExpression(builder.expression((builder.ctorUnsignedInteger(2, 64)))); + builder.addReturn(builder.ctorString("return2", true)); + builder.addExpression(builder.expression((builder.ctorUnsignedInteger(3, 64)))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 8); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK_EQ(to->getData()->print(), "\"return1\""); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK_EQ(to->getData()->print(), "\"return2\""); + } + + CXXGraph::id_t mix1; + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "\"return2\""); + CHECK(to->getData()->isA()); + mix1 = to->getId(); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "3;"); + CHECK(to->getData()->isA()); + CHECK_EQ(to->getId(), mix1); + } + + CXXGraph::id_t mix2; + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "\"return1\""); + CHECK(to->getData()->isA()); + mix2 = to->getId(); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getId(), mix1); + CHECK_EQ(to->getId(), mix2); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getId(), mix2); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_CASE("throw") { + auto ctx = std::make_unique(nullptr); + auto builder = hilti::Builder(ctx.get()); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(1, 64))); + builder.addThrow(builder.expression(builder.ctorString("throw", true))); + builder.addExpression(builder.expression(builder.ctorUnsignedInteger(2, 64))); + auto&& ast = builder.block(); + const auto cfg = CFG(ast); + + const auto edges = sorted_edges(cfg); + CHECK_EQ(edges.size(), 5); + + auto it = edges.begin(); + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK_EQ(to->getData()->print(), "1;"); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "1;"); + CHECK_EQ(to->getData()->print(), "\"throw\""); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "\"throw\""); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK_EQ(from->getData()->print(), "2;"); + CHECK(to->getData()->isA()); + } + + { + auto&& [from, to] = (it++)->get()->getNodePair(); + CHECK(from->getData()->isA()); + CHECK(to->getData()->isA()); + } + + CHECK_EQ(it, edges.end()); +} + +TEST_SUITE_END(); diff --git a/tests/Baseline/hilti.optimization.unreachable-code/log b/tests/Baseline/hilti.optimization.unreachable-code/log new file mode 100644 index 000000000..4219aee54 --- /dev/null +++ b/tests/Baseline/hilti.optimization.unreachable-code/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] [unreachable-code.hlt:16:5-16:32] statement::Expression "hilti::print("after1", True);" -> null (unreachable code) +[debug/optimizer] [unreachable-code.hlt:17:5-17:32] statement::Expression "hilti::print("after2", True);" -> null (unreachable code) +[debug/optimizer] [unreachable-code.hlt:23:5-26:36] statement::If "if ( x ) { hilti::print("True", True); } else { hilti::print("False", True); }" -> null (unreachable code) diff --git a/tests/Baseline/hilti.optimization.unreachable-code/noopt.hlt b/tests/Baseline/hilti.optimization.unreachable-code/noopt.hlt new file mode 100644 index 000000000..2e4135584 --- /dev/null +++ b/tests/Baseline/hilti.optimization.unreachable-code/noopt.hlt @@ -0,0 +1,22 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +module bla { + +import hilti; + +public function void foo() { + return; + hilti::print("after1", True); + hilti::print("after2", True); +} + +public function void bar(bool x) { + return; + + if ( x ) + hilti::print("True", True); + else + hilti::print("False", True); + +} + +} diff --git a/tests/Baseline/hilti.optimization.unreachable-code/opt.hlt b/tests/Baseline/hilti.optimization.unreachable-code/opt.hlt new file mode 100644 index 000000000..b8396e603 --- /dev/null +++ b/tests/Baseline/hilti.optimization.unreachable-code/opt.hlt @@ -0,0 +1,14 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +module bla { + +import hilti; + +public function void foo() { + return; +} + +public function void bar(bool x) { + return; +} + +} diff --git a/tests/hilti/optimization/unreachable-code.hlt b/tests/hilti/optimization/unreachable-code.hlt new file mode 100644 index 000000000..7868061a9 --- /dev/null +++ b/tests/hilti/optimization/unreachable-code.hlt @@ -0,0 +1,29 @@ +# @TEST-EXEC: 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 -k 3 > log +# @TEST-EXEC: btest-diff opt.hlt +# @TEST-EXEC: btest-diff log +# +# @TEST-DOC: Tests optimizations performing dead code removal. + +module bla { + +import hilti; + +public function void foo() { + return; + hilti::print("after1", True); + hilti::print("after2", True); +} + +public function void bar(bool x) { + return; + + if ( x ) + hilti::print("True", True); + else + hilti::print("False", True); +} + +}