Fix deferred record initialization

Put RecordFieldInit instances into creation_inits during parsing and
determine their deferrability in an InitPostScript step. Any
RecordFieldInits can be deferred are moved into deferred_inits.

Closes #3260
This commit is contained in:
Arne Welzel 2023-09-12 12:13:08 +02:00
parent 7d6c8d7224
commit 384e7e6b25
3 changed files with 107 additions and 1 deletions

View file

@ -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<RecordType*>(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<const Type*> 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<detail::RecordFieldInit>(cast_intrusive<RecordType>(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<detail::RecordFieldInit>(cast_intrusive<RecordType>(type));
creation_inits.emplace_back(field, std::move(rfi));
}
else if ( tag == TYPE_TABLE )
init = std::make_unique<detail::TableFieldInit>(cast_intrusive<TableType>(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;

View file

@ -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:
// <fieldoffset, init> pairs.
std::vector<std::pair<int, std::unique_ptr<detail::FieldInit>>> creation_inits;
class CreationInitsOptimizer;
friend zeek::RecordVal;
const auto& DeferredInits() const { return deferred_inits; }
const auto& CreationInits() const { return creation_inits; }

View file

@ -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();