reworked AST optimizers analysis of side effects during aggregate operations & calls

This commit is contained in:
Vern Paxson 2023-12-04 16:55:38 -08:00
parent c028901146
commit 740a087765
13 changed files with 1119 additions and 223 deletions

View file

@ -8,6 +8,7 @@
#include "zeek/Desc.h"
#include "zeek/Func.h"
#include "zeek/Stmt.h"
#include "zeek/script_opt/FuncInfo.h"
#include "zeek/script_opt/IDOptInfo.h"
namespace zeek::detail {
@ -85,7 +86,16 @@ TraversalCode ProfileFunc::PreStmt(const Stmt* s) {
case STMT_INIT:
for ( const auto& id : s->AsInitStmt()->Inits() ) {
inits.insert(id.get());
TrackType(id->GetType());
auto& t = id->GetType();
TrackType(t);
auto attrs = id->GetAttrs();
if ( attrs )
constructor_attrs[attrs.get()] = t;
if ( t->Tag() == TYPE_RECORD )
CheckRecordConstructor(t);
}
// Don't traverse further into the statement, since we
@ -147,6 +157,14 @@ TraversalCode ProfileFunc::PreStmt(const Stmt* s) {
expr_switches.insert(sw);
} break;
case STMT_ADD:
case STMT_DELETE: {
auto ad_stmt = static_cast<const AddDelStmt*>(s);
auto ad_e = ad_stmt->StmtExpr();
auto& lhs_t = ad_e->GetOp1()->GetType();
aggr_mods.insert(lhs_t.get());
} break;
default: break;
}
@ -221,32 +239,89 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e) {
}
break;
case EXPR_INDEX: {
auto lhs_t = e->GetOp1()->GetType();
if ( lhs_t->Tag() == TYPE_TABLE )
tbl_refs.insert(lhs_t.get());
} break;
case EXPR_INCR:
case EXPR_DECR:
case EXPR_ADD_TO:
case EXPR_REMOVE_FROM:
case EXPR_ASSIGN: {
if ( e->GetOp1()->Tag() != EXPR_REF )
// this isn't a direct assignment
auto lhs = e->GetOp1();
if ( lhs->Tag() == EXPR_REF )
lhs = lhs->GetOp1();
else if ( e->Tag() == EXPR_ASSIGN )
// This isn't a direct assignment, but instead an overloaded
// use of "=" such as in a table constructor.
break;
auto lhs = e->GetOp1()->GetOp1();
if ( lhs->Tag() != EXPR_NAME )
break;
auto lhs_t = lhs->GetType();
auto id = lhs->AsNameExpr()->Id();
TrackAssignment(id);
switch ( lhs->Tag() ) {
case EXPR_NAME: {
auto id = lhs->AsNameExpr()->Id();
TrackAssignment(id);
if ( e->Tag() == EXPR_ASSIGN ) {
auto a_e = static_cast<const AssignExpr*>(e);
auto& av = a_e->AssignVal();
if ( av )
// This is a funky "local" assignment
// inside a when clause.
when_locals.insert(id);
if ( e->Tag() == EXPR_ASSIGN ) {
auto a_e = static_cast<const AssignExpr*>(e);
auto& av = a_e->AssignVal();
if ( av )
// This is a funky "local" assignment
// inside a when clause.
when_locals.insert(id);
}
else if ( IsAggr(lhs_t->Tag()) )
aggr_mods.insert(lhs_t.get());
} break;
case EXPR_INDEX: {
auto lhs_aggr = lhs->GetOp1();
auto lhs_aggr_t = lhs_aggr->GetType();
// Determine which aggregate is being modified. For an
// assignment "a[b] = aggr", it's not a[b]'s type but
// rather a's type. However, for any of the others,
// e.g. "a[b] -= aggr" it is a[b]'s type.
if ( e->Tag() == EXPR_ASSIGN )
aggr_mods.insert(lhs_aggr_t.get());
else
aggr_mods.insert(lhs_t.get());
if ( lhs_aggr_t->Tag() == TYPE_TABLE ) {
// We don't want the default recursion into the
// expression's LHS because that will treat this
// table modification as a reference instead. So
// do it manually. Given that, we need to do the
// expression's RHS manually too.
lhs->GetOp1()->Traverse(this);
lhs->GetOp2()->Traverse(this);
auto rhs = e->GetOp2();
if ( rhs )
rhs->Traverse(this);
return TC_ABORTSTMT;
}
} break;
case EXPR_FIELD: aggr_mods.insert(lhs_t.get()); break;
case EXPR_LIST: {
for ( auto id : lhs->AsListExpr()->Exprs() ) {
auto id_t = id->GetType();
if ( IsAggr(id_t->Tag()) )
aggr_mods.insert(id_t.get());
}
} break;
default: reporter->InternalError("bad expression in ProfileFunc: %s", obj_desc(e).c_str());
}
break;
}
} break;
case EXPR_CALL: {
auto c = e->AsCallExpr();
@ -272,8 +347,8 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e) {
auto func_vf = func_v->AsFunc();
if ( func_vf->GetKind() == Func::SCRIPT_FUNC ) {
auto bf = static_cast<ScriptFunc*>(func_vf);
script_calls.insert(bf);
auto sf = static_cast<ScriptFunc*>(func_vf);
script_calls.insert(sf);
}
else
BiF_globals.insert(func);
@ -329,18 +404,20 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e) {
// In general, we don't want to recurse into the body.
// However, we still want to *profile* it so we can
// identify calls within it.
ProfileFunc body_pf(l->Ingredients()->Body().get(), false);
script_calls.insert(body_pf.ScriptCalls().begin(), body_pf.ScriptCalls().end());
auto pf = std::make_shared<ProfileFunc>(l->Ingredients()->Body().get(), false);
script_calls.insert(pf->ScriptCalls().begin(), pf->ScriptCalls().end());
return TC_ABORTSTMT;
}
case EXPR_RECORD_CONSTRUCTOR: CheckRecordConstructor(e->GetType()); break;
case EXPR_SET_CONSTRUCTOR: {
auto sc = static_cast<const SetConstructorExpr*>(e);
const auto& attrs = sc->GetAttrs();
if ( attrs )
constructor_attrs.insert(attrs.get());
constructor_attrs[attrs.get()] = sc->GetType();
} break;
case EXPR_TABLE_CONSTRUCTOR: {
@ -348,7 +425,24 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e) {
const auto& attrs = tc->GetAttrs();
if ( attrs )
constructor_attrs.insert(attrs.get());
constructor_attrs[attrs.get()] = tc->GetType();
} break;
case EXPR_RECORD_COERCE:
// This effectively does a record construction of the target
// type, so check that.
CheckRecordConstructor(e->GetType());
break;
case EXPR_TABLE_COERCE: {
// This is written without casting so it can work with other
// types if needed.
auto res_type = e->GetType().get();
auto orig_type = e->GetOp1()->GetType().get();
if ( type_aliases.count(res_type) == 0 )
type_aliases[orig_type] = {res_type};
else
type_aliases[orig_type].insert(res_type);
} break;
default: break;
@ -395,20 +489,42 @@ void ProfileFunc::TrackAssignment(const ID* id) {
++assignees[id];
else
assignees[id] = 1;
if ( id->IsGlobal() || captures.count(id) > 0 )
non_local_assignees.insert(id);
}
void ProfileFunc::CheckRecordConstructor(TypePtr t) {
auto rt = cast_intrusive<RecordType>(t);
for ( auto td : *rt->Types() )
if ( td->attrs ) {
// In principle we could figure out whether this particular
// constructor happens to explicitly specify &default fields, and
// not include those attributes if it does since they won't come
// into play. However that seems like added complexity for almost
// surely no ultimate gain.
auto attrs = td->attrs.get();
constructor_attrs[attrs] = rt;
if ( rec_constructor_attrs.count(rt.get()) == 0 )
rec_constructor_attrs[rt.get()] = {attrs};
else
rec_constructor_attrs[rt.get()].insert(attrs);
}
}
ProfileFuncs::ProfileFuncs(std::vector<FuncInfo>& funcs, is_compilable_pred pred, bool _full_record_hashes) {
full_record_hashes = _full_record_hashes;
for ( auto& f : funcs ) {
if ( f.ShouldSkip() )
continue;
auto pf = std::make_unique<ProfileFunc>(f.Func(), f.Body(), full_record_hashes);
auto pf = std::make_shared<ProfileFunc>(f.Func(), f.Body(), full_record_hashes);
if ( ! pred || (*pred)(pf.get(), nullptr) )
MergeInProfile(pf.get());
// Track the profile even if we're not compiling the function, since
// the AST optimizer will still need it to reason about function-call
// side effects.
f.SetProfile(std::move(pf));
func_profs[f.Func()] = f.ProfilePtr();
}
@ -432,6 +548,81 @@ ProfileFuncs::ProfileFuncs(std::vector<FuncInfo>& funcs, is_compilable_pred pred
// Computing those hashes could have led to traversals that
// create more pending expressions to analyze.
} while ( ! pending_exprs.empty() );
// Now that we have everything profiled, we can proceed to analyses
// that require full global information.
ComputeSideEffects();
}
bool ProfileFuncs::IsTableWithDefaultAggr(const Type* t) {
auto analy = tbl_has_aggr_default.find(t);
if ( analy != tbl_has_aggr_default.end() )
// We already have the answer.
return analy->second;
// See whether an alias for the type has already been resolved.
if ( t->AsTableType()->Yield() ) {
for ( auto& at : tbl_has_aggr_default )
if ( same_type(at.first, t) ) {
tbl_has_aggr_default[t] = at.second;
return at.second;
}
}
tbl_has_aggr_default[t] = false;
return false;
}
bool ProfileFuncs::HasSideEffects(SideEffectsOp::AccessType access, const TypePtr& t) const {
IDSet nli;
TypeSet aggrs;
if ( GetSideEffects(access, t.get(), nli, aggrs) )
return true;
return ! nli.empty() || ! aggrs.empty();
}
bool ProfileFuncs::GetSideEffects(SideEffectsOp::AccessType access, const Type* t, IDSet& non_local_ids,
TypeSet& aggrs) const {
for ( auto se : side_effects_ops )
if ( AssessSideEffects(se.get(), access, t, non_local_ids, aggrs) )
return true;
return false;
}
bool ProfileFuncs::GetCallSideEffects(const NameExpr* n, IDSet& non_local_ids, TypeSet& aggrs, bool& is_unknown) {
auto fid = n->Id();
auto fv = fid->GetVal();
if ( ! fv || ! fid->IsConst() ) {
// The value is unavailable (likely a bug), or might change at run-time.
is_unknown = true;
return true;
}
auto func = fv->AsFunc();
if ( func->GetKind() == Func::BUILTIN_FUNC ) {
if ( ! is_side_effect_free(func->Name()) )
is_unknown = true;
return true;
}
auto sf = static_cast<ScriptFunc*>(func);
auto seo = GetCallSideEffects(sf);
if ( ! seo )
return false;
if ( seo->HasUnknownChanges() )
is_unknown = true;
for ( auto a : seo->ModAggrs() )
aggrs.insert(a);
for ( auto nl : seo->ModNonLocals() )
non_local_ids.insert(nl);
return true;
}
void ProfileFuncs::MergeInProfile(ProfileFunc* pf) {
@ -460,7 +651,7 @@ void ProfileFuncs::MergeInProfile(ProfileFunc* pf) {
auto& attrs = g->GetAttrs();
if ( attrs )
AnalyzeAttrs(attrs.get());
AnalyzeAttrs(attrs.get(), t.get());
}
constants.insert(pf->Constants().begin(), pf->Constants().end());
@ -475,7 +666,13 @@ void ProfileFuncs::MergeInProfile(ProfileFunc* pf) {
}
for ( auto& a : pf->ConstructorAttrs() )
AnalyzeAttrs(a);
AnalyzeAttrs(a.first, a.second.get());
for ( auto& ta : pf->TypeAliases() ) {
if ( type_aliases.count(ta.first) == 0 )
type_aliases[ta.first] = std::set<const Type*>{};
type_aliases[ta.first].insert(ta.second.begin(), ta.second.end());
}
}
void ProfileFuncs::TraverseValue(const ValPtr& v) {
@ -579,8 +776,12 @@ void ProfileFuncs::ComputeBodyHashes(std::vector<FuncInfo>& funcs) {
if ( ! f.ShouldSkip() )
ComputeProfileHash(f.ProfilePtr());
for ( auto& l : lambdas )
ComputeProfileHash(ExprProf(l));
for ( auto& l : lambdas ) {
auto pf = ExprProf(l);
func_profs[l->PrimaryFunc().get()] = pf;
lambda_primaries[l->Name()] = l->PrimaryFunc().get();
ComputeProfileHash(pf);
}
}
void ProfileFuncs::ComputeProfileHash(std::shared_ptr<ProfileFunc> pf) {
@ -710,11 +911,8 @@ p_hash_type ProfileFuncs::HashType(const Type* t) {
// We don't hash the field name, as in some contexts
// those are ignored.
if ( f->attrs ) {
if ( do_hash )
h = merge_p_hashes(h, HashAttrs(f->attrs));
AnalyzeAttrs(f->attrs.get());
}
if ( f->attrs && do_hash )
h = merge_p_hashes(h, HashAttrs(f->attrs));
}
} break;
@ -731,8 +929,24 @@ p_hash_type ProfileFuncs::HashType(const Type* t) {
auto ft = t->AsFuncType();
auto flv = ft->FlavorString();
h = merge_p_hashes(h, p_hash(flv));
// We deal with the parameters individually, rather than just
// recursing into the RecordType that's used (for convenience)
// to represent them. We do so because their properties are
// somewhat different - in particular, an &default on a parameter
// field is resolved in the context of the caller, not the
// function itself, and so we don't want to track those as
// attributes associated with the function body's execution.
h = merge_p_hashes(h, p_hash("params"));
h = merge_p_hashes(h, HashType(ft->Params()));
auto params = ft->Params()->Types();
if ( params ) {
h = merge_p_hashes(h, p_hash(params->length()));
for ( auto p : *params )
h = merge_p_hashes(h, HashType(p->type));
}
h = merge_p_hashes(h, p_hash("func-yield"));
h = merge_p_hashes(h, HashType(ft->Yield()));
} break;
@ -803,18 +1017,367 @@ p_hash_type ProfileFuncs::HashAttrs(const AttributesPtr& Attrs) {
return h;
}
void ProfileFuncs::AnalyzeAttrs(const Attributes* Attrs) {
auto attrs = Attrs->GetAttrs();
void ProfileFuncs::AnalyzeAttrs(const Attributes* attrs, const Type* t) {
for ( const auto& a : attrs->GetAttrs() ) {
auto& e = a->GetExpr();
for ( const auto& a : attrs ) {
const Expr* e = a->GetExpr().get();
if ( ! e )
continue;
if ( e ) {
pending_exprs.push_back(e);
if ( e->Tag() == EXPR_LAMBDA )
lambdas.insert(e->AsLambdaExpr());
pending_exprs.push_back(e.get());
auto prev_ea = expr_attrs.find(a.get());
if ( prev_ea == expr_attrs.end() )
expr_attrs[a.get()] = {t};
else {
// Add it if new. This is rare, but can arise due to attributes
// being shared for example from initializers with a variable
// itself.
bool found = false;
for ( auto ea : prev_ea->second )
if ( ea == t ) {
found = true;
break;
}
if ( ! found )
prev_ea->second.push_back(t);
}
if ( e->Tag() == EXPR_LAMBDA )
lambdas.insert(e->AsLambdaExpr());
}
}
void ProfileFuncs::ComputeSideEffects() {
// Computing side effects is an iterative process, because whether
// a given expression has a side effect can depend on whether it
// includes accesses to types that themselves have side effects.
// Step one: assemble the candidate pool of attributes to assess.
for ( auto& ea : expr_attrs ) {
// Is this an attribute that can be triggered by
// statement/expression execution?
auto a = ea.first;
auto at = a->Tag();
if ( at == ATTR_DEFAULT || at == ATTR_DEFAULT_INSERT || at == ATTR_ON_CHANGE ) {
if ( at == ATTR_DEFAULT ) {
// Look for tables with &default's returning aggregate values.
for ( auto t : ea.second ) {
if ( t->Tag() != TYPE_TABLE )
continue;
auto y = t->AsTableType()->Yield();
if ( y && IsAggr(y->Tag()) ) {
tbl_has_aggr_default[t] = true;
for ( auto ta : type_aliases[t] )
tbl_has_aggr_default[ta] = true;
}
}
}
// Weed out very-common-and-completely-safe expressions.
if ( ! DefinitelyHasNoSideEffects(a->GetExpr()) )
candidates.insert(a);
}
}
// At this point, very often there are no candidates and we're done.
// However, if we have candidates then we need to process them in an
// iterative fashion because it's possible that the side effects of
// some of them depend on the side effects of other candidates.
while ( ! candidates.empty() ) {
// For which attributes have we resolved their status.
AttrSet made_decision;
for ( auto c : candidates ) {
IDSet non_local_ids;
TypeSet aggrs;
bool is_unknown = false;
// Track the candidate we're currently analyzing, since sometimes
// it's self-referential and we need to identify that fact.
curr_candidate = c;
if ( ! AssessSideEffects(c->GetExpr(), non_local_ids, aggrs, is_unknown) )
// Can't make a decision yet.
continue;
// We've resolved this candidate.
made_decision.insert(c);
SetSideEffects(c, non_local_ids, aggrs, is_unknown);
}
if ( made_decision.empty() ) {
// We weren't able to make forward progress. This happens when
// the pending candidates are mutually dependent. While in
// principle we could scope the worst-case resolution of their
// side effects, this is such an unlikely situation that we just
// mark them all as unknown.
// We keep these empty.
IDSet non_local_ids;
TypeSet aggrs;
for ( auto c : candidates )
SetSideEffects(c, non_local_ids, aggrs, true);
// We're now all done.
break;
}
for ( auto md : made_decision )
candidates.erase(md);
}
}
bool ProfileFuncs::DefinitelyHasNoSideEffects(const ExprPtr& e) const {
if ( e->Tag() == EXPR_CONST || e->Tag() == EXPR_VECTOR_CONSTRUCTOR )
return true;
if ( e->Tag() == EXPR_NAME )
return e->GetType()->Tag() != TYPE_FUNC;
auto ep = expr_profs.find(e.get());
ASSERT(ep != expr_profs.end());
const auto& pf = ep->second;
if ( ! pf->NonLocalAssignees().empty() || ! pf->TableRefs().empty() || ! pf->AggrMods().empty() ||
! pf->ScriptCalls().empty() )
return false;
for ( auto& b : pf->BiFGlobals() )
if ( ! is_side_effect_free(b->Name()) )
return false;
return true;
}
void ProfileFuncs::SetSideEffects(const Attr* a, IDSet& non_local_ids, TypeSet& aggrs, bool is_unknown) {
auto seo_vec = std::vector<std::shared_ptr<SideEffectsOp>>{};
bool is_rec = expr_attrs[a][0]->Tag() == TYPE_RECORD;
SideEffectsOp::AccessType at;
if ( is_rec )
at = SideEffectsOp::CONSTRUCTION;
else if ( a->Tag() == ATTR_ON_CHANGE )
at = SideEffectsOp::WRITE;
else
at = SideEffectsOp::READ;
if ( non_local_ids.empty() && aggrs.empty() && ! is_unknown )
// Definitely no side effects.
seo_vec.push_back(std::make_shared<SideEffectsOp>());
else {
attrs_with_side_effects.insert(a);
// Set side effects for all of the types associated with this attribute.
for ( auto ea_t : expr_attrs[a] ) {
auto seo = std::make_shared<SideEffectsOp>(at, ea_t);
seo->AddModNonGlobal(non_local_ids);
seo->AddModAggrs(aggrs);
if ( is_unknown )
seo->SetUnknownChanges();
side_effects_ops.push_back(seo);
seo_vec.push_back(std::move(seo));
}
}
if ( is_rec )
record_constr_with_side_effects[a] = std::move(seo_vec);
else
aggr_side_effects[a] = std::move(seo_vec);
}
AttrVec ProfileFuncs::AssociatedAttrs(const Type* t) {
AttrVec assoc_attrs;
// Search both the pending candidates and the ones already identified.
// You might think we'd just do the latter, but we want to include the
// pending ones, too, so we can identify not-yet-resolved dependencies.
FindAssociatedAttrs(candidates, t, assoc_attrs);
FindAssociatedAttrs(attrs_with_side_effects, t, assoc_attrs);
return assoc_attrs;
}
void ProfileFuncs::FindAssociatedAttrs(const AttrSet& attrs, const Type* t, AttrVec& assoc_attrs) {
for ( auto a : attrs ) {
for ( auto ea_t : expr_attrs[a] ) {
if ( same_type(t, ea_t) ) {
assoc_attrs.push_back(a);
break;
}
for ( auto ta : type_aliases[ea_t] )
if ( same_type(t, ta) ) {
assoc_attrs.push_back(a);
break;
}
}
}
}
bool ProfileFuncs::AssessSideEffects(const ExprPtr& e, IDSet& non_local_ids, TypeSet& aggrs, bool& is_unknown) {
if ( e->Tag() == EXPR_NAME && e->GetType()->Tag() == TYPE_FUNC )
// This occurs when the expression is itself a function name, and
// in an attribute context indicates an implicit call.
return GetCallSideEffects(e->AsNameExpr(), non_local_ids, aggrs, is_unknown);
ASSERT(expr_profs.count(e.get()) != 0);
auto pf = expr_profs[e.get()];
return AssessSideEffects(pf.get(), non_local_ids, aggrs, is_unknown);
}
bool ProfileFuncs::AssessSideEffects(const ProfileFunc* pf, IDSet& non_local_ids, TypeSet& aggrs, bool& is_unknown) {
if ( pf->DoesIndirectCalls() ) {
is_unknown = true;
return true;
}
for ( auto& b : pf->BiFGlobals() )
if ( ! is_side_effect_free(b->Name()) ) {
is_unknown = true;
return true;
}
IDSet nla;
TypeSet mod_aggrs;
for ( auto& a : pf->NonLocalAssignees() )
nla.insert(a);
for ( auto& r : pf->RecordConstructorAttrs() )
if ( ! AssessAggrEffects(SideEffectsOp::CONSTRUCTION, r.first, nla, mod_aggrs, is_unknown) )
// Not enough information yet to know all of the side effects.
return false;
for ( auto& tr : pf->TableRefs() )
if ( ! AssessAggrEffects(SideEffectsOp::READ, tr, nla, mod_aggrs, is_unknown) )
return false;
for ( auto& tm : pf->AggrMods() ) {
if ( tm->Tag() == TYPE_TABLE && ! AssessAggrEffects(SideEffectsOp::WRITE, tm, nla, mod_aggrs, is_unknown) )
return false;
mod_aggrs.insert(tm);
}
for ( auto& f : pf->ScriptCalls() ) {
if ( f->Flavor() != FUNC_FLAVOR_FUNCTION ) {
// A hook (since events can't be called) - not something
// to analyze further.
is_unknown = true;
return true;
}
auto pff = func_profs[f];
if ( active_func_profiles.count(pff) > 0 )
// We're already processing this function and arrived here via
// recursion. Skip further analysis here, we'll do it instead
// for the original instance.
continue;
// Track this analysis so we can detect recursion.
active_func_profiles.insert(pff);
auto a = AssessSideEffects(pff.get(), nla, mod_aggrs, is_unknown);
active_func_profiles.erase(pff);
if ( ! a )
return false;
}
non_local_ids.insert(nla.begin(), nla.end());
aggrs.insert(mod_aggrs.begin(), mod_aggrs.end());
return true;
}
bool ProfileFuncs::AssessAggrEffects(SideEffectsOp::AccessType access, const Type* t, IDSet& non_local_ids,
TypeSet& aggrs, bool& is_unknown) {
auto assoc_attrs = AssociatedAttrs(t);
for ( auto a : assoc_attrs ) {
if ( a == curr_candidate )
// Self-reference - don't treat the absence of any determination
// for it as meaning we can't resolve the candidate.
continue;
// See whether we've already determined the side affects associated
// with this attribute.
auto ase = aggr_side_effects.find(a);
if ( ase == aggr_side_effects.end() ) {
ase = record_constr_with_side_effects.find(a);
if ( ase == record_constr_with_side_effects.end() )
// Haven't resolved it yet, so can't resolve current candidate.
return false;
}
for ( auto& se : ase->second )
if ( AssessSideEffects(se.get(), access, t, non_local_ids, aggrs) ) {
is_unknown = true;
return true;
}
}
return true;
}
bool ProfileFuncs::AssessSideEffects(const SideEffectsOp* se, SideEffectsOp::AccessType access, const Type* t,
IDSet& non_local_ids, TypeSet& aggrs) const {
// First determine whether the SideEffectsOp applies.
if ( se->GetAccessType() != access )
return false;
if ( ! same_type(se->GetType(), t) )
return false;
// It applies, return its effects.
if ( se->HasUnknownChanges() )
return true;
for ( auto a : se->ModAggrs() )
aggrs.insert(a);
for ( auto nl : se->ModNonLocals() )
non_local_ids.insert(nl);
return false;
}
std::shared_ptr<SideEffectsOp> ProfileFuncs::GetCallSideEffects(const ScriptFunc* sf) {
if ( lambda_primaries.count(sf->Name()) > 0 )
sf = lambda_primaries[sf->Name()];
auto sf_se = func_side_effects.find(sf);
if ( sf_se != func_side_effects.end() )
// Return cached result.
return sf_se->second;
bool is_unknown = false;
IDSet nla;
TypeSet mod_aggrs;
ASSERT(func_profs.count(sf) != 0);
auto pf = func_profs[sf];
if ( ! AssessSideEffects(pf.get(), nla, mod_aggrs, is_unknown) )
// Can't figure it out yet.
return nullptr;
auto seo = std::make_shared<SideEffectsOp>(SideEffectsOp::CALL);
seo->AddModNonGlobal(nla);
seo->AddModAggrs(mod_aggrs);
if ( is_unknown )
seo->SetUnknownChanges();
func_side_effects[sf] = seo;
return seo;
}
} // namespace zeek::detail