diff --git a/src/Type.cc b/src/Type.cc index 9b53b5b200..5377b602de 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -13,7 +13,9 @@ #include "zeek/Desc.h" #include "zeek/Expr.h" #include "zeek/Reporter.h" +#include "zeek/RunState.h" #include "zeek/Scope.h" +#include "zeek/Traverse.h" #include "zeek/Val.h" #include "zeek/Var.h" #include "zeek/module_util.h" @@ -1065,6 +1067,8 @@ public: return ZVal(v, init_type); } + bool IsDeferrable() const override { return false; } + private: detail::ExprPtr init_expr; TypePtr init_type; @@ -1080,6 +1084,12 @@ public: ZVal Generate() const override { return ZVal(new RecordVal(init_type)); } + bool IsDeferrable() const override + { + assert(! run_state::is_parsing); + return init_type->IsDeferrable(); + } + private: RecordTypePtr init_type; }; @@ -1116,6 +1126,59 @@ private: } // namespace detail +// Helper TraversalCallback optimizing RecordType instances by moving +// deferrable FieldInits from creation_inits to deferred_inits once +// parsing has completed. +class RecordType::CreationInitsOptimizer : public detail::TraversalCallback + { +public: + detail::TraversalCode PreID(const detail::ID* id) override + { + if ( const auto& t = id->GetType() ) + HANDLE_TC_TYPE_POST(t->Traverse(this)); + + return detail::TC_CONTINUE; + } + + detail::TraversalCode PreType(const Type* t) override + { + if ( analyzed_types.count(t) > 0 ) + return detail::TC_ABORTSTMT; + + analyzed_types.emplace(t); + + if ( t->Tag() == TYPE_RECORD ) + { + auto* rt = const_cast(t->AsRecordType()); + OptimizeCreationInits(rt); + } + + return detail::TC_CONTINUE; + } + +private: + void OptimizeCreationInits(RecordType* rt) + { + int i = 0; + for ( auto& ci : rt->creation_inits ) + { + if ( ! ci.second->IsDeferrable() ) + rt->creation_inits[i++] = std::move(ci); + else + { + assert(! rt->deferred_inits[ci.first]); + rt->deferred_inits[ci.first].swap(ci.second); + } + } + + // Discard remaining elements. + rt->creation_inits.resize(i); + } + + // Endless recursion avoidance. + std::unordered_set analyzed_types; + }; + RecordType::RecordType(type_decl_list* arg_types) : Type(TYPE_RECORD) { types = arg_types; @@ -1132,6 +1195,12 @@ RecordType::RecordType(type_decl_list* arg_types) : Type(TYPE_RECORD) num_orig_fields = num_fields; } +void RecordType::InitPostScript() + { + auto cb = CreationInitsOptimizer(); + detail::traverse_all(&cb); + } + // in this case the clone is actually not so shallow, since // it gets modified by everyone. TypePtr RecordType::ShallowClone() @@ -1205,7 +1274,15 @@ void RecordType::AddField(unsigned int field, const TypeDecl* td) TypeTag tag = type->Tag(); if ( tag == TYPE_RECORD ) - init = std::make_unique(cast_intrusive(type)); + { + // Initially, put record fields into creation_inits. Once parsing has + // completed, they may move into deferred_inits. See RecordType::InitPostScript() + // and RecordType::CreationInitisOptimizer. + // + // init (nil) is appended to deferred_inits as placeholder. + auto rfi = std::make_unique(cast_intrusive(type)); + creation_inits.emplace_back(field, std::move(rfi)); + } else if ( tag == TYPE_TABLE ) init = std::make_unique(cast_intrusive(type), a); @@ -1589,6 +1666,20 @@ detail::TraversalCode RecordType::Traverse(detail::TraversalCallback* cb) const HANDLE_TC_TYPE_POST(tc); } +bool RecordType::IsDeferrable() const + { + assert(! run_state::is_parsing); + auto is_deferrable = [](const auto& p) -> bool + { + return p.second->IsDeferrable(); + }; + + // If all creation_inits are deferrable, this record type is deferrable, too. + // It will be optimized later on. Note, all_of() returns true for an empty + // range, which is correct. + return std::all_of(creation_inits.begin(), creation_inits.end(), is_deferrable); + } + FileType::FileType(TypePtr yield_type) : Type(TYPE_FILE), yield(std::move(yield_type)) { } FileType::~FileType() = default; diff --git a/src/Type.h b/src/Type.h index 056645158b..b7900bb5ba 100644 --- a/src/Type.h +++ b/src/Type.h @@ -44,6 +44,9 @@ public: // Return the initialization value of the field. virtual ZVal Generate() const = 0; + + // Can initialization of the field be deferred? + virtual bool IsDeferrable() const { return true; } }; } // namespace detail @@ -734,6 +737,15 @@ public: detail::TraversalCode Traverse(detail::TraversalCallback* cb) const override; + // Can initialization of record values of this type be deferred? + // + // When record types contain non-const &default expressions or recursively + // contain any nested records that themselves are not deferrable, + // initialization can not be deferred, otherwise possible. + bool IsDeferrable() const; + + static void InitPostScript(); + private: RecordType() { types = nullptr; } @@ -754,6 +766,7 @@ private: // pairs. std::vector>> creation_inits; + class CreationInitsOptimizer; friend zeek::RecordVal; const auto& DeferredInits() const { return deferred_inits; } const auto& CreationInits() const { return creation_inits; } diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index 17982dbb09..685c4599f5 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -869,6 +869,8 @@ SetupResult setup(int argc, char** argv, Options* zopts) if ( reporter->Errors() > 0 ) exit(1); + RecordType::InitPostScript(); + telemetry_mgr->InitPostScript(); iosource_mgr->InitPostScript(); log_mgr->InitPostScript();