tracking of optimization information associated with identifiers

This commit is contained in:
Vern Paxson 2021-08-16 10:52:41 -07:00
parent 074b18f3e8
commit 9a9995bdd1
7 changed files with 822 additions and 20 deletions

View file

@ -20,6 +20,7 @@
#include "zeek/zeekygen/ScriptInfo.h"
#include "zeek/zeekygen/utils.h"
#include "zeek/module_util.h"
#include "zeek/script_opt/IDOptInfo.h"
namespace zeek {
@ -119,6 +120,8 @@ ID::ID(const char* arg_name, IDScope arg_scope, bool arg_is_export)
is_type = false;
offset = 0;
opt_info = new IDOptInfo(this);
infer_return_type = false;
SetLocationInfo(&start_location, &end_location);
@ -127,6 +130,7 @@ ID::ID(const char* arg_name, IDScope arg_scope, bool arg_is_export)
ID::~ID()
{
delete [] name;
delete opt_info;
}
std::string ID::ModuleName() const
@ -285,11 +289,6 @@ const AttrPtr& ID::GetAttr(AttrTag t) const
return attrs ? attrs->Find(t) : Attr::nil;
}
void ID::AddInitExpr(ExprPtr init_expr)
{
init_exprs.emplace_back(std::move(init_expr));
}
bool ID::IsDeprecated() const
{
return GetAttr(ATTR_DEPRECATED) != nullptr;
@ -676,6 +675,12 @@ std::vector<Func*> ID::GetOptionHandlers() const
return v;
}
void IDOptInfo::AddInitExpr(ExprPtr init_expr)
{
init_exprs.emplace_back(std::move(init_expr));
}
} // namespace detail
} // namespace zeek

View file

@ -7,7 +7,6 @@
#include <string_view>
#include <vector>
#include "zeek/IntrusivePtr.h"
#include "zeek/Obj.h"
#include "zeek/Attr.h"
#include "zeek/Notifier.h"
@ -44,6 +43,8 @@ enum IDScope { SCOPE_FUNCTION, SCOPE_MODULE, SCOPE_GLOBAL };
class ID;
using IDPtr = IntrusivePtr<ID>;
class IDOptInfo;
class ID final : public Obj, public notifier::detail::Modifiable {
public:
static inline const IDPtr nil;
@ -112,10 +113,6 @@ public:
const AttrPtr& GetAttr(AttrTag t) const;
void AddInitExpr(ExprPtr init_expr);
const std::vector<ExprPtr>& GetInitExprs() const
{ return init_exprs; }
bool IsDeprecated() const;
void MakeDeprecated(ExprPtr deprecation);
@ -144,6 +141,8 @@ public:
void AddOptionHandler(FuncPtr callback, int priority);
std::vector<Func*> GetOptionHandlers() const;
IDOptInfo* GetOptInfo() const { return opt_info; }
protected:
void EvalFunc(ExprPtr ef, ExprPtr ev);
@ -161,15 +160,15 @@ protected:
ValPtr val;
AttributesPtr attrs;
// Expressions used to initialize the identifier, for use by
// the scripts-to-C++ compiler. We need to track all of them
// because it's possible that a global value gets created using
// one of the earlier instances rather than the last one.
std::vector<ExprPtr> init_exprs;
// contains list of functions that are called when an option changes
std::multimap<int, FuncPtr> option_handlers;
// Information managed by script optimization. We package this
// up into a separate object for purposes of modularity, and,
// via the associated pointer, to allow it to be modified in
// contexts where the ID is itself "const".
IDOptInfo* opt_info;
};
} // namespace zeek::detail

View file

