diff --git a/src/Stmt.cc b/src/Stmt.cc index 58c1d712f8..671e70a488 100644 --- a/src/Stmt.cc +++ b/src/Stmt.cc @@ -32,6 +32,8 @@ const char* stmt_name(StmtTag t) "for", "next", "break", "return", "add", "delete", "list", "bodylist", "", "fallthrough", "while", + "catch-return", + "check-any-length", "null", }; @@ -94,6 +96,18 @@ const SwitchStmt* Stmt::AsSwitchStmt() const return (const SwitchStmt*) this; } +const ExprStmt* Stmt::AsExprStmt() const + { + CHECK_TAG(tag, STMT_EXPR, "Stmt::AsExprStmt", stmt_name) + return (const ExprStmt*) this; + } + +const ReturnStmt* Stmt::AsReturnStmt() const + { + CHECK_TAG(tag, STMT_RETURN, "Stmt::AsReturnStmt", stmt_name) + return (const ReturnStmt*) this; + } + bool Stmt::SetLocationInfo(const Location* start, const Location* end) { if ( ! Obj::SetLocationInfo(start, end) ) @@ -354,6 +368,11 @@ ExprStmt::ExprStmt(StmtTag t, ExprPtr arg_e) : Stmt(t), e(std::move(arg_e)) ExprStmt::~ExprStmt() = default; +ExprPtr ExprStmt::StmtExprPtr() const + { + return e; + } + ValPtr ExprStmt::Exec(Frame* f, StmtFlowType& flow) const { RegisterAccess(); @@ -961,17 +980,37 @@ TraversalCode SwitchStmt::Traverse(TraversalCallback* cb) const HANDLE_TC_STMT_POST(tc); } -AddStmt::AddStmt(ExprPtr arg_e) : ExprStmt(STMT_ADD, std::move(arg_e)) + +AddDelStmt::AddDelStmt(StmtTag t, ExprPtr arg_e) +: ExprStmt(t, std::move(arg_e)) + { + } + +bool AddDelStmt::IsPure() const + { + return false; + } + +TraversalCode AddDelStmt::Traverse(TraversalCallback* cb) const + { + TraversalCode tc = cb->PreStmt(this); + HANDLE_TC_STMT_PRE(tc); + + // Argument is stored in base class's "e" field. + tc = e->Traverse(cb); + HANDLE_TC_STMT_PRE(tc); + + tc = cb->PostStmt(this); + HANDLE_TC_STMT_POST(tc); + } + + +AddStmt::AddStmt(ExprPtr arg_e) : AddDelStmt(STMT_ADD, std::move(arg_e)) { if ( ! e->CanAdd() ) Error("illegal add statement"); } -bool AddStmt::IsPure() const - { - return false; - } - ValPtr AddStmt::Exec(Frame* f, StmtFlowType& flow) const { RegisterAccess(); @@ -981,20 +1020,7 @@ ValPtr AddStmt::Exec(Frame* f, StmtFlowType& flow) const } -TraversalCode AddStmt::Traverse(TraversalCallback* cb) const - { - TraversalCode tc = cb->PreStmt(this); - HANDLE_TC_STMT_PRE(tc); - - // Argument is stored in base class's "e" field. - tc = e->Traverse(cb); - HANDLE_TC_STMT_PRE(tc); - - tc = cb->PostStmt(this); - HANDLE_TC_STMT_POST(tc); - } - -DelStmt::DelStmt(ExprPtr arg_e) : ExprStmt(STMT_DELETE, std::move(arg_e)) +DelStmt::DelStmt(ExprPtr arg_e) : AddDelStmt(STMT_DELETE, std::move(arg_e)) { if ( e->IsError() ) return; @@ -1003,11 +1029,6 @@ DelStmt::DelStmt(ExprPtr arg_e) : ExprStmt(STMT_DELETE, std::move(arg_e)) Error("illegal delete statement"); } -bool DelStmt::IsPure() const - { - return false; - } - ValPtr DelStmt::Exec(Frame* f, StmtFlowType& flow) const { RegisterAccess(); @@ -1016,18 +1037,6 @@ ValPtr DelStmt::Exec(Frame* f, StmtFlowType& flow) const return nullptr; } -TraversalCode DelStmt::Traverse(TraversalCallback* cb) const - { - TraversalCode tc = cb->PreStmt(this); - HANDLE_TC_STMT_PRE(tc); - - // Argument is stored in base class's "e" field. - tc = e->Traverse(cb); - HANDLE_TC_STMT_PRE(tc); - - tc = cb->PostStmt(this); - HANDLE_TC_STMT_POST(tc); - } EventStmt::EventStmt(EventExprPtr arg_e) : ExprStmt(STMT_EVENT, arg_e), event_expr(std::move(arg_e)) @@ -1060,10 +1069,10 @@ TraversalCode EventStmt::Traverse(TraversalCallback* cb) const HANDLE_TC_STMT_POST(tc); } -WhileStmt::WhileStmt(ExprPtr arg_loop_condition, - StmtPtr arg_body) +WhileStmt::WhileStmt(ExprPtr arg_loop_condition, StmtPtr arg_body) : Stmt(STMT_WHILE), - loop_condition(std::move(arg_loop_condition)), body(std::move(arg_body)) + loop_condition(std::move(arg_loop_condition)), + body(std::move(arg_body)) { if ( ! loop_condition->IsError() && ! IsBool(loop_condition->GetType()->Tag()) ) @@ -1119,6 +1128,9 @@ ValPtr WhileStmt::Exec(Frame* f, StmtFlowType& flow) const for ( ; ; ) { + if ( loop_cond_pred_stmt ) + loop_cond_pred_stmt->Exec(f, flow); + auto cond = loop_condition->Eval(f); if ( ! cond ) @@ -1568,12 +1580,15 @@ void ReturnStmt::StmtDescribe(ODesc* d) const StmtList::StmtList() : Stmt(STMT_LIST) { + stmts = new StmtPList; } StmtList::~StmtList() { - for ( const auto& stmt : stmts ) + for ( const auto& stmt : Stmts() ) Unref(stmt); + + delete stmts; } ValPtr StmtList::Exec(Frame* f, StmtFlowType& flow) const @@ -1581,7 +1596,7 @@ ValPtr StmtList::Exec(Frame* f, StmtFlowType& flow) const RegisterAccess(); flow = FLOW_NEXT; - for ( const auto& stmt : stmts ) + for ( const auto& stmt : Stmts() ) { f->SetNextStmt(stmt); @@ -1604,7 +1619,7 @@ ValPtr StmtList::Exec(Frame* f, StmtFlowType& flow) const bool StmtList::IsPure() const { - for ( const auto& stmt : stmts ) + for ( const auto& stmt : Stmts() ) if ( ! stmt->IsPure() ) return false; return true; @@ -1615,10 +1630,10 @@ void StmtList::StmtDescribe(ODesc* d) const if ( ! d->IsReadable() ) { AddTag(d); - d->AddCount(stmts.length()); + d->AddCount(stmts->length()); } - if ( stmts.length() == 0 ) + if ( stmts->length() == 0 ) DescribeDone(d); else @@ -1629,7 +1644,7 @@ void StmtList::StmtDescribe(ODesc* d) const d->NL(); } - for ( const auto& stmt : stmts ) + for ( const auto& stmt : Stmts() ) { stmt->Describe(d); d->NL(); @@ -1645,7 +1660,7 @@ TraversalCode StmtList::Traverse(TraversalCallback* cb) const TraversalCode tc = cb->PreStmt(this); HANDLE_TC_STMT_PRE(tc); - for ( const auto& stmt : stmts ) + for ( const auto& stmt : Stmts() ) { tc = stmt->Traverse(cb); HANDLE_TC_STMT_PRE(tc); diff --git a/src/Stmt.h b/src/Stmt.h index 931e97834f..fa07546a4a 100644 --- a/src/Stmt.h +++ b/src/Stmt.h @@ -26,6 +26,9 @@ public: // Optimization-related: void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + protected: ExprListStmt(StmtTag t, ListExprPtr arg_l); @@ -38,6 +41,12 @@ protected: void StmtDescribe(ODesc* d) const override; ListExprPtr l; + + // Optimization-related: + + // Returns a new version of the original derived object + // based on the given list of singleton expressions. + virtual StmtPtr DoSubclassReduce(ListExprPtr singletons, Reducer* c) = 0; }; class PrintStmt final : public ExprListStmt { @@ -51,6 +60,9 @@ public: protected: ValPtr DoExec(std::vector vals, StmtFlowType& flow) const override; + + // Optimization-related: + StmtPtr DoSubclassReduce(ListExprPtr singletons, Reducer* c) override; }; class ExprStmt : public Stmt { @@ -58,9 +70,15 @@ public: explicit ExprStmt(ExprPtr e); ~ExprStmt() override; + // This constructor is only meant for internal use, but it's + // not protected since ExprPtr's mask the actual caller, + // not allowing us to use "friend" for protected access. + ExprStmt(StmtTag t, ExprPtr e); + ValPtr Exec(Frame* f, StmtFlowType& flow) const override; const Expr* StmtExpr() const { return e.get(); } + ExprPtr StmtExprPtr() const; void StmtDescribe(ODesc* d) const override; @@ -70,9 +88,10 @@ public: StmtPtr Duplicate() override; void Inline(Inliner* inl) override; -protected: - ExprStmt(StmtTag t, ExprPtr e); + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; +protected: virtual ValPtr DoExec(Frame* f, Val* v, StmtFlowType& flow) const; bool IsPure() const override; @@ -96,6 +115,11 @@ public: StmtPtr Duplicate() override; void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + + bool NoFlowAfter(bool ignore_break) const override; + protected: ValPtr DoExec(Frame* f, Val* v, StmtFlowType& flow) const override; bool IsPure() const override; @@ -118,6 +142,8 @@ public: const Stmt* Body() const { return s.get(); } Stmt* Body() { return s.get(); } + void UpdateBody(StmtPtr new_body) { s = new_body; } + void Describe(ODesc* d) const override; TraversalCode Traverse(TraversalCallback* cb) const; @@ -148,6 +174,11 @@ public: StmtPtr Duplicate() override; void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + + bool NoFlowAfter(bool ignore_break) const override; + protected: ValPtr DoExec(Frame* f, Val* v, StmtFlowType& flow) const override; bool IsPure() const override; @@ -178,28 +209,38 @@ protected: std::vector> case_label_type_list; }; -class AddStmt final : public ExprStmt { +// Helper class. Added for script optimization, but it makes sense +// in terms of factoring even without. +class AddDelStmt : public ExprStmt { +public: + TraversalCode Traverse(TraversalCallback* cb) const override; + + bool IsPure() const override; + + // Optimization-related: + StmtPtr DoReduce(Reducer* c) override; + bool IsReduced(Reducer* c) const override; + +protected: + AddDelStmt(StmtTag t, ExprPtr arg_e); +}; + +class AddStmt final : public AddDelStmt { public: explicit AddStmt(ExprPtr e); - bool IsPure() const override; ValPtr Exec(Frame* f, StmtFlowType& flow) const override; - TraversalCode Traverse(TraversalCallback* cb) const override; - // Optimization-related: StmtPtr Duplicate() override; }; -class DelStmt final : public ExprStmt { +class DelStmt final : public AddDelStmt { public: explicit DelStmt(ExprPtr e); - bool IsPure() const override; ValPtr Exec(Frame* f, StmtFlowType& flow) const override; - TraversalCode Traverse(TraversalCallback* cb) const override; - // Optimization-related: StmtPtr Duplicate() override; }; @@ -215,6 +256,8 @@ public: // Optimization-related: StmtPtr Duplicate() override; + StmtPtr DoReduce(Reducer* c) override; + protected: EventExprPtr event_expr; }; @@ -232,11 +275,20 @@ public: TraversalCode Traverse(TraversalCallback* cb) const override; // Optimization-related: - const Stmt* CondStmt() const - { return loop_cond_stmt ? loop_cond_stmt.get() : nullptr; } + StmtPtr CondPredStmt() const + { return loop_cond_pred_stmt ? loop_cond_pred_stmt : nullptr; } StmtPtr Duplicate() override; void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + + // Note, no need for a NoFlowAfter method because the loop might + // execute zero times, so it's always the default of "false". + + const StmtPtr ConditionAsStmt() const + { return stmt_loop_condition; } + protected: ValPtr Exec(Frame* f, StmtFlowType& flow) const override; @@ -246,8 +298,14 @@ protected: // Optimization-related member variables. // When in reduced form, the following holds a statement (which - // might be a block) for evaluating the loop's conditional. - StmtPtr loop_cond_stmt = nullptr; + // might be a block) that's a *predecessor* necessary for evaluating + // the loop's conditional. + StmtPtr loop_cond_pred_stmt = nullptr; + + // When reducing, we create a *statement* associated with + // evaluating the reduced conditional, as well as the reduced + // expression. This turns out to be useful in propagating RDs/UDs. + StmtPtr stmt_loop_condition = nullptr; }; class ForStmt final : public ExprStmt { @@ -274,6 +332,12 @@ public: StmtPtr Duplicate() override; void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + + // Note, no need for a NoFlowAfter method because the loop might + // execute zero times, so it's always the default of "false". + protected: ValPtr DoExec(Frame* f, Val* v, StmtFlowType& flow) const override; @@ -297,6 +361,9 @@ public: // Optimization-related: StmtPtr Duplicate() override { return SetSucc(new NextStmt()); } + + bool NoFlowAfter(bool ignore_break) const override + { return true; } protected: }; @@ -314,6 +381,9 @@ public: // Optimization-related: StmtPtr Duplicate() override { return SetSucc(new BreakStmt()); } + bool NoFlowAfter(bool ignore_break) const override + { return ! ignore_break; } + protected: }; @@ -349,6 +419,12 @@ public: // Constructor used for duplication, when we've already done // all of the type-checking. ReturnStmt(ExprPtr e, bool ignored); + + // Optimization-related: + StmtPtr DoReduce(Reducer* c) override; + + bool NoFlowAfter(bool ignore_break) const override + { return true; } }; class StmtList : public Stmt { @@ -358,8 +434,8 @@ public: ValPtr Exec(Frame* f, StmtFlowType& flow) const override; - const StmtPList& Stmts() const { return stmts; } - StmtPList& Stmts() { return stmts; } + const StmtPList& Stmts() const { return *stmts; } + StmtPList& Stmts() { return *stmts; } void StmtDescribe(ODesc* d) const override; @@ -369,6 +445,11 @@ public: StmtPtr Duplicate() override; void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + + bool NoFlowAfter(bool ignore_break) const override; + // Idioms commonly used in reduction. StmtList(StmtPtr s1, Stmt* s2); StmtList(StmtPtr s1, StmtPtr s2); @@ -377,7 +458,16 @@ public: protected: bool IsPure() const override; - StmtPList stmts; + StmtPList* stmts; + + // Optimization-related: + bool ReduceStmt(int& s_i, StmtPList* f_stmts, Reducer* c); + + void ResetStmts(StmtPList* new_stmts) + { + delete stmts; + stmts = new_stmts; + } }; class InitStmt final : public Stmt { @@ -396,6 +486,9 @@ public: // Optimization-related: StmtPtr Duplicate() override; + bool IsReduced(Reducer* c) const override; + StmtPtr DoReduce(Reducer* c) override; + protected: std::vector inits; }; @@ -439,6 +532,8 @@ public: StmtPtr Duplicate() override; void Inline(Inliner* inl) override; + bool IsReduced(Reducer* c) const override; + protected: ExprPtr cond; StmtPtr s1; diff --git a/src/StmtEnums.h b/src/StmtEnums.h index 7847206a76..70a3983718 100644 --- a/src/StmtEnums.h +++ b/src/StmtEnums.h @@ -18,6 +18,8 @@ enum StmtTag { STMT_INIT, STMT_FALLTHROUGH, STMT_WHILE, + STMT_CATCH_RETURN, // for reduced InlineExpr's + STMT_CHECK_ANY_LEN, // internal reduced statement STMT_NULL #define NUM_STMTS (int(STMT_NULL) + 1) }; diff --git a/src/script_opt/Stmt.cc b/src/script_opt/Stmt.cc index d24f7ba9c9..f1ab0c8f6d 100644 --- a/src/script_opt/Stmt.cc +++ b/src/script_opt/Stmt.cc @@ -4,11 +4,50 @@ #include "zeek/Stmt.h" #include "zeek/Expr.h" +#include "zeek/Frame.h" +#include "zeek/Reporter.h" +#include "zeek/Desc.h" +#include "zeek/Traverse.h" +#include "zeek/script_opt/Reduce.h" namespace zeek::detail { +bool Stmt::IsReduced(Reducer* c) const + { + return true; + } + +StmtPtr Stmt::Reduce(Reducer* c) + { + auto this_ptr = ThisPtr(); + + auto repl = c->ReplacementStmt(this_ptr); + if ( repl ) + return repl; + + if ( c->ShouldOmitStmt(this_ptr) ) + { + auto null = make_intrusive(); + null->SetOriginal(this_ptr); + return null; + } + + return DoReduce(c); + } + +StmtPtr Stmt::TransformMe(Stmt* new_me, Reducer* c) + { + ASSERT(new_me != this); + + // Set the original prior to reduction, to support "original chains" + // to ultimately resolve back to the source statement. + new_me->SetOriginal(ThisPtr()); + return new_me->Reduce(c); + } + + void ExprListStmt::Inline(Inliner* inl) { auto& e = l->Exprs(); @@ -16,12 +55,70 @@ void ExprListStmt::Inline(Inliner* inl) e.replace(i, e[i]->Inline(inl).release()); } +bool ExprListStmt::IsReduced(Reducer* c) const + { + const ExprPList& e = l->Exprs(); + for ( const auto& expr : e ) + if ( ! expr->IsSingleton(c) ) + return NonReduced(expr); + + return true; + } + +StmtPtr ExprListStmt::DoReduce(Reducer* c) + { + if ( ! c->Optimizing() && IsReduced(c) ) + return ThisPtr(); + + auto new_l = make_intrusive(); + auto s = new StmtList; + + ExprPList& e = l->Exprs(); + for ( auto& expr : e ) + { + if ( c->Optimizing() ) + new_l->Append(c->OptExpr(expr)); + + else if ( expr->IsSingleton(c) ) + new_l->Append({NewRef{}, expr}); + + else + { + StmtPtr red_e_stmt; + auto red_e = expr->ReduceToSingleton(c, red_e_stmt); + new_l->Append(red_e); + + if ( red_e_stmt ) + s->Stmts().push_back(red_e_stmt.release()); + } + } + + if ( c->Optimizing() ) + { + l = new_l; + return ThisPtr(); + } + + else + { + s->Stmts().push_back(DoSubclassReduce(new_l, c).release()); + return s->Reduce(c); + } + } + StmtPtr PrintStmt::Duplicate() { return SetSucc(new PrintStmt(l->Duplicate()->AsListExprPtr())); } +StmtPtr PrintStmt::DoSubclassReduce(ListExprPtr singletons, Reducer* c) + { + auto new_me = make_intrusive(singletons); + new_me->SetOriginal(ThisPtr()); + return new_me; + } + StmtPtr ExprStmt::Duplicate() { @@ -34,6 +131,61 @@ void ExprStmt::Inline(Inliner* inl) e = e->Inline(inl); } +bool ExprStmt::IsReduced(Reducer* c) const + { + if ( ! e || e->IsReduced(c) ) + return true; + + return NonReduced(e.get()); + } + +StmtPtr ExprStmt::DoReduce(Reducer* c) + { + if ( ! e ) + // e can be nil for our derived classes (like ReturnStmt). + return TransformMe(new NullStmt, c); + + auto t = e->Tag(); + + if ( t == EXPR_NOP ) + return TransformMe(new NullStmt, c); + + if ( c->Optimizing() ) + { + e = c->OptExpr(e); + return ThisPtr(); + } + + if ( e->IsSingleton(c) ) + // No point evaluating. + return TransformMe(new NullStmt, c); + + if ( (t == EXPR_ASSIGN || t == EXPR_CALL || + t == EXPR_INDEX_ASSIGN || t == EXPR_FIELD_LHS_ASSIGN || + t == EXPR_APPEND_TO) && + e->IsReduced(c) ) + return ThisPtr(); + + StmtPtr red_e_stmt; + + if ( t == EXPR_CALL ) + // A bare call. If we reduce it regularly, if + // it has a non-void type it'll generate an + // assignment to a temporary. + red_e_stmt = e->ReduceToSingletons(c); + else + e = e->Reduce(c, red_e_stmt); + + if ( red_e_stmt ) + { + auto s = new StmtList(red_e_stmt, ThisPtr()); + return TransformMe(s, c); + } + + else + return ThisPtr(); + } + StmtPtr IfStmt::Duplicate() { @@ -51,6 +203,119 @@ void IfStmt::Inline(Inliner* inl) s2->Inline(inl); } +bool IfStmt::IsReduced(Reducer* c) const + { + if ( ! e->IsReducedConditional(c) ) + return NonReduced(e.get()); + + return s1->IsReduced(c) && s2->IsReduced(c); + } + +StmtPtr IfStmt::DoReduce(Reducer* c) + { + StmtPtr red_e_stmt; + + if ( e->WillTransformInConditional(c) ) + e = e->ReduceToConditional(c, red_e_stmt); + + // First, assess some fundamental transformations. + if ( e->Tag() == EXPR_NOT ) + { // Change "if ( ! x ) s1 else s2" to "if ( x ) s2 else s1". + auto s1_orig = s1; + s1 = s2; + s2 = s1_orig; + + e = e->GetOp1(); + } + + if ( e->Tag() == EXPR_OR_OR && c->BifurcationOkay() ) + { + c->PushBifurcation(); + + // Expand "if ( a || b ) s1 else s2" to + // "if ( a ) s1 else { if ( b ) s1 else s2 }" + auto a = e->GetOp1(); + auto b = e->GetOp2(); + + auto s1_dup = s1 ? s1->Duplicate() : nullptr; + s2 = make_intrusive(b, s1_dup, s2); + e = a; + + auto res = DoReduce(c); + c->PopBifurcation(); + return res; + } + + if ( e->Tag() == EXPR_AND_AND && c->BifurcationOkay() ) + { + c->PushBifurcation(); + + // Expand "if ( a && b ) s1 else s2" to + // "if ( a ) { if ( b ) s1 else s2 } else s2" + auto a = e->GetOp1(); + auto b = e->GetOp2(); + + auto s2_dup = s2 ? s2->Duplicate() : nullptr; + s1 = make_intrusive(b, s1, s2_dup); + e = a; + + auto res = DoReduce(c); + c->PopBifurcation(); + return res; + } + + s1 = s1->Reduce(c); + s2 = s2->Reduce(c); + + if ( s1->Tag() == STMT_NULL && s2->Tag() == STMT_NULL ) + return TransformMe(new NullStmt, c); + + if ( c->Optimizing() ) + e = c->OptExpr(e); + else + { + StmtPtr cond_red_stmt; + e = e->ReduceToConditional(c, cond_red_stmt); + + if ( red_e_stmt && cond_red_stmt ) + red_e_stmt = make_intrusive(red_e_stmt, + cond_red_stmt); + else if ( cond_red_stmt ) + red_e_stmt = cond_red_stmt; + } + + if ( e->IsConst() ) + { + auto c_e = e->AsConstExprPtr(); + auto t = c_e->Value()->AsBool(); + + if ( c->Optimizing() ) + return t ? s1 : s2; + + if ( t ) + return TransformMe(new StmtList(red_e_stmt, s1), c); + else + return TransformMe(new StmtList(red_e_stmt, s2), c); + } + + if ( red_e_stmt ) + return TransformMe(new StmtList(red_e_stmt, this), c); + + return ThisPtr(); + } + +bool IfStmt::NoFlowAfter(bool ignore_break) const + { + if ( s1 && s2 ) + return s1->NoFlowAfter(ignore_break) && + s2->NoFlowAfter(ignore_break); + + // Assuming the test isn't constant, the non-existent branch + // could be picked, so flow definitely continues afterwards. + // (Constant branches will be pruned during reduciton.) + return false; + } + IntrusivePtr Case::Duplicate() { @@ -86,6 +351,128 @@ void SwitchStmt::Inline(Inliner* inl) c->Body()->Inline(inl); } +bool SwitchStmt::IsReduced(Reducer* r) const + { + if ( ! e->IsReduced(r) ) + return NonReduced(e.get()); + + for ( const auto& c : *cases ) + { + if ( c->ExprCases() && ! c->ExprCases()->IsReduced(r) ) + return false; + + if ( c->TypeCases() && ! r->IDsAreReduced(c->TypeCases()) ) + return false; + + if ( ! c->Body()->IsReduced(r) ) + return false; + } + + return true; + } + +StmtPtr SwitchStmt::DoReduce(Reducer* rc) + { + auto s = make_intrusive(); + StmtPtr red_e_stmt; + + if ( rc->Optimizing() ) + e = rc->OptExpr(e); + else + e = e->Reduce(rc, red_e_stmt); + + // Note, the compiler checks for constant switch expressions. + + if ( red_e_stmt ) + s->Stmts().push_back(red_e_stmt.release()); + + for ( const auto& c : *cases ) + { + auto c_e = c->ExprCases(); + if ( c_e ) + { + StmtPtr c_e_stmt; + auto red_cases = c_e->Reduce(rc, c_e_stmt); + + if ( c_e_stmt ) + s->Stmts().push_back(c_e_stmt.release()); + } + + auto c_t = c->TypeCases(); + if ( c_t ) + rc->UpdateIDs(c_t); + + c->UpdateBody(c->Body()->Reduce(rc)); + } + + // Upate type cases. + for ( auto& i : case_label_type_list ) + { + IDPtr idp = {NewRef{}, i.first}; + i.first = rc->UpdateID(idp).release(); + } + + if ( s->Stmts().length() > 0 ) + { + StmtPtr me = ThisPtr(); + auto pre_and_me = new StmtList(s, me); + return TransformMe(pre_and_me, rc); + } + + return ThisPtr(); + } + +bool SwitchStmt::NoFlowAfter(bool ignore_break) const + { + bool control_reaches_end = false; + bool default_seen_with_no_flow_after = false; + + for ( const auto& c : *Cases() ) + { + if ( ! c->Body()->NoFlowAfter(true) ) + return false; + + if ( (! c->ExprCases() || + c->ExprCases()->Exprs().length() == 0) && + (! c->TypeCases() || + c->TypeCases()->length() == 0) ) + // We saw the default, and the test before this + // one established that it has no flow after it. + default_seen_with_no_flow_after = true; + } + + return default_seen_with_no_flow_after; + } + + +bool AddDelStmt::IsReduced(Reducer* c) const + { + return e->HasReducedOps(c); + } + +StmtPtr AddDelStmt::DoReduce(Reducer* c) + { + if ( c->Optimizing() ) + { + e = c->OptExpr(e); + return ThisPtr(); + } + + if ( e->Tag() != EXPR_INDEX && e->Tag() != EXPR_FIELD ) + Internal("bad \"add\"/\"delete\""); + + auto red_e_stmt = e->ReduceToSingletons(c); + + if ( red_e_stmt ) + { + auto s = new StmtList(red_e_stmt, ThisPtr()); + return TransformMe(s, c); + } + + else + return ThisPtr(); + } + StmtPtr AddStmt::Duplicate() { @@ -104,6 +491,32 @@ StmtPtr EventStmt::Duplicate() return SetSucc(new EventStmt(e->Duplicate()->AsEventExprPtr())); } +StmtPtr EventStmt::DoReduce(Reducer* c) + { + if ( c->Optimizing() ) + { + e = c->OptExpr(e); + event_expr = e->AsEventExprPtr(); + } + + else if ( ! event_expr->IsSingleton(c) ) + { + StmtPtr red_e_stmt; + auto ee_red = event_expr->Reduce(c, red_e_stmt); + + event_expr = ee_red->AsEventExprPtr(); + e = event_expr; + + if ( red_e_stmt ) + { + auto s = new StmtList(red_e_stmt, ThisPtr()); + return TransformMe(s, c); + } + } + + return ThisPtr(); + } + StmtPtr WhileStmt::Duplicate() { @@ -115,12 +528,55 @@ void WhileStmt::Inline(Inliner* inl) { loop_condition = loop_condition->Inline(inl); - if ( loop_cond_stmt ) - loop_cond_stmt->Inline(inl); + if ( loop_cond_pred_stmt ) + loop_cond_pred_stmt->Inline(inl); if ( body ) body->Inline(inl); } +bool WhileStmt::IsReduced(Reducer* c) const + { + // No need to check loop_cond_pred_stmt, as we create it reduced. + return loop_condition->IsReducedConditional(c) && body->IsReduced(c); + } + +StmtPtr WhileStmt::DoReduce(Reducer* c) + { + if ( c->Optimizing() ) + loop_condition = c->OptExpr(loop_condition); + else + { + if ( IsReduced(c) ) + { + if ( ! c->IsPruning() ) + { + // See comment below for the particulars + // of this constructor. + stmt_loop_condition = + make_intrusive(STMT_EXPR, + loop_condition); + return ThisPtr(); + } + } + else + loop_condition = loop_condition->ReduceToConditional(c, + loop_cond_pred_stmt); + } + + body = body->Reduce(c); + + // We use the more involved ExprStmt constructor here to bypass + // its check for whether the expression is being ignored, since + // we're not actually creating an ExprStmt for execution. + stmt_loop_condition = + make_intrusive(STMT_EXPR, loop_condition); + + if ( loop_cond_pred_stmt ) + loop_cond_pred_stmt = loop_cond_pred_stmt->Reduce(c); + + return ThisPtr(); + } + StmtPtr ForStmt::Duplicate() { @@ -151,6 +607,46 @@ void ForStmt::Inline(Inliner* inl) body->Inline(inl); } +bool ForStmt::IsReduced(Reducer* c) const + { + if ( ! e->IsReduced(c) ) + return NonReduced(e.get()); + + if ( ! c->IDsAreReduced(loop_vars) ) + return false; + + if ( value_var && ! c->ID_IsReduced(value_var) ) + return false; + + return body->IsReduced(c); + } + +StmtPtr ForStmt::DoReduce(Reducer* c) + { + StmtPtr red_e_stmt; + + if ( c->Optimizing() ) + e = c->OptExpr(e); + else + { + e = e->Reduce(c, red_e_stmt); + c->UpdateIDs(loop_vars); + + if ( value_var ) + value_var = c->UpdateID(value_var); + } + + body = body->Reduce(c); + + if ( body->Tag() == STMT_NULL ) + Error("empty \"for\" body leaves loop variables in indeterminant state"); + + if ( red_e_stmt ) + return TransformMe(new StmtList(red_e_stmt, this), c); + + return ThisPtr(); + } + StmtPtr ReturnStmt::Duplicate() { @@ -162,6 +658,61 @@ ReturnStmt::ReturnStmt(ExprPtr arg_e, bool ignored) { } +StmtPtr ReturnStmt::DoReduce(Reducer* c) + { + if ( ! e ) + return ThisPtr(); + + if ( c->Optimizing() ) + { + e = c->OptExpr(e); + return ThisPtr(); + } + + if ( ! e->IsSingleton(c) ) + { + StmtPtr red_e_stmt; + e = e->Reduce(c, red_e_stmt); + + if ( red_e_stmt ) + { + auto s = new StmtList(red_e_stmt, ThisPtr()); + return TransformMe(s, c); + } + } + + return ThisPtr(); + } + + +StmtList::StmtList(StmtPtr s1, Stmt* s2) : Stmt(STMT_LIST) + { + stmts = new StmtPList; + if ( s1 ) + stmts->append(s1.release()); + if ( s2 ) + stmts->append(s2); + } + +StmtList::StmtList(StmtPtr s1, StmtPtr s2) : Stmt(STMT_LIST) + { + stmts = new StmtPList; + if ( s1 ) + stmts->append(s1.release()); + if ( s2 ) + stmts->append(s2.release()); + } + +StmtList::StmtList(StmtPtr s1, StmtPtr s2, StmtPtr s3) : Stmt(STMT_LIST) + { + stmts = new StmtPList; + if ( s1 ) + stmts->append(s1.release()); + if ( s2 ) + stmts->append(s2.release()); + if ( s3 ) + stmts->append(s3.release()); + } StmtPtr StmtList::Duplicate() { @@ -179,6 +730,181 @@ void StmtList::Inline(Inliner* inl) stmt->Inline(inl); } +bool StmtList::IsReduced(Reducer* c) const + { + int n = Stmts().length(); + + for ( auto i = 0; i < n; ++i ) + { + auto& s_i = Stmts()[i]; + if ( ! s_i->IsReduced(c) ) + return false; + + if ( s_i->NoFlowAfter(false) && i < n - 1 ) + return false; + } + + return true; + } + +StmtPtr StmtList::DoReduce(Reducer* c) + { + StmtPList* f_stmts = new StmtPList; + bool did_change = false; + + int n = Stmts().length(); + + for ( auto i = 0; i < n; ++i ) + { + if ( ReduceStmt(i, f_stmts, c) ) + did_change = true; + + if ( i < n - 1 && Stmts()[i]->NoFlowAfter(false) ) + { + did_change = true; + break; + } + + if ( reporter->Errors() > 0 ) + return ThisPtr(); + } + + if ( f_stmts->length() == 0 ) + return TransformMe(new NullStmt, c); + + if ( f_stmts->length() == 1 ) + return (*f_stmts)[0]->Reduce(c); + + if ( did_change ) + { + ResetStmts(f_stmts); + return Reduce(c); + } + else + delete f_stmts; + + return ThisPtr(); + } + +bool StmtList::ReduceStmt(int& s_i, StmtPList* f_stmts, Reducer* c) + { + bool did_change = false; + auto stmt = Stmts()[s_i]->ThisPtr(); + + auto old_stmt = stmt; + + stmt = stmt->Reduce(c); + + if ( stmt != old_stmt ) + did_change = true; + + if ( c->Optimizing() && stmt->Tag() == STMT_EXPR ) + { + // There are two potential optimizations that affect + // whether we keep assignment statements. The first is + // for potential assignment chains like + // + // tmp1 = x; + // tmp2 = tmp1; + // + // where we can change this pair to simply "tmp2 = x", assuming + // no later use of tmp1. + // + // In addition, if we have "tmp1 = e" and "e" is an expression + // already computed into another temporary (say tmp0) that's + // safely usable at this point, then we can elide the tmp1 + // assignment entirely. + auto s_e = stmt->AsExprStmt(); + auto e = s_e->StmtExpr(); + + if ( e->Tag() != EXPR_ASSIGN ) + { + f_stmts->append(stmt.release()); + return false; + } + + auto a = e->AsAssignExpr(); + auto lhs = a->Op1()->AsRefExprPtr()->Op(); + + if ( lhs->Tag() != EXPR_NAME ) + { + f_stmts->append(stmt.release()); + return false; + } + + auto var = lhs->AsNameExpr(); + auto rhs = a->GetOp2(); + + if ( s_i < Stmts().length() - 1 ) + { + // See if we can compress an assignment chain. + auto& s_i_succ = Stmts()[s_i + 1]; + + // Don't reduce s_i_succ. If it's what we're + // looking for, it's already reduced. + auto merge = c->MergeStmts(var, rhs, s_i_succ); + if ( merge ) + { + f_stmts->append(merge); + + // Skip both this statement and the next, + // now that we've substituted the merge. + ++s_i; + return true; + } + } + + if ( c->IsCSE(a, var, rhs.get()) ) + { + // printf("discarding %s as unnecessary\n", obj_desc(a)); + // Skip this now unnecessary statement. + return true; + } + } + + if ( stmt->Tag() == STMT_LIST ) + { // inline the list + auto sl = stmt->AsStmtList(); + + for ( auto& sub_stmt : sl->Stmts() ) + f_stmts->append(sub_stmt->Ref()); + + did_change = true; + } + + else if ( stmt->Tag() == STMT_NULL ) + // skip it + did_change = true; + + else + // No need to Ref() because the StmtPList destructor + // doesn't Unref(), only the explict list-walking + // in the ~StmtList destructor. + f_stmts->append(stmt.release()); + + return did_change; + } + +bool StmtList::NoFlowAfter(bool ignore_break) const + { + for ( auto& s : Stmts() ) + { + // For "break" statements, if ignore_break is set then + // by construction flow *does* go to after this statement + // list. If we just used the second test below, then + // while the "break" would indicate there's flow after it, + // if there's dead code following that includes a "return", + // this would in fact be incorrect. + if ( ignore_break && s->Tag() == STMT_BREAK ) + return false; + + if ( s->NoFlowAfter(ignore_break) ) + return true; + } + + return false; + } + StmtPtr InitStmt::Duplicate() { @@ -191,6 +917,17 @@ StmtPtr InitStmt::Duplicate() return SetSucc(new InitStmt(new_inits)); } +bool InitStmt::IsReduced(Reducer* c) const + { + return c->IDsAreReduced(inits); + } + +StmtPtr InitStmt::DoReduce(Reducer* c) + { + c->UpdateIDs(inits); + return ThisPtr(); + } + StmtPtr WhenStmt::Duplicate() { @@ -208,5 +945,149 @@ void WhenStmt::Inline(Inliner* inl) // the frames of closures. } +bool WhenStmt::IsReduced(Reducer* c) const + { + // We consider these always reduced because they're not + // candidates for any further optimization. + return true; + } + + +CatchReturnStmt::CatchReturnStmt(StmtPtr _block, NameExprPtr _ret_var) + : Stmt(STMT_CATCH_RETURN) + { + block = _block; + ret_var = _ret_var; + } + +ValPtr CatchReturnStmt::Exec(Frame* f, StmtFlowType& flow) const + { + RegisterAccess(); + + auto val = block->Exec(f, flow); + + if ( flow == FLOW_RETURN ) + flow = FLOW_NEXT; + + if ( ret_var ) + f->SetElement(ret_var->Id()->Offset(), val); + + // Note, do *not* return the value! That's taken as a signal + // that a full return executed. + return nullptr; + } + +bool CatchReturnStmt::IsPure() const + { + // The ret_var is pure by construction. + return block->IsPure(); + } + +StmtPtr CatchReturnStmt::Duplicate() + { + auto rv_dup = ret_var->Duplicate(); + auto rv_dup_ptr = rv_dup->AsNameExprPtr(); + return SetSucc(new CatchReturnStmt(block->Duplicate(), rv_dup_ptr)); + } + +StmtPtr CatchReturnStmt::DoReduce(Reducer* c) + { + block = block->Reduce(c); + + if ( block->Tag() == STMT_RETURN ) + { + // The whole thing reduced to a bare return. This can + // happen due to constant propagation. + auto ret = block->AsReturnStmt(); + auto ret_e = ret->StmtExprPtr(); + + if ( ! ret_e ) + { + if ( ret_var ) + reporter->InternalError("inlining inconsistency: no return value"); + + return make_intrusive(); + } + + auto assign = make_intrusive(ret_var->Duplicate(), + ret_e->Duplicate(), + false); + assign_stmt = make_intrusive(assign); + return assign_stmt; + } + + return ThisPtr(); + } + +void CatchReturnStmt::StmtDescribe(ODesc* d) const + { + Stmt::StmtDescribe(d); + block->Describe(d); + DescribeDone(d); + } + +TraversalCode CatchReturnStmt::Traverse(TraversalCallback* cb) const + { + TraversalCode tc = cb->PreStmt(this); + HANDLE_TC_STMT_PRE(tc); + + block->Traverse(cb); + + if ( ret_var ) + ret_var->Traverse(cb); + + tc = cb->PostStmt(this); + HANDLE_TC_STMT_POST(tc); + } + + +CheckAnyLenStmt::CheckAnyLenStmt(ExprPtr arg_e, int _expected_len) + : ExprStmt(STMT_CHECK_ANY_LEN, std::move(arg_e)) + { + expected_len = _expected_len; + } + +ValPtr CheckAnyLenStmt::Exec(Frame* f, StmtFlowType& flow) const + { + RegisterAccess(); + flow = FLOW_NEXT; + + auto& v = e->Eval(f)->AsListVal()->Vals(); + + if ( v.size() != expected_len ) + reporter->ExprRuntimeError(e.get(), "mismatch in list lengths"); + + return nullptr; + } + +StmtPtr CheckAnyLenStmt::Duplicate() + { + return SetSucc(new CheckAnyLenStmt(e->Duplicate(), expected_len)); + } + +bool CheckAnyLenStmt::IsReduced(Reducer* c) const + { + return true; + } + +StmtPtr CheckAnyLenStmt::DoReduce(Reducer* c) + { + // These are created in reduced form. + return ThisPtr(); + } + +void CheckAnyLenStmt::StmtDescribe(ODesc* d) const + { + Stmt::StmtDescribe(d); + + e->Describe(d); + if ( ! d->IsBinary() ) + d->Add(".length == "); + + d->Add(expected_len); + + DescribeDone(d); + } + } // namespace zeek::detail