// See the file "COPYING" in the main distribution directory for copyright. // Optimization-related methods for Stmt classes. #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) ) { auto null = make_intrusive(); null->SetOriginal(this_ptr); return null; } return DoReduce(c); } StmtPtr Stmt::TransformMe(StmtPtr 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(); for ( auto i = 0; i < e.length(); ++i ) 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 = make_intrusive(); 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() { return SetSucc(new ExprStmt(e ? e->Duplicate() : nullptr)); } void ExprStmt::Inline(Inliner* inl) { if ( e ) 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(make_intrusive(), c); auto t = e->Tag(); if ( t == EXPR_NOP ) return TransformMe(make_intrusive(), c); if ( c->Optimizing() ) { e = c->OptExpr(e); return ThisPtr(); } if ( e->IsSingleton(c) ) // No point evaluating. return TransformMe(make_intrusive(), 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 = make_intrusive(red_e_stmt, ThisPtr()); return TransformMe(s, c); } else return ThisPtr(); } StmtPtr IfStmt::Duplicate() { return SetSucc(new IfStmt(e->Duplicate(), s1->Duplicate(), s2->Duplicate())); } void IfStmt::Inline(Inliner* inl) { ExprStmt::Inline(inl); if ( s1 ) s1->Inline(inl); if ( s2 ) 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(make_intrusive(), 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(make_intrusive(red_e_stmt, s1), c); else return TransformMe(make_intrusive(red_e_stmt, s2), c); } if ( red_e_stmt ) return TransformMe(make_intrusive(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() { if ( expr_cases ) { auto new_exprs = expr_cases->Duplicate()->AsListExprPtr(); return make_intrusive(new_exprs, type_cases, s->Duplicate()); } else return make_intrusive(nullptr, type_cases, s->Duplicate()); } StmtPtr SwitchStmt::Duplicate() { auto new_cases = new case_list; loop_over_list(*cases, i) new_cases->append((*cases)[i]->Duplicate().release()); return SetSucc(new SwitchStmt(e->Duplicate(), new_cases)); } void SwitchStmt::Inline(Inliner* inl) { ExprStmt::Inline(inl); for ( auto c : *cases ) // In principle this can do the operation multiple times // for a given body, but that's no big deal as repeated // calls won't do anything. 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 = make_intrusive(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 = make_intrusive(red_e_stmt, ThisPtr()); return TransformMe(s, c); } else return ThisPtr(); } StmtPtr AddStmt::Duplicate() { return SetSucc(new AddStmt(e->Duplicate())); } StmtPtr DelStmt::Duplicate() { return SetSucc(new DelStmt(e->Duplicate())); } 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 = make_intrusive(red_e_stmt, ThisPtr()); return TransformMe(s, c); } } return ThisPtr(); } StmtPtr WhileStmt::Duplicate() { return SetSucc(new WhileStmt(loop_condition->Duplicate(), body->Duplicate())); } void WhileStmt::Inline(Inliner* inl) { loop_condition = loop_condition->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() { auto expr_copy = e->Duplicate(); auto new_loop_vars = new zeek::IDPList; loop_over_list(*loop_vars, i) { auto id = (*loop_vars)[i]; zeek::Ref(id); new_loop_vars->append(id); } ForStmt* f; if ( value_var ) f = new ForStmt(new_loop_vars, expr_copy, value_var); else f = new ForStmt(new_loop_vars, expr_copy); f->AddBody(body->Duplicate()); return SetSucc(f); } void ForStmt::Inline(Inliner* inl) { ExprStmt::Inline(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(make_intrusive(red_e_stmt, this), c); return ThisPtr(); } StmtPtr ReturnStmt::Duplicate() { return SetSucc(new ReturnStmt(e ? e->Duplicate() : nullptr, true)); } ReturnStmt::ReturnStmt(ExprPtr arg_e, bool ignored) : ExprStmt(STMT_RETURN, std::move(arg_e)) { } 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 = make_intrusive(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() { auto new_sl = new StmtList(); for ( auto& stmt : Stmts() ) new_sl->Stmts().push_back(stmt->Duplicate().release()); return SetSucc(new_sl); } void StmtList::Inline(Inliner* inl) { for ( const auto& stmt : Stmts() ) 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(make_intrusive(), 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.release()); // 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() { // Need to duplicate the initializer list since later reductions // can modify it in place. std::vector new_inits; for ( auto id : inits ) new_inits.push_back(id); 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() { auto cond_d = cond->Duplicate(); auto s1_d = s1->Duplicate(); auto s2_d = s2 ? s2->Duplicate() : nullptr; auto timeout_d = timeout ? timeout->Duplicate() : nullptr; return SetSucc(new WhenStmt(cond_d, s1_d, s2_d, timeout_d, is_return)); } void WhenStmt::Inline(Inliner* inl) { // Don't inline, since we currently don't correctly capture // 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) { 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) { RegisterAccess(); flow = FLOW_NEXT; auto& v = e->Eval(f)->AsListVal()->Vals(); if ( v.size() != static_cast(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