@ -98,6 +98,7 @@
#include "zeek/zeekygen/Manager.h"
#include "zeek/module_util.h"
#include "zeek/IntrusivePtr.h"
#include "zeek/script_opt/IDOptInfo.h"
extern const char* filename; // Absolute path of file currently being parsed.
extern const char* last_filename; // Absolute path of last file parsed.
@ -244,7 +245,7 @@ static void build_global(ID* id, Type* t, InitClass ic, Expr* e,
add_global(id_ptr, std::move(t_ptr), ic, e_ptr, std::move(attrs_ptr), dt);
id->AddInitExpr(e_ptr);
id->GetOptInfo()->AddInitExpr(e_ptr);
if ( dt == VAR_REDEF )
zeekygen_mgr->Redef(id, ::filename, ic, std::move(e_ptr));
@ -265,7 +266,7 @@ static StmtPtr build_local(ID* id, Type* t, InitClass ic, Expr* e,
auto init = add_local(std::move(id_ptr), std::move(t_ptr), ic,
e_ptr, std::move(attrs_ptr), dt);
id->AddInitExpr(std::move(e_ptr));
id->GetOptInfo()->AddInitExpr(std::move(e_ptr));
if ( do_coverage )
script_coverage_mgr.AddStmt(init.get());

View file

@ -6,6 +6,7 @@
#include "zeek/module_util.h"
#include "zeek/script_opt/ProfileFunc.h"
#include "zeek/script_opt/IDOptInfo.h"
#include "zeek/script_opt/CPP/Compile.h"
@ -122,7 +123,7 @@ void CPPCompile::GenGlobalInit(const ID* g, string& gl, const ValPtr& v)
// expression anyway.)
// Use the final initialization expression.
auto& init_exprs = g->GetInitExprs();
auto& init_exprs = g->GetOptInfo()->GetInitExprs();
init_val = GenExpr(init_exprs.back(), GEN_VAL_PTR, false);
}
else

523
src/script_opt/IDOptInfo.cc Normal file
View file

@ -0,0 +1,523 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "zeek/Stmt.h"
#include "zeek/Expr.h"
#include "zeek/Desc.h"
#include "zeek/script_opt/IDOptInfo.h"
#include "zeek/script_opt/StmtOptInfo.h"
namespace zeek::detail {
const char* trace_ID = nullptr;
IDDefRegion::IDDefRegion(const Stmt* s, bool maybe, int def)
{
start_stmt = s->GetOptInfo()->stmt_num;
block_level = s->GetOptInfo()->block_level;
Init(maybe, def);
}
IDDefRegion::IDDefRegion(int stmt_num, int level, bool maybe, int def)
{
start_stmt = stmt_num;
block_level = level;
Init(maybe, def);
}
IDDefRegion::IDDefRegion(const Stmt* s, const IDDefRegion& ur)
{
start_stmt = s->GetOptInfo()->stmt_num;
block_level = s->GetOptInfo()->block_level;
Init(ur.MaybeDefined(), ur.DefinedAfter());
SetDefExpr(ur.DefExprAfter());
}
void IDDefRegion::Dump() const
{
printf("\t%d->%d (%d): ", start_stmt, end_stmt, block_level);
if ( defined != NO_DEF )
printf("%d (%s)", defined, def_expr ? obj_desc(def_expr.get()).c_str() : "<no expr>");
else if ( maybe_defined )
printf("?");
else
printf("N/A");
printf("\n");
}
void IDOptInfo::Clear()
{
static bool did_init = false;
if ( ! did_init )
{
trace_ID = getenv("ZEEK_TRACE_ID");
did_init = true;
}
init_exprs.clear();
usage_regions.clear();
pending_confluences.clear();
confluence_stmts.clear();
tracing = trace_ID && util::streq(trace_ID, my_id->Name());
}
void IDOptInfo::DefinedAfter(const Stmt* s, const ExprPtr& e,
const std::vector<const Stmt*>& conf_blocks,
int conf_start)
{
if ( tracing )
printf("ID %s defined at %d: %s\n", trace_ID, s ? s->GetOptInfo()->stmt_num : NO_DEF, s ? obj_desc(s).c_str() : "<entry>");
if ( ! s )
{ // This is a definition-upon-entry
ASSERT(usage_regions.size() == 0);
usage_regions.emplace_back(0, 0, true, 0);
if ( tracing )
DumpBlocks();
return;
}
auto s_oi = s->GetOptInfo();
auto stmt_num = s_oi->stmt_num;
if ( usage_regions.size() == 0 )
{
// We're seeing this identifier for the first time,
// so we don't have any context or confluence
// information for it. Create its "backstory" region.
ASSERT(confluence_stmts.size() == 0);
usage_regions.emplace_back(0, 0, false, NO_DEF);
}
// Any pending regions stop prior to this statement.
EndRegionsAfter(stmt_num - 1, s_oi->block_level);
// Fill in any missing confluence blocks.
int b = 0; // index into our own blocks
int n = confluence_stmts.size();
while ( b < n && conf_start < conf_blocks.size() )
{
auto outer_block = conf_blocks[conf_start];
// See if we can find that block.
for ( ; b < n; ++b )
if ( confluence_stmts[b] == outer_block )
break;
if ( b < n )
{ // We found it, look for the next one.
++conf_start;
++b;
}
}
// Add in the remainder.
for ( ; conf_start < conf_blocks.size(); ++conf_start )
StartConfluenceBlock(conf_blocks[conf_start]);
// Create a new region corresponding to this definition.
// This needs to come after filling out the confluence
// blocks, since they'll create their own (earlier) regions.
usage_regions.emplace_back(s, true, stmt_num);
usage_regions.back().SetDefExpr(e);
if ( tracing )
DumpBlocks();
}
void IDOptInfo::ReturnAt(const Stmt* s)
{
if ( tracing )
printf("ID %s subject to return %d: %s\n", trace_ID, s->GetOptInfo()->stmt_num, obj_desc(s).c_str());
// Look for a catch-return that this would branch to.
for ( int i = confluence_stmts.size() - 1; i >= 0; --i )
if ( confluence_stmts[i]->Tag() == STMT_CATCH_RETURN )
{
BranchBeyond(s, confluence_stmts[i], false);
if ( tracing )
DumpBlocks();
return;
}
auto s_oi = s->GetOptInfo();
EndRegionsAfter(s_oi->stmt_num - 1, s_oi->block_level);
if ( tracing )
DumpBlocks();
}
void IDOptInfo::BranchBackTo(const Stmt* from, const Stmt* to, bool close_all)
{
if ( tracing )
printf("ID %s branching back from %d->%d: %s\n", trace_ID,
from->GetOptInfo()->stmt_num,
to->GetOptInfo()->stmt_num, obj_desc(from).c_str());
// The key notion we need to update is whether the regions
// between from_reg and to_reg still have unique definitions.
// Confluence due to the branch can only take that away, it
// can't instill it. (OTOH, in principle it could update
// "maybe defined", but not in a way we care about, since we
// only draw upon that for diagnosing usage errors, and for
// those the error has already occurred on entry into the loop.)
auto from_reg = ActiveRegion();
auto f_oi = from->GetOptInfo();
auto t_oi = to->GetOptInfo();
auto t_r_ind = FindRegionBeforeIndex(t_oi->stmt_num);
auto& t_r = usage_regions[t_r_ind];
if ( from_reg && from_reg->DefinedAfter() != t_r.DefinedAfter() &&
t_r.DefinedAfter() != NO_DEF )
{
// They disagree on the definition. Move the definition
// point to be the start of the confluence region, and
// update any blocks inside the region that refer to
// a pre-"to" definition to instead reflect the confluence
// region (and remove their definition expressions).
int new_def = t_oi->stmt_num;
for ( auto i = t_r_ind; i < usage_regions.size(); ++i )
{
auto& ur = usage_regions[i];
if ( ur.DefinedAfter() < new_def )
{
ASSERT(ur.DefinedAfter() != NO_DEF);
ur.UpdateDefinedAfter(new_def);
ur.SetDefExpr(nullptr);
}
}
}
int level = close_all ? t_oi->block_level + 1 : f_oi->block_level;
EndRegionsAfter(f_oi->stmt_num, level);
if ( tracing )
DumpBlocks();
}
void IDOptInfo::BranchBeyond(const Stmt* end_s, const Stmt* block,
bool close_all)
{
if ( tracing )
printf("ID %s branching forward from %d beyond %d: %s\n",
trace_ID, end_s->GetOptInfo()->stmt_num,
block->GetOptInfo()->stmt_num, obj_desc(end_s).c_str());
ASSERT(pending_confluences.count(block) > 0);
auto ar = ActiveRegionIndex();
if ( ar != NO_DEF )
pending_confluences[block].insert(ar);
auto end_oi = end_s->GetOptInfo();
int level;
if ( close_all )
level = block->GetOptInfo()->block_level + 1;
else
level = end_oi->block_level;
EndRegionsAfter(end_oi->stmt_num, level);
if ( tracing )
DumpBlocks();
}
void IDOptInfo::StartConfluenceBlock(const Stmt* s)
{
if ( tracing )
printf("ID %s starting confluence block at %d: %s\n", trace_ID, s->GetOptInfo()->stmt_num, obj_desc(s).c_str());
auto s_oi = s->GetOptInfo();
int block_level = s_oi->block_level;
// End any confluence blocks at this or inner levels.
for ( auto cs : confluence_stmts )
{
ASSERT(cs != s);
auto cs_level = cs->GetOptInfo()->block_level;
if ( cs_level >= block_level )
{
ASSERT(cs_level == block_level);
ASSERT(cs == confluence_stmts.back());
EndRegionsAfter(s_oi->stmt_num - 1, block_level);
}
}
ConfluenceSet empty_set;
pending_confluences[s] = empty_set;
confluence_stmts.push_back(s);
block_has_orig_flow.push_back(s_oi->contains_branch_beyond);
// Inherit the closest open, outer region, if necessary.
for ( int i = usage_regions.size() - 1; i >= 0; --i )
{
auto& ur = usage_regions[i];
if ( ur.EndsAfter() == NO_DEF )
{
if ( ur.BlockLevel() > block_level )
{
// This can happen for regions left over
// from a previous catch-return, which
// we haven't closed out yet because we
// don't track new identifiers beyond
// outer CRs. Close the region now.
ASSERT(s->Tag() == STMT_CATCH_RETURN);
ur.SetEndsAfter(s_oi->stmt_num - 1);
continue;
}
if ( ur.BlockLevel() < block_level )
// Didn't find one at our own level,
// so create on inherited from the
// outer one.
usage_regions.emplace_back(s, ur);
// We now have one at our level that we can use.
break;
}
}
if ( tracing )
DumpBlocks();
}
void IDOptInfo::ConfluenceBlockEndsAfter(const Stmt* s, bool no_orig_flow)
{
auto stmt_num = s->GetOptInfo()->stmt_num;
ASSERT(confluence_stmts.size() > 0);
auto cs = confluence_stmts.back();
auto& pc = pending_confluences[cs];
// End any active regions. Those will all have a level >= that
// of cs, since we're now returning to cs's level.
int cs_stmt_num = cs->GetOptInfo()->stmt_num;
int cs_level = cs->GetOptInfo()->block_level;
if ( tracing )
printf("ID %s ending (%d) confluence block (%d, level %d) at %d: %s\n", trace_ID, no_orig_flow, cs_stmt_num, cs_level, stmt_num, obj_desc(s).c_str());
if ( block_has_orig_flow.back() )
no_orig_flow = false;
// Compute the state of the definition at the point of confluence:
// whether it's at least could-be-defined, whether it's definitely
// defined and if so whether it has a single point of definition.
bool maybe = false;
bool defined = true;
bool did_single_def = false;
int single_def = NO_DEF;
ExprPtr single_def_expr;
bool have_multi_defs = false;
int num_regions = 0;
for ( auto i = 0; i < usage_regions.size(); ++i )
{
auto& ur = usage_regions[i];
if ( ur.BlockLevel() < cs_level )
// Region is not applicable.
continue;
if ( ur.EndsAfter() == NO_DEF )
{ // End this region.
ur.SetEndsAfter(stmt_num);
if ( ur.StartsAfter() <= cs_stmt_num && no_orig_flow &&
pc.count(i) == 0 )
// Don't include this region in our assessment.
continue;
}
else if ( ur.EndsAfter() < cs_stmt_num )
// Irrelevant, didn't extend into confluence region.
// We test here just to avoid the set lookup in
// the next test, which presumably will sometimes
// be a tad expensive.
continue;
else if ( pc.count(i) == 0 )
// This region isn't active, and we're not
// tracking it for confluence.
continue;
++num_regions;
maybe = maybe || ur.MaybeDefined();
if ( ur.DefinedAfter() == NO_DEF )
{
defined = false;
continue;
}
if ( did_single_def )
{
if ( single_def != ur.DefinedAfter() )
have_multi_defs = true;
}
else
{
single_def = ur.DefinedAfter();
single_def_expr = ur.DefExprAfter();
did_single_def = true;
}
}
if ( num_regions == 0 )
{ // Nothing survives.
ASSERT(maybe == false);
defined = false;
}
if ( ! defined )
{
single_def = NO_DEF;
have_multi_defs = false;
}
if ( have_multi_defs )
// Definition reflects confluence point, which comes
// just after 's'.
single_def = stmt_num + 1;
int level = cs->GetOptInfo()->block_level;
usage_regions.emplace_back(stmt_num, level, maybe, single_def);
if ( single_def != NO_DEF && ! have_multi_defs )
usage_regions.back().SetDefExpr(single_def_expr);
confluence_stmts.pop_back();
block_has_orig_flow.pop_back();
pending_confluences.erase(cs);
if ( tracing )
DumpBlocks();
}
bool IDOptInfo::IsPossiblyDefinedBefore(const Stmt* s)
{
return IsPossiblyDefinedBefore(s->GetOptInfo()->stmt_num);
}
bool IDOptInfo::IsDefinedBefore(const Stmt* s)
{
return IsDefinedBefore(s->GetOptInfo()->stmt_num);
}
int IDOptInfo::DefinitionBefore(const Stmt* s)
{
return DefinitionBefore(s->GetOptInfo()->stmt_num);
}
ExprPtr IDOptInfo::DefExprBefore(const Stmt* s)
{
return DefExprBefore(s->GetOptInfo()->stmt_num);
}
bool IDOptInfo::IsPossiblyDefinedBefore(int stmt_num)
{
if ( usage_regions.size() == 0 )
return false;
return FindRegionBefore(stmt_num).MaybeDefined();
}
bool IDOptInfo::IsDefinedBefore(int stmt_num)
{
if ( usage_regions.size() == 0 )
return false;
return FindRegionBefore(stmt_num).DefinedAfter() != NO_DEF;
}
int IDOptInfo::DefinitionBefore(int stmt_num)
{
if ( usage_regions.size() == 0 )
return NO_DEF;
return FindRegionBefore(stmt_num).DefinedAfter();
}
ExprPtr IDOptInfo::DefExprBefore(int stmt_num)
{
if ( usage_regions.size() == 0 )
return nullptr;
return FindRegionBefore(stmt_num).DefExprAfter();
}
void IDOptInfo::EndRegionsAfter(int stmt_num, int level)
{
for ( int i = usage_regions.size() - 1; i >= 0; --i )
{
auto& ur = usage_regions[i];
if ( ur.BlockLevel() < level )
return;
if ( ur.EndsAfter() == NO_DEF )
ur.SetEndsAfter(stmt_num);
}
}
int IDOptInfo::FindRegionBeforeIndex(int stmt_num)
{
int region_ind = NO_DEF;
for ( auto i = 0; i < usage_regions.size(); ++i )
{
auto ur = usage_regions[i];
if ( ur.StartsAfter() >= stmt_num )
break;
if ( ur.EndsAfter() == NO_DEF )
// It's active for everything beyond its start.
region_ind = i;
else if ( ur.EndsAfter() >= stmt_num - 1 )
// It's active at the beginning of the statement of
// interest.
region_ind = i;
}
ASSERT(region_ind != NO_DEF);
return region_ind;
}
int IDOptInfo::ActiveRegionIndex()
{
int i;
for ( i = usage_regions.size() - 1; i >= 0; --i )
if ( usage_regions[i].EndsAfter() == NO_DEF )
return i;
return NO_DEF;
}
void IDOptInfo::DumpBlocks() const
{
for ( auto i = 0; i < usage_regions.size(); ++i )
usage_regions[i].Dump();
printf("<end>\n");
}
} // zeek::detail

272
src/script_opt/IDOptInfo.h Normal file
View file

@ -0,0 +1,272 @@
// See the file "COPYING" in the main distribution directory for copyright.
// Auxiliary information associated with identifiers to aid script
// optimization.
#pragma once
#include <set>
#include "zeek/IntrusivePtr.h"
namespace zeek::detail {
class Expr;
class Stmt;
using ExprPtr = IntrusivePtr<Expr>;
#define NO_DEF -1
// This class tracks a single region during which an identifier has
// a consistent state of definition, meaning either it's (1) defined
// as of its value after a specific statement, (2) might-or-might-not
// be defined, or (3) definitely not defined.
class IDDefRegion {
public:
IDDefRegion(const Stmt* s, bool maybe, int def);
IDDefRegion(int stmt_num, int level, bool maybe, int def);
IDDefRegion(const Stmt* s, const IDDefRegion& ur);
void Init(bool maybe, int def)
{
if ( def != NO_DEF )
maybe_defined = true;
else
maybe_defined = maybe;
defined = def;
}
// Returns the starting point of the region, i.e., the number
// of the statement *after* which executing this region begins.
int StartsAfter() const { return start_stmt; }
// Returns or sets the ending point of the region, i.e., the
// last statement for which this region applies (including executing
// that statement). A value of NO_DEF means that the region
// continues indefinitely, i.e., we haven't yet encountered its end.
int EndsAfter() const { return end_stmt; }
void SetEndsAfter(int _end_stmt) { end_stmt = _end_stmt; }
// The confluence nesting level associated with the region. Other
// regions that overlap take precedence if they have a higher
// (= more inner) block level.
int BlockLevel() const { return block_level; }
// True if in the region the identifer could be defined.
bool MaybeDefined() const { return maybe_defined; }
// Returns (or sets) the statement after which the identifer is
// (definitely) defined, or NO_DEF if it doesn't have a definite
// point of definition.
int DefinedAfter() const { return defined; }
void UpdateDefinedAfter(int _defined) { defined = _defined; }
// Returns (or sets) the expression used to define the identifier,
// if any. Note that an identifier can be definitely defined
// (i.e., DefinedAfter() returns a statement number, not NO_DEF)
// but not have an associated expression, if the point-of-definition
// is the end of a confluence block.
const ExprPtr& DefExprAfter() const { return def_expr; }
void SetDefExpr(ExprPtr e) { def_expr = e; }
// Used for debugging.
void Dump() const;
protected:
// Number of the statement for which this region applies *after*
// its execution.
int start_stmt;
// Number of the statement that this region applies to, *after*
// its execution.
int end_stmt = NO_DEF; // means the region hasn't ended yet
// Degree of confluence nesting associated with this region.
int block_level;
// Identifier could be defined in this region.
bool maybe_defined;
// If not NO_DEF, then the statement number of either the identifier's
// definition, or its confluence point if multiple, differing
// definitions come together.
int defined;
// The expression used to define the identifier in this region.
// Nil if either it's ambiguous (due to confluence), or the
// identifier isn't guaranteed to be defined.
ExprPtr def_expr;
};
// Class tracking optimization information associated with identifiers.
class IDOptInfo {
public:
IDOptInfo(const ID* id) { my_id = id; }
// Reset all computed information about the identifier. Used
// when making a second pass over an AST after optimizing it,
// to avoid inheriting now-stale information.
void Clear();
// Used to track expressions employed when explicitly initializing
// the identifier. These are needed by compile-to-C++ script
// optimization. They're not used by ZAM optimization.
void AddInitExpr(ExprPtr init_expr);
const std::vector<ExprPtr>& GetInitExprs() const
{ return init_exprs; }
// Associated constant expression, if any. This is only set
// for identifiers that are aliases for a constant (i.e., there
// are no other assignments to them).
const ConstExpr* Const() const { return const_expr; }
// The most use of "const" in any single line in the Zeek
// codebase :-P ... though only by one!
void SetConst(const ConstExpr* _const) { const_expr = _const; }
// Whether the identifier is a temporary variable. Temporaries
// are guaranteed to have exactly one point of definition.
bool IsTemp() const { return is_temp; }
void SetTemp() { is_temp = true; }
// Called when the identifier is defined via execution of the
// given statement, with an assignment to the expression 'e'
// (only non-nil for simple direct assignments). "conf_blocks"
// gives the full set of surrounding confluence statements.
// It should be processed starting at conf_start (note that
// conf_blocks may be empty).
void DefinedAfter(const Stmt* s, const ExprPtr& e,
const std::vector<const Stmt*>& conf_blocks,
int conf_start);
// Called upon encountering a "return" statement.
void ReturnAt(const Stmt* s);
// Called when the current region ends with a backwards branch,
// possibly across multiple block levels, occurring at "from"
// and going into the block "to". If "close_all" is true then
// any pending regions at a level inner to "to" should be
// closed; if not, just those at "from"'s level.
void BranchBackTo(const Stmt* from, const Stmt* to, bool close_all);
// Called when the current region ends at statement end_s with a
// forwards branch, possibly across multiple block levels, to
// the statement that comes right after the execution of "block".
// See above re "close_all".
void BranchBeyond(const Stmt* end_s, const Stmt* block, bool close_all);
// Start tracking a confluence block that begins with the body
// of s (not s itself).
void StartConfluenceBlock(const Stmt* s);
// Finish tracking confluence; s is the last point of execution
// prior to leaving a block. If no_orig_flow is true, then
// the region for 's' itself does not continue to the end of
// the block.
void ConfluenceBlockEndsAfter(const Stmt* s, bool no_orig_flow);
// All of these regard the identifer's state just *prior* to
// executing the given statement.
bool IsPossiblyDefinedBefore(const Stmt* s);
bool IsDefinedBefore(const Stmt* s);
int DefinitionBefore(const Stmt* s);
ExprPtr DefExprBefore(const Stmt* s);
// Same, but using statement numbers.
bool IsPossiblyDefinedBefore(int stmt_num);
bool IsDefinedBefore(int stmt_num);
int DefinitionBefore(int stmt_num);
ExprPtr DefExprBefore(int stmt_num);
// The following are used to avoid multiple error messages
// for use of undefined variables.
bool DidUndefinedWarning() const
{ return did_undefined_warning; }
bool DidPossiblyUndefinedWarning() const
{ return did_possibly_undefined_warning; }
void SetDidUndefinedWarning()
{ did_undefined_warning = true; }
void SetDidPossiblyUndefinedWarning()
{ did_possibly_undefined_warning = true; }
private:
// End any active regions that are at or inner to the given level.
void EndRegionsAfter(int stmt_num, int level);
// Find the region that applies *before* executing the given
// statement. There should always be such a region.
IDDefRegion& FindRegionBefore(int stmt_num)
{ return usage_regions[FindRegionBeforeIndex(stmt_num)]; }
int FindRegionBeforeIndex(int stmt_num);
// Return the current "active" region, if any. The active region
// is the innermost region that currently has an end of NO_DEF,
// meaning we have not yet found its end.
IDDefRegion* ActiveRegion()
{
auto ind = ActiveRegionIndex();
return ind >= 0 ? &usage_regions[ind] : nullptr;
}
int ActiveRegionIndex();
// Used for debugging.
void DumpBlocks() const;
// Expressions used to initialize the identifier, for use by
// the scripts-to-C++ compiler. We need to track all of them
// because it's possible that a global value gets created using
// one of the earlier instances rather than the last one.
std::vector<ExprPtr> init_exprs;
// If non-nil, a constant that this identifier always holds
// once initially defined.
const ConstExpr* const_expr = nullptr;
// The different usage regions associated with the identifier.
// These are constructed such that they're always with non-decreasing
// starting statements.
std::vector<IDDefRegion> usage_regions;
// A type for collecting the indices of usage_regions that will
// all have confluence together at one point. Used to track
// things like "break" statements that jump out of loops or
// switch confluence regions.
using ConfluenceSet = std::set<int>;
// Maps loops/switches/catch-returns to their associated
// confluence sets.
std::map<const Stmt*, ConfluenceSet> pending_confluences;
// A stack of active confluence statements, so we can always find
// the innermost when ending a confluence block.
std::vector<const Stmt*> confluence_stmts;
// Parallel vector that tracks whether, upon creating the
// confluence block, there had already been observed internal flow
// going beyond it. If so, then we can ignore no_orig_flow when
// ending the block, because in fact there *was* original flow.
std::vector<bool> block_has_orig_flow;
// Whether the identifier is a temporary variable.
bool is_temp = false;
// Only needed for debugging purposes.
const ID* my_id;
bool tracing = false;
// Track whether we've already generated usage errors.
bool did_undefined_warning = false;
bool did_possibly_undefined_warning = false;
};
// If non-nil, then output detailed tracing information when building
// up the usage regions for any identifier with the given name.
extern const char* trace_ID;
} // namespace zeek::detail

View file

@ -4,6 +4,7 @@
#include <cerrno>
#include "zeek/script_opt/ProfileFunc.h"
#include "zeek/script_opt/IDOptInfo.h"
#include "zeek/Desc.h"
#include "zeek/Stmt.h"
#include "zeek/Func.h"
@ -500,7 +501,7 @@ void ProfileFuncs::MergeInProfile(ProfileFunc* pf)
if ( t->Tag() == TYPE_TYPE )
(void) HashType(t->AsTypeType()->GetType());
auto& init_exprs = g->GetInitExprs();
auto& init_exprs = g->GetOptInfo()->GetInitExprs();
for ( const auto& i_e : init_exprs )
if ( i_e )
{