Merge remote-tracking branch 'origin/topic/vern/zam-feature-complete'

* origin/topic/vern/zam-feature-complete: (23 commits)
  documentation updates
  updates to ZAM-specific BTest baseline
  Address PR review feedback on zam-feature-complete
  Updates to BTest baselines due to previous BTest tweaks
  tweaks to BTests for ZAM feature completeness; new ZAM-only btest
  removal of unused functionality and some follow-on simplifications
  feature completeness for ZAM
  -O gen-C++ tweaks to be compatible with ZAM changes
  ZAM support for "when" statements
  ZAM changes intermixed with lambda and "when" support
  WhenStmt/WhenInfo restructuring in support of ZAM "when" statements
  ZAM support for lambdas
  ZAM internals have a notion of "captures" as global-like variables
  AST profiling enhnacements in support of script optimization for lambdas/whens
  refinements to LambdaExpr's to provide flexibility, support for ZVal captures
  support in ScriptFunc class for ZVal-oriented vector of captures
  simplifications to the Frame class now that it no longer has to support old-style captures
  use Ingredients directly for constructing functions
  the "Capture" struct is now a class
  more debugging information when dumping script optimization data structures
  ...
This commit is contained in:
Arne Welzel 2023-06-30 10:19:19 +02:00
commit fcc38d3b4f
75 changed files with 1591 additions and 581 deletions

50
CHANGES
View file

@ -1,3 +1,53 @@
6.1.0-dev.152 | 2023-06-30 10:19:19 +0200
* documentation updates (Vern Paxson, Corelight)
* updates to ZAM-specific BTest baseline (Vern Paxson, Corelight)
* Address PR review feedback on zam-feature-complete (Vern Paxson, Corelight)
* Updates to BTest baselines due to previous BTest tweaks (Vern Paxson, Corelight)
* tweaks to BTests for ZAM feature completeness; new ZAM-only btest (Vern Paxson, Corelight)
* removal of unused functionality and some follow-on simplifications (Vern Paxson, Corelight)
* feature completeness for ZAM (Vern Paxson, Corelight)
* -O gen-C++ tweaks to be compatible with ZAM changes (Vern Paxson, Corelight)
* ZAM support for "when" statements (Vern Paxson, Corelight)
* ZAM changes intermixed with lambda and "when" support (Vern Paxson, Corelight)
* WhenStmt/WhenInfo restructuring in support of ZAM "when" statements (Vern Paxson, Corelight)
* ZAM support for lambdas (Vern Paxson, Corelight)
* ZAM internals have a notion of "captures" as global-like variables (Vern Paxson, Corelight)
* AST profiling enhnacements in support of script optimization for lambdas/whens (Vern Paxson, Corelight)
* refinements to LambdaExpr's to provide flexibility, support for ZVal captures (Vern Paxson, Corelight)
* support in ScriptFunc class for ZVal-oriented vector of captures (Vern Paxson, Corelight)
* simplifications to the Frame class now that it no longer has to support old-style captures (Vern Paxson, Corelight)
* use Ingredients directly for constructing functions (Vern Paxson, Corelight)
* the "Capture" struct is now a class (Vern Paxson, Corelight)
* more debugging information when dumping script optimization data structures (Vern Paxson, Corelight)
* bug fixes for script optimization intermediate forms (Vern Paxson, Corelight)
* clarifying comments, interface tightening (Vern Paxson, Corelight)
* added some class accessors/set-ers (Vern Paxson, Corelight)
* Update doc submodule [nomail] [skip ci] (zeek-bot)
6.1.0-dev.127 | 2023-06-29 11:22:58 -0700 6.1.0-dev.127 | 2023-06-29 11:22:58 -0700
* Move CMake template files to separate directory (Tim Wojtulewicz, Corelight) * Move CMake template files to separate directory (Tim Wojtulewicz, Corelight)

View file

@ -1 +1 @@
6.1.0-dev.127 6.1.0-dev.152

@ -1 +1 @@
Subproject commit 16841c95849d4d82f239bfb0c46bc217af368da2 Subproject commit f62a0215f063bc768d7e85b4a49faeb07ca3cb14

View file

@ -25,6 +25,7 @@
#include "zeek/digest.h" #include "zeek/digest.h"
#include "zeek/module_util.h" #include "zeek/module_util.h"
#include "zeek/script_opt/ExprOptInfo.h" #include "zeek/script_opt/ExprOptInfo.h"
#include "zeek/script_opt/ScriptOpt.h"
namespace zeek::detail namespace zeek::detail
{ {
@ -4644,14 +4645,15 @@ void CallExpr::ExprDescribe(ODesc* d) const
args->Describe(d); args->Describe(d);
} }
LambdaExpr::LambdaExpr(std::unique_ptr<FunctionIngredients> arg_ing, IDPList arg_outer_ids, LambdaExpr::LambdaExpr(FunctionIngredientsPtr arg_ing, IDPList arg_outer_ids, std::string name,
StmtPtr when_parent) StmtPtr when_parent)
: Expr(EXPR_LAMBDA) : Expr(EXPR_LAMBDA)
{ {
ingredients = std::move(arg_ing); ingredients = std::move(arg_ing);
outer_ids = std::move(arg_outer_ids); outer_ids = std::move(arg_outer_ids);
SetType(ingredients->GetID()->GetType()); auto ingr_t = ingredients->GetID()->GetType<FuncType>();
SetType(ingr_t);
if ( ! CheckCaptures(when_parent) ) if ( ! CheckCaptures(when_parent) )
{ {
@ -4659,47 +4661,61 @@ LambdaExpr::LambdaExpr(std::unique_ptr<FunctionIngredients> arg_ing, IDPList arg
return; return;
} }
// Install a dummy version of the function globally for use only // Install a primary version of the function globally. This is used
// when broker provides a closure. // by both broker (for transmitting closures) and script optimization
auto dummy_func = make_intrusive<ScriptFunc>(ingredients->GetID()); // (replacing its AST body with a compiled one).
dummy_func->AddBody(ingredients->Body(), ingredients->Inits(), ingredients->FrameSize(), primary_func = make_intrusive<ScriptFunc>(ingredients->GetID());
ingredients->Priority()); primary_func->SetOuterIDs(outer_ids);
dummy_func->SetOuterIDs(outer_ids); // When we build the body, it will get updated with initialization
// statements. Update the ingredients to reflect the new body,
// and no more need for initializers.
primary_func->AddBody(*ingredients);
primary_func->SetScope(ingredients->Scope());
ingredients->ClearInits();
// Get the body's "string" representation. if ( name.empty() )
ODesc d; BuildName();
dummy_func->Describe(&d); else
my_name = name;
for ( ;; )
{
hash128_t h;
KeyedHash::Hash128(d.Bytes(), d.Len(), &h);
my_name = "lambda_<" + std::to_string(h[0]) + ">";
auto fullname = make_full_var_name(current_module.data(), my_name.data());
const auto& id = global_scope()->Find(fullname);
if ( id )
// Just try again to make a unique lambda name.
// If two peer processes need to agree on the same
// lambda name, this assumes they're loading the same
// scripts and thus have the same hash collisions.
d.Add(" ");
else
break;
}
// Install that in the global_scope // Install that in the global_scope
lambda_id = install_ID(my_name.c_str(), current_module.c_str(), true, false); lambda_id = install_ID(my_name.c_str(), current_module.c_str(), true, false);
// Update lamb's name // Update lamb's name
dummy_func->SetName(my_name.c_str()); primary_func->SetName(my_name.c_str());
auto v = make_intrusive<FuncVal>(std::move(dummy_func)); auto v = make_intrusive<FuncVal>(primary_func);
lambda_id->SetVal(std::move(v)); lambda_id->SetVal(std::move(v));
lambda_id->SetType(ingredients->GetID()->GetType()); lambda_id->SetType(ingr_t);
lambda_id->SetConst(); lambda_id->SetConst();
captures = ingr_t->GetCaptures();
analyze_lambda(this);
}
LambdaExpr::LambdaExpr(LambdaExpr* orig) : Expr(EXPR_LAMBDA)
{
primary_func = orig->primary_func;
ingredients = orig->ingredients;
lambda_id = orig->lambda_id;
my_name = orig->my_name;
private_captures = orig->private_captures;
// We need to have our own copies of the outer IDs and captures so
// we can rename them when inlined.
for ( auto i : orig->outer_ids )
outer_ids.append(i);
if ( orig->captures )
{
captures = std::vector<FuncType::Capture>{};
for ( auto& c : *orig->captures )
captures->push_back(c);
}
SetType(orig->GetType());
} }
bool LambdaExpr::CheckCaptures(StmtPtr when_parent) bool LambdaExpr::CheckCaptures(StmtPtr when_parent)
@ -4726,7 +4742,7 @@ bool LambdaExpr::CheckCaptures(StmtPtr when_parent)
for ( const auto& c : *captures ) for ( const auto& c : *captures )
{ {
auto cid = c.id.get(); auto cid = c.Id().get();
if ( ! cid ) if ( ! cid )
// This happens for undefined/inappropriate // This happens for undefined/inappropriate
@ -4768,7 +4784,7 @@ bool LambdaExpr::CheckCaptures(StmtPtr when_parent)
for ( const auto& c : *captures ) for ( const auto& c : *captures )
{ {
auto cid = c.id.get(); auto cid = c.Id().get();
if ( cid && capture_is_matched.count(cid) == 0 ) if ( cid && capture_is_matched.count(cid) == 0 )
{ {
auto msg = util::fmt("%s is captured but not used inside %s", cid->Name(), desc); auto msg = util::fmt("%s is captured but not used inside %s", cid->Name(), desc);
@ -4784,6 +4800,32 @@ bool LambdaExpr::CheckCaptures(StmtPtr when_parent)
return true; return true;
} }
void LambdaExpr::BuildName()
{
// Get the body's "string" representation.
ODesc d;
primary_func->Describe(&d);
for ( ;; )
{
hash128_t h;
KeyedHash::Hash128(d.Bytes(), d.Len(), &h);
my_name = "lambda_<" + std::to_string(h[0]) + ">";
auto fullname = make_full_var_name(current_module.data(), my_name.data());
const auto& id = global_scope()->Find(fullname);
if ( id )
// Just try again to make a unique lambda name.
// If two peer processes need to agree on the same
// lambda name, this assumes they're loading the same
// scripts and thus have the same hash collisions.
d.Add(" ");
else
break;
}
}
ScopePtr LambdaExpr::GetScope() const ScopePtr LambdaExpr::GetScope() const
{ {
return ingredients->Scope(); return ingredients->Scope();
@ -4792,12 +4834,23 @@ ScopePtr LambdaExpr::GetScope() const
ValPtr LambdaExpr::Eval(Frame* f) const ValPtr LambdaExpr::Eval(Frame* f) const
{ {
auto lamb = make_intrusive<ScriptFunc>(ingredients->GetID()); auto lamb = make_intrusive<ScriptFunc>(ingredients->GetID());
lamb->AddBody(ingredients->Body(), ingredients->Inits(), ingredients->FrameSize(),
ingredients->Priority());
StmtPtr body = primary_func->GetBodies()[0].stmts;
if ( run_state::is_parsing )
// We're evaluating this lambda at parse time, which happens
// for initializations. If we're doing script optimization
// then the current version of the body might be left in an
// inconsistent state (e.g., if it's replaced with ZAM code)
// causing problems if we execute this lambda subsequently.
// To avoid that problem, we duplicate the AST so it's
// distinct.
body = body->Duplicate();
lamb->AddBody(*ingredients, body);
lamb->CreateCaptures(f); lamb->CreateCaptures(f);
// Set name to corresponding dummy func. // Set name to corresponding master func.
// Allows for lookups by the receiver. // Allows for lookups by the receiver.
lamb->SetName(my_name.c_str()); lamb->SetName(my_name.c_str());
@ -4807,6 +4860,22 @@ ValPtr LambdaExpr::Eval(Frame* f) const
void LambdaExpr::ExprDescribe(ODesc* d) const void LambdaExpr::ExprDescribe(ODesc* d) const
{ {
d->Add(expr_name(Tag())); d->Add(expr_name(Tag()));
if ( captures && d->IsReadable() )
{
d->Add("[");
for ( auto& c : *captures )
{
if ( &c != &(*captures)[0] )
d->AddSP(", ");
d->Add(c.Id()->Name());
}
d->Add("]");
}
ingredients->Body()->Describe(d); ingredients->Body()->Describe(d);
} }

View file

@ -28,8 +28,11 @@ namespace detail
class Frame; class Frame;
class Scope; class Scope;
class FunctionIngredients; class FunctionIngredients;
class WhenInfo;
using IDPtr = IntrusivePtr<ID>; using IDPtr = IntrusivePtr<ID>;
using ScopePtr = IntrusivePtr<Scope>; using ScopePtr = IntrusivePtr<Scope>;
using ScriptFuncPtr = IntrusivePtr<ScriptFunc>;
using FunctionIngredientsPtr = std::shared_ptr<FunctionIngredients>;
enum ExprTag : int enum ExprTag : int
{ {
@ -1452,12 +1455,17 @@ protected:
class LambdaExpr final : public Expr class LambdaExpr final : public Expr
{ {
public: public:
LambdaExpr(std::unique_ptr<FunctionIngredients> ingredients, IDPList outer_ids, LambdaExpr(FunctionIngredientsPtr ingredients, IDPList outer_ids, std::string name = "",
StmtPtr when_parent = nullptr); StmtPtr when_parent = nullptr);
const std::string& Name() const { return my_name; } const std::string& Name() const { return my_name; }
const IDPList& OuterIDs() const { return outer_ids; } const IDPList& OuterIDs() const { return outer_ids; }
const FunctionIngredients& Ingredients() const { return *ingredients; }
// Lambda's potentially have their own private copy of captures,
// to enable updates to the set during script optimization.
using CaptureList = std::vector<FuncType::Capture>;
const std::optional<CaptureList>& GetCaptures() const { return captures; }
ValPtr Eval(Frame* f) const override; ValPtr Eval(Frame* f) const override;
TraversalCode Traverse(TraversalCallback* cb) const override; TraversalCode Traverse(TraversalCallback* cb) const override;
@ -1466,19 +1474,43 @@ public:
// Optimization-related: // Optimization-related:
ExprPtr Duplicate() override; ExprPtr Duplicate() override;
ExprPtr Inline(Inliner* inl) override;
const ScriptFuncPtr& PrimaryFunc() const { return primary_func; }
const FunctionIngredientsPtr& Ingredients() const { return ingredients; }
bool IsReduced(Reducer* c) const override;
bool HasReducedOps(Reducer* c) const override;
ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override; ExprPtr Reduce(Reducer* c, StmtPtr& red_stmt) override;
StmtPtr ReduceToSingletons(Reducer* c) override;
protected: protected:
// Constructor used for script optimization.
LambdaExpr(LambdaExpr* orig);
void ExprDescribe(ODesc* d) const override; void ExprDescribe(ODesc* d) const override;
private: private:
bool CheckCaptures(StmtPtr when_parent); friend class WhenInfo;
std::unique_ptr<FunctionIngredients> ingredients; // "Private" captures are captures that correspond to "when"
// condition locals. These aren't true captures in that they
// don't come from the outer frame when the lambda is constructed,
// but they otherwise behave like captures in that they persist
// across function invocations.
void SetPrivateCaptures(const IDSet& pcaps) { private_captures = pcaps; }
bool CheckCaptures(StmtPtr when_parent);
void BuildName();
void UpdateCaptures(Reducer* c);
FunctionIngredientsPtr ingredients;
ScriptFuncPtr primary_func;
IDPtr lambda_id; IDPtr lambda_id;
IDPList outer_ids; IDPList outer_ids;
std::optional<CaptureList> captures;
IDSet private_captures;
std::string my_name; std::string my_name;
}; };

View file

@ -139,11 +139,8 @@ static bool val_is_func(const ValPtr& v, ScriptFunc* func)
return v->AsFunc() == func; return v->AsFunc() == func;
} }
broker::expected<broker::data> Frame::SerializeCopyFrame() broker::expected<broker::data> Frame::Serialize()
{ {
broker::vector rval;
rval.emplace_back(std::string("CopyFrame"));
broker::vector body; broker::vector body;
for ( int i = 0; i < size; ++i ) for ( int i = 0; i < size; ++i )
@ -158,28 +155,18 @@ broker::expected<broker::data> Frame::SerializeCopyFrame()
body.emplace_back(std::move(val_tuple)); body.emplace_back(std::move(val_tuple));
} }
broker::vector rval;
rval.emplace_back(std::move(body)); rval.emplace_back(std::move(body));
return {std::move(rval)}; return {std::move(rval)};
} }
std::pair<bool, FramePtr> Frame::Unserialize(const broker::vector& data, std::pair<bool, FramePtr> Frame::Unserialize(const broker::vector& data)
const std::optional<FuncType::CaptureList>& captures)
{ {
if ( data.size() == 0 ) if ( data.size() == 0 )
return std::make_pair(true, nullptr); return std::make_pair(true, nullptr);
auto where = data.begin(); auto where = data.begin();
auto has_name = broker::get_if<std::string>(*where);
if ( ! has_name )
return std::make_pair(false, nullptr);
std::advance(where, 1);
if ( captures )
ASSERT(*has_name == "CopyFrame");
auto has_body = broker::get_if<broker::vector>(*where); auto has_body = broker::get_if<broker::vector>(*where);
if ( ! has_body ) if ( ! has_body )
return std::make_pair(false, nullptr); return std::make_pair(false, nullptr);

View file

@ -53,6 +53,13 @@ public:
*/ */
Frame(int size, const ScriptFunc* func, const zeek::Args* fn_args); Frame(int size, const ScriptFunc* func, const zeek::Args* fn_args);
/**
* Returns the size of the frame.
*
* @return the number of elements in the frame.
*/
int FrameSize() const { return size; }
/** /**
* @param n the index to get. * @param n the index to get.
* @return the value at index *n* of the underlying array. * @return the value at index *n* of the underlying array.
@ -165,16 +172,11 @@ public:
Frame* CloneForTrigger() const; Frame* CloneForTrigger() const;
/** /**
* Serializes the frame in support of copy semantics for lambdas: * Serializes the frame (only done for lambda/when captures) as a
* * sequence of two-element vectors, the first element reflecting
* [ "CopyFrame", serialized_values ] * the frame value, the second its type.
*
* where serialized_values are two-element vectors. A serialized_value
* has the result of calling broker::data_to_val on the value in the
* first index, and an integer representing that value's type in the
* second index.
*/ */
broker::expected<broker::data> SerializeCopyFrame(); broker::expected<broker::data> Serialize();
/** /**
* Instantiates a Frame from a serialized one. * Instantiates a Frame from a serialized one.
@ -182,13 +184,8 @@ public:
* @return a pair in which the first item is the status of the serialization; * @return a pair in which the first item is the status of the serialization;
* and the second is the unserialized frame with reference count +1, or * and the second is the unserialized frame with reference count +1, or
* null if the serialization wasn't successful. * null if the serialization wasn't successful.
*
* The *captures* argument, if non-nil, specifies that the frame
* reflects captures with copy-semantics rather than deprecated
* reference semantics.
*/ */
static std::pair<bool, FramePtr> static std::pair<bool, FramePtr> Unserialize(const broker::vector& data);
Unserialize(const broker::vector& data, const std::optional<FuncType::CaptureList>& captures);
// If the frame is run in the context of a trigger condition evaluation, // If the frame is run in the context of a trigger condition evaluation,
// the trigger needs to be registered. // the trigger needs to be registered.
@ -239,7 +236,7 @@ private:
*/ */
int current_offset; int current_offset;
/** Frame used for captures (if any) with copy semantics. */ /** Frame used for lambda/when captures. */
Frame* captures; Frame* captures;
/** Maps IDs to offsets into the "captures" frame. If the ID /** Maps IDs to offsets into the "captures" frame. If the ID

View file

@ -123,6 +123,14 @@ std::string render_call_stack()
return rval; return rval;
} }
void Func::AddBody(const detail::FunctionIngredients& ingr, detail::StmtPtr new_body)
{
if ( ! new_body )
new_body = ingr.Body();
AddBody(new_body, ingr.Inits(), ingr.FrameSize(), ingr.Priority(), ingr.Groups());
}
void Func::AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits, void Func::AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits,
size_t new_frame_size, int priority) size_t new_frame_size, int priority)
{ {
@ -130,6 +138,13 @@ void Func::AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& n
AddBody(new_body, new_inits, new_frame_size, priority, groups); AddBody(new_body, new_inits, new_frame_size, priority, groups);
} }
void Func::AddBody(detail::StmtPtr new_body, size_t new_frame_size)
{
std::vector<detail::IDPtr> no_inits;
std::set<EventGroupPtr> no_groups;
AddBody(new_body, no_inits, new_frame_size, 0, no_groups);
}
void Func::AddBody(detail::StmtPtr /* new_body */, void Func::AddBody(detail::StmtPtr /* new_body */,
const std::vector<detail::IDPtr>& /* new_inits */, size_t /* new_frame_size */, const std::vector<detail::IDPtr>& /* new_inits */, size_t /* new_frame_size */,
int /* priority */, const std::set<EventGroupPtr>& /* groups */) int /* priority */, const std::set<EventGroupPtr>& /* groups */)
@ -316,6 +331,15 @@ ScriptFunc::ScriptFunc(std::string _name, FuncTypePtr ft, std::vector<StmtPtr> b
ScriptFunc::~ScriptFunc() ScriptFunc::~ScriptFunc()
{ {
if ( captures_vec )
{
auto& cvec = *captures_vec;
auto& captures = *type->GetCaptures();
for ( auto i = 0u; i < captures.size(); ++i )
if ( captures[i].IsManaged() )
ZVal::DeleteManagedType(cvec[i]);
}
delete captures_frame; delete captures_frame;
delete captures_offset_mapping; delete captures_offset_mapping;
} }
@ -498,31 +522,77 @@ void ScriptFunc::CreateCaptures(Frame* f)
if ( ! captures ) if ( ! captures )
return; return;
// Create a private Frame to hold the values of captured variables, // Create *either* a private Frame to hold the values of captured
// and a mapping from those variables to their offsets in the Frame. // variables, and a mapping from those variables to their offsets
delete captures_frame; // in the Frame; *or* a ZVal frame if this script has a ZAM-compiled
delete captures_offset_mapping; // body.
captures_frame = new Frame(captures->size(), this, nullptr); ASSERT(bodies.size() == 1);
captures_offset_mapping = new OffsetMap;
if ( bodies[0].stmts->Tag() == STMT_ZAM )
captures_vec = std::make_unique<std::vector<ZVal>>();
else
{
delete captures_frame;
delete captures_offset_mapping;
captures_frame = new Frame(captures->size(), this, nullptr);
captures_offset_mapping = new OffsetMap;
}
int offset = 0; int offset = 0;
for ( const auto& c : *captures ) for ( const auto& c : *captures )
{ {
auto v = f->GetElementByID(c.id); auto v = f->GetElementByID(c.Id());
if ( v ) if ( v )
{ {
if ( c.deep_copy || ! v->Modifiable() ) if ( c.IsDeepCopy() || ! v->Modifiable() )
v = v->Clone(); v = v->Clone();
captures_frame->SetElement(offset, std::move(v)); if ( captures_vec )
// Don't use v->GetType() here, as that might
// be "any" and we need to convert.
captures_vec->push_back(ZVal(v, c.Id()->GetType()));
else
captures_frame->SetElement(offset, std::move(v));
} }
(*captures_offset_mapping)[c.id->Name()] = offset; else if ( captures_vec )
captures_vec->push_back(ZVal());
if ( ! captures_vec )
captures_offset_mapping->insert_or_assign(c.Id()->Name(), offset);
++offset; ++offset;
} }
} }
void ScriptFunc::CreateCaptures(std::unique_ptr<std::vector<ZVal>> cvec)
{
const auto& captures = *type->GetCaptures();
ASSERT(cvec->size() == captures.size());
ASSERT(bodies.size() == 1 && bodies[0].stmts->Tag() == STMT_ZAM);
captures_vec = std::move(cvec);
auto n = captures.size();
for ( auto i = 0U; i < n; ++i )
{
auto& c_i = captures[i];
auto& cv_i = (*captures_vec)[i];
if ( c_i.IsDeepCopy() )
{
auto& t = c_i.Id()->GetType();
auto new_cv_i = cv_i.ToVal(t)->Clone();
if ( c_i.IsManaged() )
ZVal::DeleteManagedType(cv_i);
cv_i = ZVal(new_cv_i, t);
}
}
}
void ScriptFunc::SetCaptures(Frame* f) void ScriptFunc::SetCaptures(Frame* f)
{ {
const auto& captures = type->GetCaptures(); const auto& captures = type->GetCaptures();
@ -536,7 +606,7 @@ void ScriptFunc::SetCaptures(Frame* f)
int offset = 0; int offset = 0;
for ( const auto& c : *captures ) for ( const auto& c : *captures )
{ {
(*captures_offset_mapping)[c.id->Name()] = offset; captures_offset_mapping->insert_or_assign(c.Id()->Name(), offset);
++offset; ++offset;
} }
} }
@ -595,11 +665,32 @@ void ScriptFunc::ReplaceBody(const StmtPtr& old_body, StmtPtr new_body)
bool ScriptFunc::DeserializeCaptures(const broker::vector& data) bool ScriptFunc::DeserializeCaptures(const broker::vector& data)
{ {
auto result = Frame::Unserialize(data, GetType()->GetCaptures()); auto result = Frame::Unserialize(data);
ASSERT(result.first); ASSERT(result.first);
SetCaptures(result.second.release()); auto& f = result.second;
if ( bodies[0].stmts->Tag() == STMT_ZAM )
{
auto& captures = *type->GetCaptures();
int n = f->FrameSize();
ASSERT(captures.size() == static_cast<size_t>(n));
auto cvec = std::make_unique<std::vector<ZVal>>();
for ( int i = 0; i < n; ++i )
{
auto& f_i = f->GetElement(i);
cvec->push_back(ZVal(f_i, captures[i].Id()->GetType()));
}
CreateCaptures(std::move(cvec));
}
else
SetCaptures(f.release());
return true; return true;
} }
@ -608,6 +699,9 @@ FuncPtr ScriptFunc::DoClone()
{ {
// ScriptFunc could hold a closure. In this case a clone of it must // ScriptFunc could hold a closure. In this case a clone of it must
// store a copy of this closure. // store a copy of this closure.
//
// We don't use make_intrusive<> directly because we're accessing
// a protected constructor.
auto other = IntrusivePtr{AdoptRef{}, new ScriptFunc()}; auto other = IntrusivePtr{AdoptRef{}, new ScriptFunc()};
CopyStateInto(other.get()); CopyStateInto(other.get());
@ -622,13 +716,43 @@ FuncPtr ScriptFunc::DoClone()
*other->captures_offset_mapping = *captures_offset_mapping; *other->captures_offset_mapping = *captures_offset_mapping;
} }
if ( captures_vec )
{
auto cv_i = captures_vec->begin();
other->captures_vec = std::make_unique<std::vector<ZVal>>();
for ( auto& c : *type->GetCaptures() )
{
// Need to clone cv_i.
auto& t_i = c.Id()->GetType();
auto cv_i_val = cv_i->ToVal(t_i)->Clone();
other->captures_vec->push_back(ZVal(cv_i_val, t_i));
++cv_i;
}
}
return other; return other;
} }
broker::expected<broker::data> ScriptFunc::SerializeCaptures() const broker::expected<broker::data> ScriptFunc::SerializeCaptures() const
{ {
if ( captures_vec )
{
auto& cv = *captures_vec;
auto& captures = *type->GetCaptures();
int n = captures_vec->size();
auto temp_frame = make_intrusive<Frame>(n, this, nullptr);
for ( int i = 0; i < n; ++i )
{
auto c_i = cv[i].ToVal(captures[i].Id()->GetType());
temp_frame->SetElement(i, c_i);
}
return temp_frame->Serialize();
}
if ( captures_frame ) if ( captures_frame )
return captures_frame->SerializeCopyFrame(); return captures_frame->Serialize();
// No captures, return an empty vector. // No captures, return an empty vector.
return broker::vector{}; return broker::vector{};

View file

@ -44,6 +44,7 @@ using IDPtr = IntrusivePtr<ID>;
using StmtPtr = IntrusivePtr<Stmt>; using StmtPtr = IntrusivePtr<Stmt>;
class ScriptFunc; class ScriptFunc;
class FunctionIngredients;
} // namespace detail } // namespace detail
@ -114,14 +115,18 @@ public:
return Invoke(&zargs); return Invoke(&zargs);
} }
// Add a new event handler to an existing function (event). // Various ways to add a new event handler to an existing function
// (event). The usual version to use is the first with its default
// parameter. All of the others are for use by script optimization,
// as is a non-default second parameter to the first method, which
// overrides the function body in "ingr".
void AddBody(const detail::FunctionIngredients& ingr, detail::StmtPtr new_body = nullptr);
virtual void AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits, virtual void AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits,
size_t new_frame_size, int priority, size_t new_frame_size, int priority,
const std::set<EventGroupPtr>& groups); const std::set<EventGroupPtr>& groups);
void AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits,
// Add a new event handler to an existing function (event). size_t new_frame_size, int priority = 0);
virtual void AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits, void AddBody(detail::StmtPtr new_body, size_t new_frame_size);
size_t new_frame_size, int priority = 0);
virtual void SetScope(detail::ScopePtr newscope); virtual void SetScope(detail::ScopePtr newscope);
virtual detail::ScopePtr GetScope() const { return scope; } virtual detail::ScopePtr GetScope() const { return scope; }
@ -190,6 +195,17 @@ public:
*/ */
void CreateCaptures(Frame* f); void CreateCaptures(Frame* f);
/**
* Uses the given set of ZVal's for captures. Note that this is
* different from the method above, which uses its argument to
* compute the captures, rather than here where they are pre-computed.
*
* Makes deep copies if required.
*
* @param cvec a vector of ZVal's corresponding to the captures.
*/
void CreateCaptures(std::unique_ptr<std::vector<ZVal>> cvec);
/** /**
* Returns the frame associated with this function for tracking * Returns the frame associated with this function for tracking
* captures, or nil if there isn't one. * captures, or nil if there isn't one.
@ -198,6 +214,18 @@ public:
*/ */
Frame* GetCapturesFrame() const { return captures_frame; } Frame* GetCapturesFrame() const { return captures_frame; }
/**
* Returns the set of ZVal's used for captures. It's okay to modify
* these as long as memory-management is done for managed entries.
*
* @return internal vector of ZVal's kept for persisting captures
*/
auto& GetCapturesVec() const
{
ASSERT(captures_vec);
return *captures_vec;
}
// Same definition as in Frame.h. // Same definition as in Frame.h.
using OffsetMap = std::unordered_map<std::string, int>; using OffsetMap = std::unordered_map<std::string, int>;
@ -273,6 +301,7 @@ protected:
/** /**
* Uses the given frame for captures, and generates the * Uses the given frame for captures, and generates the
* mapping from captured variables to offsets in the frame. * mapping from captured variables to offsets in the frame.
* Virtual so it can be modified for script optimization uses.
* *
* @param f the frame holding the values of capture variables * @param f the frame holding the values of capture variables
*/ */
@ -291,6 +320,9 @@ private:
OffsetMap* captures_offset_mapping = nullptr; OffsetMap* captures_offset_mapping = nullptr;
// Captures when using ZVal block instead of a Frame.
std::unique_ptr<std::vector<ZVal>> captures_vec;
// The most recently added/updated body ... // The most recently added/updated body ...
StmtPtr current_body; StmtPtr current_body;
@ -332,8 +364,7 @@ struct CallInfo
const zeek::Args& args; const zeek::Args& args;
}; };
// Struct that collects all the specifics defining a Func. Used for ScriptFuncs // Class that collects all the specifics defining a Func.
// with closures.
class FunctionIngredients class FunctionIngredients
{ {
public: public:
@ -344,14 +375,19 @@ public:
const IDPtr& GetID() const { return id; } const IDPtr& GetID() const { return id; }
const StmtPtr& Body() const { return body; } const StmtPtr& Body() const { return body; }
void SetBody(StmtPtr _body) { body = std::move(_body); }
const auto& Inits() const { return inits; } const auto& Inits() const { return inits; }
void ClearInits() { inits.clear(); }
size_t FrameSize() const { return frame_size; } size_t FrameSize() const { return frame_size; }
int Priority() const { return priority; } int Priority() const { return priority; }
const ScopePtr& Scope() const { return scope; } const ScopePtr& Scope() const { return scope; }
const auto& Groups() const { return groups; } const auto& Groups() const { return groups; }
// Used by script optimization to update lambda ingredients
// after compilation.
void SetFrameSize(size_t _frame_size) { frame_size = _frame_size; }
private: private:
IDPtr id; IDPtr id;
StmtPtr body; StmtPtr body;
@ -362,6 +398,8 @@ private:
std::set<EventGroupPtr> groups; std::set<EventGroupPtr> groups;
}; };
using FunctionIngredientsPtr = std::shared_ptr<FunctionIngredients>;
extern std::vector<CallInfo> call_stack; extern std::vector<CallInfo> call_stack;
/** /**

View file

@ -52,7 +52,6 @@ const char* stmt_name(StmtTag t)
"check-any-length", "check-any-length",
"compiled-C++", "compiled-C++",
"ZAM", "ZAM",
"ZAM-resumption",
"null", "null",
"assert", "assert",
}; };
@ -2005,35 +2004,7 @@ WhenInfo::WhenInfo(ExprPtr arg_cond, FuncType::CaptureList* arg_cl, bool arg_is_
if ( ! cl ) if ( ! cl )
cl = new zeek::FuncType::CaptureList; cl = new zeek::FuncType::CaptureList;
ProfileFunc cond_pf(cond.get()); BuildProfile();
when_expr_locals = cond_pf.Locals();
when_expr_globals = cond_pf.AllGlobals();
when_new_locals = cond_pf.WhenLocals();
// Make any when-locals part of our captures, if not already present,
// to enable sharing between the condition and the body/timeout code.
for ( auto& wl : when_new_locals )
{
bool is_present = false;
for ( auto& c : *cl )
if ( c.id == wl )
{
is_present = true;
break;
}
if ( ! is_present )
{
IDPtr wl_ptr = {NewRef{}, const_cast<ID*>(wl)};
cl->emplace_back(FuncType::Capture{wl_ptr, false});
}
// In addition, don't treat them as external locals that
// existed at the onset.
when_expr_locals.erase(wl);
}
// Create the internal lambda we'll use to manage the captures. // Create the internal lambda we'll use to manage the captures.
static int num_params = 0; // to ensure each is distinct static int num_params = 0; // to ensure each is distinct
@ -2049,12 +2020,39 @@ WhenInfo::WhenInfo(ExprPtr arg_cond, FuncType::CaptureList* arg_cl, bool arg_is_
if ( ! is_return ) if ( ! is_return )
lambda_ft->SetExpressionlessReturnOkay(true); lambda_ft->SetExpressionlessReturnOkay(true);
lambda_ft->SetCaptures(*cl);
auto id = current_scope()->GenerateTemporary("when-internal"); auto id = current_scope()->GenerateTemporary("when-internal");
id->SetType(lambda_ft); id->SetType(lambda_ft);
push_scope(std::move(id), nullptr); push_scope(std::move(id), nullptr);
auto arg_id = install_ID(lambda_param_id.c_str(), current_module.c_str(), false, false); param_id = install_ID(lambda_param_id.c_str(), current_module.c_str(), false, false);
arg_id->SetType(count_t); param_id->SetType(count_t);
}
WhenInfo::WhenInfo(const WhenInfo* orig)
{
if ( orig->cl )
{
cl = new FuncType::CaptureList;
*cl = *orig->cl;
}
cond = orig->OrigCond()->Duplicate();
// We don't duplicate these, as they'll be compiled separately.
s = orig->OrigBody();
timeout_s = orig->OrigBody();
timeout = orig->OrigTimeout();
if ( timeout )
timeout = timeout->Duplicate();
lambda = cast_intrusive<LambdaExpr>(orig->Lambda()->Duplicate());
is_return = orig->IsReturn();
BuildProfile();
} }
WhenInfo::WhenInfo(bool arg_is_return) : is_return(arg_is_return) WhenInfo::WhenInfo(bool arg_is_return) : is_return(arg_is_return)
@ -2063,10 +2061,49 @@ WhenInfo::WhenInfo(bool arg_is_return) : is_return(arg_is_return)
BuildInvokeElems(); BuildInvokeElems();
} }
void WhenInfo::BuildProfile()
{
ProfileFunc cond_pf(cond.get());
auto when_expr_locals_set = cond_pf.Locals();
when_expr_globals = cond_pf.AllGlobals();
when_new_locals = cond_pf.WhenLocals();
// Make any when-locals part of our captures, if not already present,
// to enable sharing between the condition and the body/timeout code.
for ( auto& wl : when_new_locals )
{
bool is_present = false;
for ( auto& c : *cl )
if ( c.Id() == wl )
{
is_present = true;
break;
}
if ( ! is_present )
{
IDPtr wl_ptr = {NewRef{}, const_cast<ID*>(wl)};
cl->emplace_back(FuncType::Capture{wl_ptr, false});
}
// In addition, don't treat them as external locals that
// existed at the onset.
when_expr_locals_set.erase(wl);
}
for ( auto& w : when_expr_locals_set )
{
// We need IDPtr versions of the locals so we can manipulate
// them during script optimization.
auto non_const_w = const_cast<ID*>(w);
when_expr_locals.push_back({NewRef{}, non_const_w});
}
}
void WhenInfo::Build(StmtPtr ws) void WhenInfo::Build(StmtPtr ws)
{ {
lambda_ft->SetCaptures(*cl);
// Our general strategy is to construct a single lambda (so that // Our general strategy is to construct a single lambda (so that
// the values of captures are shared across all of its elements) // the values of captures are shared across all of its elements)
// that's used for all three of the "when" components: condition, // that's used for all three of the "when" components: condition,
@ -2086,10 +2123,13 @@ void WhenInfo::Build(StmtPtr ws)
// First, the constants we'll need. // First, the constants we'll need.
BuildInvokeElems(); BuildInvokeElems();
if ( lambda )
// No need to build the lambda.
return;
auto true_const = make_intrusive<ConstExpr>(val_mgr->True()); auto true_const = make_intrusive<ConstExpr>(val_mgr->True());
// Access to the parameter that selects which action we're doing. // Access to the parameter that selects which action we're doing.
auto param_id = lookup_ID(lambda_param_id.c_str(), current_module.c_str());
ASSERT(param_id); ASSERT(param_id);
auto param = make_intrusive<NameExpr>(param_id); auto param = make_intrusive<NameExpr>(param_id);
@ -2109,11 +2149,14 @@ void WhenInfo::Build(StmtPtr ws)
auto shebang = make_intrusive<StmtList>(do_test, do_bodies, dummy_return); auto shebang = make_intrusive<StmtList>(do_test, do_bodies, dummy_return);
auto ingredients = std::make_unique<FunctionIngredients>(current_scope(), shebang, auto ingredients = std::make_shared<FunctionIngredients>(current_scope(), shebang,
current_module); current_module);
auto outer_ids = gather_outer_ids(pop_scope(), ingredients->Body()); auto outer_ids = gather_outer_ids(pop_scope(), ingredients->Body());
lambda = make_intrusive<LambdaExpr>(std::move(ingredients), std::move(outer_ids), ws); lambda = make_intrusive<LambdaExpr>(std::move(ingredients), std::move(outer_ids), "", ws);
lambda->SetPrivateCaptures(when_new_locals);
analyze_when_lambda(lambda.get());
} }
void WhenInfo::Instantiate(Frame* f) void WhenInfo::Instantiate(Frame* f)
@ -2205,8 +2248,7 @@ ValPtr WhenStmt::Exec(Frame* f, StmtFlowType& flow)
std::vector<ValPtr> local_aggrs; std::vector<ValPtr> local_aggrs;
for ( auto& l : wi->WhenExprLocals() ) for ( auto& l : wi->WhenExprLocals() )
{ {
IDPtr l_ptr = {NewRef{}, const_cast<ID*>(l)}; auto v = f->GetElementByID(l);
auto v = f->GetElementByID(l_ptr);
if ( v && v->Modifiable() ) if ( v && v->Modifiable() )
local_aggrs.emplace_back(std::move(v)); local_aggrs.emplace_back(std::move(v));
} }
@ -2226,6 +2268,23 @@ void WhenStmt::StmtDescribe(ODesc* d) const
{ {
Stmt::StmtDescribe(d); Stmt::StmtDescribe(d);
auto cl = wi->Captures();
if ( d->IsReadable() && ! cl->empty() )
{
d->Add("[");
for ( auto& c : *cl )
{
if ( &c != &(*cl)[0] )
d->AddSP(",");
if ( c.IsDeepCopy() )
d->Add("copy ");
d->Add(c.Id()->Name());
}
d->Add("]");
}
if ( d->IsReadable() ) if ( d->IsReadable() )
d->Add("("); d->Add("(");
@ -2267,32 +2326,13 @@ TraversalCode WhenStmt::Traverse(TraversalCallback* cb) const
TraversalCode tc = cb->PreStmt(this); TraversalCode tc = cb->PreStmt(this);
HANDLE_TC_STMT_PRE(tc); HANDLE_TC_STMT_PRE(tc);
auto wl = wi->Lambda(); tc = wi->Lambda()->Traverse(cb);
HANDLE_TC_STMT_PRE(tc);
if ( wl ) auto e = wi->TimeoutExpr();
if ( e )
{ {
tc = wl->Traverse(cb); tc = e->Traverse(cb);
HANDLE_TC_STMT_PRE(tc);
}
else
{
tc = wi->OrigCond()->Traverse(cb);
HANDLE_TC_STMT_PRE(tc);
tc = wi->OrigBody()->Traverse(cb);
HANDLE_TC_STMT_PRE(tc);
if ( wi->OrigTimeoutStmt() )
{
tc = wi->OrigTimeoutStmt()->Traverse(cb);
HANDLE_TC_STMT_PRE(tc);
}
}
if ( wi->OrigTimeout() )
{
tc = wi->OrigTimeout()->Traverse(cb);
HANDLE_TC_STMT_PRE(tc); HANDLE_TC_STMT_PRE(tc);
} }

View file

@ -450,6 +450,7 @@ public:
ReturnStmt(ExprPtr e, bool ignored); ReturnStmt(ExprPtr e, bool ignored);
// Optimization-related: // Optimization-related:
bool IsReduced(Reducer* c) const override;
StmtPtr DoReduce(Reducer* c) override; StmtPtr DoReduce(Reducer* c) override;
bool NoFlowAfter(bool ignore_break) const override { return true; } bool NoFlowAfter(bool ignore_break) const override { return true; }
@ -575,6 +576,9 @@ public:
// Takes ownership of the CaptureList. // Takes ownership of the CaptureList.
WhenInfo(ExprPtr cond, FuncType::CaptureList* cl, bool is_return); WhenInfo(ExprPtr cond, FuncType::CaptureList* cl, bool is_return);
// Used for duplication to support inlining.
WhenInfo(const WhenInfo* orig);
// Constructor used by script optimization to create a stub. // Constructor used by script optimization to create a stub.
WhenInfo(bool is_return); WhenInfo(bool is_return);
@ -615,6 +619,7 @@ public:
StmtPtr TimeoutStmt(); StmtPtr TimeoutStmt();
ExprPtr TimeoutExpr() const { return timeout; } ExprPtr TimeoutExpr() const { return timeout; }
void SetTimeoutExpr(ExprPtr e) { timeout = std::move(e); }
double TimeoutVal(Frame* f); double TimeoutVal(Frame* f);
FuncType::CaptureList* Captures() { return cl; } FuncType::CaptureList* Captures() { return cl; }
@ -624,10 +629,22 @@ public:
// The locals and globals used in the conditional expression // The locals and globals used in the conditional expression
// (other than newly introduced locals), necessary for registering // (other than newly introduced locals), necessary for registering
// the associated triggers for when their values change. // the associated triggers for when their values change.
const IDSet& WhenExprLocals() const { return when_expr_locals; } const auto& WhenExprLocals() const { return when_expr_locals; }
const IDSet& WhenExprGlobals() const { return when_expr_globals; } const auto& WhenExprGlobals() const { return when_expr_globals; }
// The locals introduced in the conditional expression.
const auto& WhenNewLocals() const { return when_new_locals; }
// Used for script optimization when in-lining needs to revise
// identifiers.
bool HasUnreducedIDs(Reducer* c) const;
void UpdateIDs(Reducer* c);
private: private:
// Profile the original AST elements to extract things like
// globals and locals used.
void BuildProfile();
// Build those elements we'll need for invoking our lambda. // Build those elements we'll need for invoking our lambda.
void BuildInvokeElems(); void BuildInvokeElems();
@ -639,8 +656,10 @@ private:
bool is_return = false; bool is_return = false;
// The name of parameter passed to the lambda. // The name of parameter passed to the lambda, and the corresponding
// identifier.
std::string lambda_param_id; std::string lambda_param_id;
IDPtr param_id;
// The expression for constructing the lambda, and its type. // The expression for constructing the lambda, and its type.
LambdaExprPtr lambda; LambdaExprPtr lambda;
@ -661,7 +680,7 @@ private:
ConstExprPtr two_const; ConstExprPtr two_const;
ConstExprPtr three_const; ConstExprPtr three_const;
IDSet when_expr_locals; std::vector<IDPtr> when_expr_locals;
IDSet when_expr_globals; IDSet when_expr_globals;
// Locals introduced via "local" in the "when" clause itself. // Locals introduced via "local" in the "when" clause itself.
@ -684,7 +703,7 @@ public:
StmtPtr TimeoutBody() const { return wi->TimeoutStmt(); } StmtPtr TimeoutBody() const { return wi->TimeoutStmt(); }
bool IsReturn() const { return wi->IsReturn(); } bool IsReturn() const { return wi->IsReturn(); }
const WhenInfo* Info() const { return wi; } WhenInfo* Info() const { return wi; }
void StmtDescribe(ODesc* d) const override; void StmtDescribe(ODesc* d) const override;
@ -692,9 +711,9 @@ public:
// Optimization-related: // Optimization-related:
StmtPtr Duplicate() override; StmtPtr Duplicate() override;
void Inline(Inliner* inl) override;
bool IsReduced(Reducer* c) const override; bool IsReduced(Reducer* c) const override;
StmtPtr DoReduce(Reducer* c) override;
private: private:
WhenInfo* wi; WhenInfo* wi;

View file

@ -31,7 +31,6 @@ enum StmtTag
STMT_CHECK_ANY_LEN, // internal reduced statement STMT_CHECK_ANY_LEN, // internal reduced statement
STMT_CPP, // compiled C++ STMT_CPP, // compiled C++
STMT_ZAM, // a ZAM function body STMT_ZAM, // a ZAM function body
STMT_ZAM_RESUMPTION, // resumes ZAM execution for "when" statements
STMT_NULL, STMT_NULL,
STMT_ASSERT, STMT_ASSERT,
#define NUM_STMTS (int(STMT_ASSERT) + 1) #define NUM_STMTS (int(STMT_ASSERT) + 1)

View file

@ -81,6 +81,8 @@ public:
// if the Val is null or it's disabled. The cache is managed using // if the Val is null or it's disabled. The cache is managed using
// void*'s so that the value can be associated with either a CallExpr // void*'s so that the value can be associated with either a CallExpr
// (for interpreted execution) or a C++ function (for compiled-to-C++). // (for interpreted execution) or a C++ function (for compiled-to-C++).
//
// Lookup() returned value must be Ref()'d if you want to hang onto it.
bool Cache(const void* obj, Val* val); bool Cache(const void* obj, Val* val);
Val* Lookup(const void* obj); Val* Lookup(const void* obj);

View file

@ -699,6 +699,15 @@ TypePtr SetType::ShallowClone()
SetType::~SetType() = default; SetType::~SetType() = default;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
FuncType::Capture::Capture(detail::IDPtr _id, bool _deep_copy)
: id(std::move(_id)), deep_copy(_deep_copy)
{
is_managed = id ? ZVal::IsManagedType(id->GetType()) : false;
}
#pragma GCC diagnostic pop
FuncType::FuncType(RecordTypePtr arg_args, TypePtr arg_yield, FunctionFlavor arg_flavor) FuncType::FuncType(RecordTypePtr arg_args, TypePtr arg_yield, FunctionFlavor arg_flavor)
: Type(TYPE_FUNC), args(std::move(arg_args)), arg_types(make_intrusive<TypeList>()), : Type(TYPE_FUNC), args(std::move(arg_args)), arg_types(make_intrusive<TypeList>()),
yield(std::move(arg_yield)) yield(std::move(arg_yield))
@ -788,9 +797,12 @@ bool FuncType::CheckArgs(const std::vector<TypePtr>& args, bool is_init, bool do
if ( my_args.size() != args.size() ) if ( my_args.size() != args.size() )
{ {
if ( do_warn ) if ( do_warn )
{
Warn(util::fmt("Wrong number of arguments for function. Expected %zu, got %zu.", Warn(util::fmt("Wrong number of arguments for function. Expected %zu, got %zu.",
args.size(), my_args.size())); args.size(), my_args.size()));
const_cast<FuncType*>(this)->reported_error = true; const_cast<FuncType*>(this)->reported_error = true;
}
return false; return false;
} }

View file

@ -511,10 +511,36 @@ public:
/** /**
* A single lambda "capture" (outer variable used in a lambda's body). * A single lambda "capture" (outer variable used in a lambda's body).
*/ */
struct Capture class Capture
{ {
detail::IDPtr id; public:
bool deep_copy; Capture(detail::IDPtr _id, bool _deep_copy);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Capture(const Capture&) = default;
Capture(Capture&&) = default;
Capture& operator=(const Capture&) = default;
Capture& operator=(Capture&&) = default;
~Capture() = default;
auto& Id() const { return id; }
bool IsDeepCopy() const { return deep_copy; }
bool IsManaged() const { return is_managed; }
// For script optimization:
void SetID(detail::IDPtr new_id) { id = std::move(new_id); }
#pragma GCC diagnostic pop
[[deprecated(
"Remove in v7.1. Use non-default constructor and associated accessors.")]] detail::
IDPtr id;
[[deprecated(
"Remove in v7.1. Use non-default constructor and associated accessors.")]] bool
deep_copy;
[[deprecated(
"Remove in v7.1. Use non-default constructor and associated accessors.")]] bool
is_managed;
}; };
using CaptureList = std::vector<Capture>; using CaptureList = std::vector<Capture>;

View file

@ -2787,6 +2787,11 @@ void TableVal::InitDefaultFunc(detail::Frame* f)
def_val = def_attr->GetExpr()->Eval(f); def_val = def_attr->GetExpr()->Eval(f);
} }
void TableVal::InitDefaultVal(ValPtr _def_val)
{
def_val = std::move(_def_val);
}
void TableVal::InitTimer(double delay) void TableVal::InitTimer(double delay)
{ {
timer = new TableValTimer(this, run_state::network_time + delay); timer = new TableValTimer(this, run_state::network_time + delay);

View file

@ -968,9 +968,13 @@ public:
// If the &default attribute is not a function, or the function has // If the &default attribute is not a function, or the function has
// already been initialized, this does nothing. Otherwise, evaluates // already been initialized, this does nothing. Otherwise, evaluates
// the function in the frame allowing it to capture its closure. // the function in the frame, allowing it to capture its closure.
void InitDefaultFunc(detail::Frame* f); void InitDefaultFunc(detail::Frame* f);
// An alternative that assigns the default value directly. Used
// by ZAM compilation.
void InitDefaultVal(ValPtr def_val);
void ClearTimer(detail::Timer* t) void ClearTimer(detail::Timer* t)
{ {
if ( timer == t ) if ( timer == t )

View file

@ -768,11 +768,10 @@ TraversalCode OuterIDBindingFinder::PreStmt(const Stmt* stmt)
if ( stmt->Tag() != STMT_WHEN ) if ( stmt->Tag() != STMT_WHEN )
return TC_CONTINUE; return TC_CONTINUE;
// The semantics of identifiers for the "when" statement are those
// of the lambda it's transformed into.
auto ws = static_cast<const WhenStmt*>(stmt); auto ws = static_cast<const WhenStmt*>(stmt);
ws->Info()->Lambda()->Traverse(this);
for ( auto& cl : ws->Info()->WhenExprLocals() )
outer_id_references.insert(const_cast<ID*>(cl.get()));
return TC_ABORTSTMT; return TC_ABORTSTMT;
} }
@ -789,18 +788,19 @@ TraversalCode OuterIDBindingFinder::PreExpr(const Expr* expr)
if ( expr->Tag() != EXPR_NAME ) if ( expr->Tag() != EXPR_NAME )
return TC_CONTINUE; return TC_CONTINUE;
auto* e = static_cast<const NameExpr*>(expr); auto e = static_cast<const NameExpr*>(expr);
auto id = e->Id();
if ( e->Id()->IsGlobal() ) if ( id->IsGlobal() )
return TC_CONTINUE; return TC_CONTINUE;
for ( const auto& scope : scopes ) for ( const auto& scope : scopes )
if ( scope->Find(e->Id()->Name()) ) if ( scope->Find(id->Name()) )
// Shadowing is not allowed, so if it's found at inner scope, it's // Shadowing is not allowed, so if it's found at inner scope, it's
// not something we have to worry about also being at outer scope. // not something we have to worry about also being at outer scope.
return TC_CONTINUE; return TC_CONTINUE;
outer_id_references.insert(e->Id()); outer_id_references.insert(id);
return TC_CONTINUE; return TC_CONTINUE;
} }
@ -845,9 +845,7 @@ void end_func(StmtPtr body, const char* module_name, bool free_of_conditionals)
id->SetConst(); id->SetConst();
} }
id->GetVal()->AsFunc()->AddBody(ingredients->Body(), ingredients->Inits(), id->GetVal()->AsFunc()->AddBody(*ingredients);
ingredients->FrameSize(), ingredients->Priority(),
ingredients->Groups());
script_coverage_mgr.AddFunction(id, ingredients->Body()); script_coverage_mgr.AddFunction(id, ingredients->Body());

View file

@ -38,7 +38,7 @@ extern void add_global(const IDPtr& id, TypePtr t, InitClass c, ExprPtr init,
extern StmtPtr add_local(IDPtr id, TypePtr t, InitClass c, ExprPtr init, extern StmtPtr add_local(IDPtr id, TypePtr t, InitClass c, ExprPtr init,
std::unique_ptr<std::vector<AttrPtr>> attr, DeclType dt); std::unique_ptr<std::vector<AttrPtr>> attr, DeclType dt);
extern ExprPtr add_and_assign_local(IDPtr id, ExprPtr init, ValPtr val = nullptr); extern ExprPtr add_and_assign_local(IDPtr id, ExprPtr init, ValPtr val);
extern void add_type(ID* id, TypePtr t, std::unique_ptr<std::vector<AttrPtr>> attr); extern void add_type(ID* id, TypePtr t, std::unique_ptr<std::vector<AttrPtr>> attr);

View file

@ -1566,7 +1566,7 @@ lambda_body:
// Gather the ingredients for a Func from the // Gather the ingredients for a Func from the
// current scope. // current scope.
auto ingredients = std::make_unique<FunctionIngredients>( auto ingredients = std::make_shared<FunctionIngredients>(
current_scope(), IntrusivePtr{AdoptRef{}, $3}, current_module.c_str()); current_scope(), IntrusivePtr{AdoptRef{}, $3}, current_module.c_str());
auto outer_ids = gather_outer_ids(pop_scope(), ingredients->Body()); auto outer_ids = gather_outer_ids(pop_scope(), ingredients->Body());
@ -1640,9 +1640,7 @@ capture:
delete [] $2; delete [] $2;
$$ = new FuncType::Capture; $$ = new FuncType::Capture(id, $1);
$$->id = id;
$$->deep_copy = $1;
} }
; ;

View file

@ -29,8 +29,8 @@ void CPPCompile::DeclareLambda(const LambdaExpr* l, const ProfileFunc* pf)
ASSERT(is_CPP_compilable(pf)); ASSERT(is_CPP_compilable(pf));
auto lname = Canonicalize(l->Name().c_str()) + "_lb"; auto lname = Canonicalize(l->Name().c_str()) + "_lb";
auto body = l->Ingredients().Body(); auto body = l->Ingredients()->Body();
auto l_id = l->Ingredients().GetID(); auto l_id = l->Ingredients()->GetID();
auto& ids = l->OuterIDs(); auto& ids = l->OuterIDs();
for ( auto id : ids ) for ( auto id : ids )

View file

@ -77,6 +77,12 @@ void CPPCompile::Compile(bool report_uncompilable)
continue; continue;
} }
if ( is_when_lambda(f) )
{
func.SetSkip(true);
continue;
}
const char* reason; const char* reason;
if ( IsCompilable(func, &reason) ) if ( IsCompilable(func, &reason) )
{ {
@ -150,7 +156,7 @@ void CPPCompile::Compile(bool report_uncompilable)
for ( const auto& l : pfs.Lambdas() ) for ( const auto& l : pfs.Lambdas() )
{ {
const auto& n = l->Name(); const auto& n = l->Name();
const auto body = l->Ingredients().Body().get(); const auto body = l->Ingredients()->Body().get();
if ( lambda_ASTs.count(n) > 0 ) if ( lambda_ASTs.count(n) > 0 )
// Reuse previous body. // Reuse previous body.
body_names[body] = body_names[lambda_ASTs[n]]; body_names[body] = body_names[lambda_ASTs[n]];
@ -176,7 +182,7 @@ void CPPCompile::Compile(bool report_uncompilable)
continue; continue;
CompileLambda(l, pfs.ExprProf(l).get()); CompileLambda(l, pfs.ExprProf(l).get());
lambda_ASTs[n] = l->Ingredients().Body().get(); lambda_ASTs[n] = l->Ingredients()->Body().get();
} }
NL(); NL();

View file

@ -1301,7 +1301,7 @@ string CPPCompile::GenLambdaClone(const LambdaExpr* l, bool all_deep)
if ( captures && ! IsNativeType(id_t) ) if ( captures && ! IsNativeType(id_t) )
{ {
for ( const auto& c : *captures ) for ( const auto& c : *captures )
if ( id == c.id && (c.deep_copy || all_deep) ) if ( id == c.Id() && (c.IsDeepCopy() || all_deep) )
arg = string("cast_intrusive<") + TypeName(id_t) + ">(" + arg + "->Clone())"; arg = string("cast_intrusive<") + TypeName(id_t) + ">(" + arg + "->Clone())";
} }

View file

@ -23,8 +23,8 @@ void CPPCompile::CompileFunc(const FuncInfo& func)
void CPPCompile::CompileLambda(const LambdaExpr* l, const ProfileFunc* pf) void CPPCompile::CompileLambda(const LambdaExpr* l, const ProfileFunc* pf)
{ {
auto lname = Canonicalize(l->Name().c_str()) + "_lb"; auto lname = Canonicalize(l->Name().c_str()) + "_lb";
auto body = l->Ingredients().Body(); auto body = l->Ingredients()->Body();
auto l_id = l->Ingredients().GetID(); auto l_id = l->Ingredients()->GetID();
auto& ids = l->OuterIDs(); auto& ids = l->OuterIDs();
DefineBody(l_id->GetType<FuncType>(), pf, lname, body, &ids, FUNC_FLAVOR_FUNCTION); DefineBody(l_id->GetType<FuncType>(), pf, lname, body, &ids, FUNC_FLAVOR_FUNCTION);

View file

@ -105,7 +105,7 @@ ValPtr when_invoke__CPP(Func* f, std::vector<ValPtr> args, Frame* frame, void* c
auto res = f->Invoke(&args, frame); auto res = f->Invoke(&args, frame);
if ( ! res ) if ( ! res )
throw DelayedCallException(); throw CPPDelayedCallException();
return res; return res;
} }

View file

@ -80,7 +80,7 @@ inline ValPtr invoke__CPP(Func* f, std::vector<ValPtr> args, Frame* frame)
extern ValPtr when_invoke__CPP(Func* f, std::vector<ValPtr> args, Frame* frame, void* caller_addr); extern ValPtr when_invoke__CPP(Func* f, std::vector<ValPtr> args, Frame* frame, void* caller_addr);
// Thrown when a call inside a "when" delays. // Thrown when a call inside a "when" delays.
class DelayedCallException : public InterpreterException class CPPDelayedCallException : public InterpreterException
{ {
}; };

View file

@ -420,9 +420,9 @@ void CPPCompile::GenWhenStmt(const WhenStmt* w)
NL(); NL();
Emit("std::vector<ValPtr> CPP__local_aggrs;"); Emit("std::vector<ValPtr> CPP__local_aggrs;");
for ( auto l : wi->WhenExprLocals() ) for ( auto& l : wi->WhenExprLocals() )
if ( IsAggr(l->GetType()) ) if ( IsAggr(l->GetType()) )
Emit("CPP__local_aggrs.emplace_back(%s);", IDNameStr(l)); Emit("CPP__local_aggrs.emplace_back(%s);", IDNameStr(l.get()));
Emit("CPP__wi->Instantiate(%s);", GenExpr(wi->Lambda(), GEN_NATIVE)); Emit("CPP__wi->Instantiate(%s);", GenExpr(wi->Lambda(), GEN_NATIVE));
@ -446,7 +446,7 @@ void CPPCompile::GenWhenStmt(const WhenStmt* w)
{ {
Emit("ValPtr retval = {NewRef{}, curr_t->Lookup(curr_assoc)};"); Emit("ValPtr retval = {NewRef{}, curr_t->Lookup(curr_assoc)};");
Emit("if ( ! retval )"); Emit("if ( ! retval )");
Emit("\tthrow DelayedCallException();"); Emit("\tthrow CPPDelayedCallException();");
Emit("return %s;", GenericValPtrToGT("retval", ret_type, GEN_NATIVE)); Emit("return %s;", GenericValPtrToGT("retval", ret_type, GEN_NATIVE));
} }

View file

@ -1851,7 +1851,12 @@ ExprPtr AssignExpr::ReduceToSingleton(Reducer* c, StmtPtr& red_stmt)
if ( val ) if ( val )
return make_intrusive<ConstExpr>(val); return make_intrusive<ConstExpr>(val);
return op1->AsRefExprPtr()->GetOp1(); auto lhs = op1->AsRefExprPtr()->GetOp1();
StmtPtr lhs_stmt;
auto new_op1 = lhs->ReduceToSingleton(c, lhs_stmt);
red_stmt = MergeStmts(red_stmt, lhs_stmt);
return new_op1;
} }
ExprPtr IndexSliceAssignExpr::Duplicate() ExprPtr IndexSliceAssignExpr::Duplicate()
@ -2337,7 +2342,7 @@ ExprPtr CallExpr::Duplicate()
auto func_type = func->GetType(); auto func_type = func->GetType();
auto in_hook = func_type->AsFuncType()->Flavor() == FUNC_FLAVOR_HOOK; auto in_hook = func_type->AsFuncType()->Flavor() == FUNC_FLAVOR_HOOK;
return SetSucc(new CallExpr(func_d, args_d, in_hook)); return SetSucc(new CallExpr(func_d, args_d, in_hook, in_when));
} }
ExprPtr CallExpr::Inline(Inliner* inl) ExprPtr CallExpr::Inline(Inliner* inl)
@ -2368,7 +2373,14 @@ bool CallExpr::HasReducedOps(Reducer* c) const
if ( ! func->IsSingleton(c) ) if ( ! func->IsSingleton(c) )
return NonReduced(this); return NonReduced(this);
return args->HasReducedOps(c); // We don't use args->HasReducedOps() here because for ListExpr's
// the method has some special-casing that isn't germane for calls.
for ( const auto& expr : args->Exprs() )
if ( ! expr->IsSingleton(c) )
return false;
return true;
} }
ExprPtr CallExpr::Reduce(Reducer* c, StmtPtr& red_stmt) ExprPtr CallExpr::Reduce(Reducer* c, StmtPtr& red_stmt)
@ -2386,9 +2398,7 @@ ExprPtr CallExpr::Reduce(Reducer* c, StmtPtr& red_stmt)
if ( ! func->IsSingleton(c) ) if ( ! func->IsSingleton(c) )
func = func->ReduceToSingleton(c, red_stmt); func = func->ReduceToSingleton(c, red_stmt);
StmtPtr red2_stmt; StmtPtr red2_stmt = args->ReduceToSingletons(c);
// We assume that ListExpr won't transform itself fundamentally.
(void)args->Reduce(c, red2_stmt);
// ### could check here for (1) pure function, and (2) all // ### could check here for (1) pure function, and (2) all
// arguments constants, and call it to fold right now. // arguments constants, and call it to fold right now.
@ -2415,23 +2425,60 @@ StmtPtr CallExpr::ReduceToSingletons(Reducer* c)
ExprPtr LambdaExpr::Duplicate() ExprPtr LambdaExpr::Duplicate()
{ {
auto ingr = std::make_unique<FunctionIngredients>(*ingredients); return SetSucc(new LambdaExpr(this));
ingr->SetBody(ingr->Body()->Duplicate());
return SetSucc(new LambdaExpr(std::move(ingr), outer_ids));
} }
ExprPtr LambdaExpr::Inline(Inliner* inl) bool LambdaExpr::IsReduced(Reducer* c) const
{ {
// Don't inline these, we currently don't get the closure right. if ( ! captures )
return ThisPtr(); return true;
for ( auto& cp : *captures )
{
auto& cid = cp.Id();
if ( private_captures.count(cid.get()) == 0 && ! c->ID_IsReduced(cid) )
return NonReduced(this);
}
return true;
}
bool LambdaExpr::HasReducedOps(Reducer* c) const
{
return IsReduced(c);
} }
ExprPtr LambdaExpr::Reduce(Reducer* c, StmtPtr& red_stmt) ExprPtr LambdaExpr::Reduce(Reducer* c, StmtPtr& red_stmt)
{ {
if ( c->Optimizing() ) if ( c->Optimizing() )
return ThisPtr(); return ThisPtr();
else
return AssignToTemporary(c, red_stmt); UpdateCaptures(c);
return AssignToTemporary(c, red_stmt);
}
StmtPtr LambdaExpr::ReduceToSingletons(Reducer* c)
{
UpdateCaptures(c);
return nullptr;
}
void LambdaExpr::UpdateCaptures(Reducer* c)
{
if ( captures )
{
for ( auto& cp : *captures )
{
auto& cid = cp.Id();
if ( private_captures.count(cid.get()) == 0 )
cp.SetID(c->UpdateID(cid));
}
c->UpdateIDs(&outer_ids);
}
} }
ExprPtr EventExpr::Duplicate() ExprPtr EventExpr::Duplicate()
@ -2558,11 +2605,14 @@ StmtPtr ListExpr::ReduceToSingletons(Reducer* c)
loop_over_list(exprs, i) loop_over_list(exprs, i)
{ {
if ( exprs[i]->IsSingleton(c) ) auto& e_i = exprs[i];
if ( e_i->IsSingleton(c) )
continue; continue;
StmtPtr e_stmt; StmtPtr e_stmt;
auto old = exprs.replace(i, exprs[i]->Reduce(c, e_stmt).release()); auto new_e_i = e_i->ReduceToSingleton(c, e_stmt);
auto old = exprs.replace(i, new_e_i.release());
Unref(old); Unref(old);
if ( e_stmt ) if ( e_stmt )
@ -2696,6 +2746,13 @@ void InlineExpr::ExprDescribe(ODesc* d) const
{ {
d->Add("inline("); d->Add("inline(");
args->Describe(d); args->Describe(d);
d->Add(")(");
for ( auto& p : params )
{
if ( &p != &params[0] )
d->AddSP(",");
d->Add(p->Name());
}
d->Add("){"); d->Add("){");
body->Describe(d); body->Describe(d);
d->Add("}"); d->Add("}");

View file

@ -196,12 +196,6 @@ TraversalCode GenIDDefs::PreStmt(const Stmt* s)
return TC_ABORTSTMT; return TC_ABORTSTMT;
} }
case STMT_WHEN:
{
// ### punt on these for now, need to reflect on bindings.
return TC_ABORTSTMT;
}
default: default:
return TC_CONTINUE; return TC_CONTINUE;
} }

View file

@ -115,16 +115,21 @@ void Inliner::Analyze()
const auto& func = func_ptr.get(); const auto& func = func_ptr.get();
const auto& body = f.Body(); const auto& body = f.Body();
if ( ! should_analyze(func_ptr, body) )
continue;
// Candidates are non-event, non-hook, non-recursive, // Candidates are non-event, non-hook, non-recursive,
// non-compiled functions ... that don't use lambdas or when's, // non-compiled functions ...
// since we don't currently compute the closures/frame if ( func->Flavor() != FUNC_FLAVOR_FUNCTION )
// sizes for them correctly, and more fundamentally since continue;
// we don't compile them and hence inlining them will
// make the parent non-compilable. if ( non_recursive_funcs.count(func) == 0 )
if ( should_analyze(func_ptr, body) && func->Flavor() == FUNC_FLAVOR_FUNCTION && continue;
non_recursive_funcs.count(func) > 0 && f.Profile()->NumLambdas() == 0 &&
f.Profile()->NumWhenStmts() == 0 && body->Tag() != STMT_CPP ) if ( body->Tag() == STMT_CPP )
inline_ables.insert(func); continue;
inline_ables.insert(func);
} }
for ( auto& f : funcs ) for ( auto& f : funcs )
@ -165,6 +170,11 @@ ExprPtr Inliner::CheckForInlining(CallExprPtr c)
// We don't inline indirect calls. // We don't inline indirect calls.
return c; return c;
if ( c->IsInWhen() )
// Don't inline these, as doing so requires propagating
// the in-when attribute to the inlined function body.
return c;
auto n = f->AsNameExpr(); auto n = f->AsNameExpr();
auto func = n->Id(); auto func = n->Id();

View file

@ -28,7 +28,23 @@ ProfileFunc::ProfileFunc(const Func* func, const StmtPtr& body, bool _abs_rec_fi
profiled_func = func; profiled_func = func;
profiled_body = body.get(); profiled_body = body.get();
abs_rec_fields = _abs_rec_fields; abs_rec_fields = _abs_rec_fields;
Profile(func->GetType().get(), body);
auto ft = func->GetType()->AsFuncType();
auto& fcaps = ft->GetCaptures();
if ( fcaps )
{
int offset = 0;
for ( auto& c : *fcaps )
{
auto cid = c.Id().get();
captures.insert(cid);
captures_offsets[cid] = offset++;
}
}
Profile(ft, body);
} }
ProfileFunc::ProfileFunc(const Stmt* s, bool _abs_rec_fields) ProfileFunc::ProfileFunc(const Stmt* s, bool _abs_rec_fields)
@ -48,10 +64,15 @@ ProfileFunc::ProfileFunc(const Expr* e, bool _abs_rec_fields)
{ {
auto func = e->AsLambdaExpr(); auto func = e->AsLambdaExpr();
for ( auto oid : func->OuterIDs() ) int offset = 0;
captures.insert(oid);
Profile(func->GetType()->AsFuncType(), func->Ingredients().Body()); for ( auto oid : func->OuterIDs() )
{
captures.insert(oid);
captures_offsets[oid] = offset++;
}
Profile(func->GetType()->AsFuncType(), func->Ingredients()->Body());
} }
else else
@ -91,9 +112,9 @@ TraversalCode ProfileFunc::PreStmt(const Stmt* s)
auto w = s->AsWhenStmt(); auto w = s->AsWhenStmt();
auto wi = w->Info(); auto wi = w->Info();
auto wl = wi ? wi->Lambda() : nullptr;
if ( wl ) for ( auto wl : wi->WhenNewLocals() )
lambdas.push_back(wl.get()); when_locals.insert(wl);
} }
break; break;
@ -171,6 +192,11 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e)
auto n = e->AsNameExpr(); auto n = e->AsNameExpr();
auto id = n->Id(); auto id = n->Id();
// Turns out that NameExpr's can be constructed using a
// different Type* than that of the identifier itself,
// so be sure we track the latter too.
TrackType(id->GetType());
if ( id->IsGlobal() ) if ( id->IsGlobal() )
{ {
globals.insert(id); globals.insert(id);
@ -179,30 +205,24 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e)
const auto& t = id->GetType(); const auto& t = id->GetType();
if ( t->Tag() == TYPE_FUNC && t->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT ) if ( t->Tag() == TYPE_FUNC && t->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT )
events.insert(id->Name()); events.insert(id->Name());
break;
} }
else // This is a tad ugly. Unfortunately due to the
{ // weird way that Zeek function *declarations* work,
// This is a tad ugly. Unfortunately due to the // there's no reliable way to get the list of
// weird way that Zeek function *declarations* work, // parameters for a function *definition*, since
// there's no reliable way to get the list of // they can have different names than what's present
// parameters for a function *definition*, since // in the declaration. So we identify them directly,
// they can have different names than what's present // by knowing that they come at the beginning of the
// in the declaration. So we identify them directly, // frame ... and being careful to avoid misconfusing
// by knowing that they come at the beginning of the // a lambda capture with a low frame offset as a
// frame ... and being careful to avoid misconfusing // parameter.
// a lambda capture with a low frame offset as a if ( captures.count(id) == 0 && id->Offset() < num_params )
// parameter. params.insert(id);
if ( captures.count(id) == 0 && id->Offset() < num_params )
params.insert(id);
locals.insert(id); locals.insert(id);
}
// Turns out that NameExpr's can be constructed using a
// different Type* than that of the identifier itself,
// so be sure we track the latter too.
TrackType(id->GetType());
break; break;
} }
@ -350,7 +370,12 @@ TraversalCode ProfileFunc::PreExpr(const Expr* e)
params.insert(i); params.insert(i);
} }
// Avoid recursing into the body. // 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());
return TC_ABORTSTMT; return TC_ABORTSTMT;
} }

View file

@ -100,6 +100,8 @@ public:
const IDSet& Globals() const { return globals; } const IDSet& Globals() const { return globals; }
const IDSet& AllGlobals() const { return all_globals; } const IDSet& AllGlobals() const { return all_globals; }
const IDSet& Locals() const { return locals; } const IDSet& Locals() const { return locals; }
const IDSet& Captures() const { return captures; }
const auto& CapturesOffsets() const { return captures_offsets; }
const IDSet& WhenLocals() const { return when_locals; } const IDSet& WhenLocals() const { return when_locals; }
const IDSet& Params() const { return params; } const IDSet& Params() const { return params; }
const std::unordered_map<const ID*, int>& Assignees() const { return assignees; } const std::unordered_map<const ID*, int>& Assignees() const { return assignees; }
@ -208,6 +210,9 @@ protected:
// If we're profiling a lambda function, this holds the captures. // If we're profiling a lambda function, this holds the captures.
IDSet captures; IDSet captures;
// This maps capture identifiers to their offsets.
std::map<const ID*, int> captures_offsets;
// Constants seen in the function. // Constants seen in the function.
std::vector<const ConstExpr*> constants; std::vector<const ConstExpr*> constants;
@ -224,7 +229,8 @@ protected:
// The same, but in a deterministic order, with duplicates removed. // The same, but in a deterministic order, with duplicates removed.
std::vector<const Type*> ordered_types; std::vector<const Type*> ordered_types;
// Script functions that this script calls. // Script functions that this script calls. Includes calls made
// by lambdas and when bodies, as the goal is to identify recursion.
std::unordered_set<ScriptFunc*> script_calls; std::unordered_set<ScriptFunc*> script_calls;
// Same for BiF's, though for them we record the corresponding global // Same for BiF's, though for them we record the corresponding global

View file

@ -36,15 +36,35 @@ static ZAMCompiler* ZAM = nullptr;
static bool generating_CPP = false; static bool generating_CPP = false;
static std::string CPP_dir; // where to generate C++ code static std::string CPP_dir; // where to generate C++ code
static std::unordered_set<const ScriptFunc*> lambdas;
static std::unordered_set<const ScriptFunc*> when_lambdas;
static ScriptFuncPtr global_stmts; static ScriptFuncPtr global_stmts;
void analyze_func(ScriptFuncPtr f) void analyze_func(ScriptFuncPtr f)
{ {
// Even if we're analyzing only a subset of the scripts, we still // Even if we're analyzing only a subset of the scripts, we still
// track all functions here because the inliner will need the full list. // track all functions here because the inliner will need the full list.
ASSERT(f->GetScope());
funcs.emplace_back(f, f->GetScope(), f->CurrentBody(), f->CurrentPriority()); funcs.emplace_back(f, f->GetScope(), f->CurrentBody(), f->CurrentPriority());
} }
void analyze_lambda(LambdaExpr* l)
{
auto& pf = l->PrimaryFunc();
analyze_func(pf);
lambdas.insert(pf.get());
}
void analyze_when_lambda(LambdaExpr* l)
{
when_lambdas.insert(l->PrimaryFunc().get());
}
bool is_when_lambda(const ScriptFunc* f)
{
return when_lambdas.count(f) > 0;
}
const FuncInfo* analyze_global_stmts(Stmt* stmts) const FuncInfo* analyze_global_stmts(Stmt* stmts)
{ {
// We ignore analysis_options.only_{files,funcs} - if they're in use, later // We ignore analysis_options.only_{files,funcs} - if they're in use, later
@ -208,7 +228,8 @@ static void optimize_func(ScriptFunc* f, std::shared_ptr<ProfileFunc> pf, ScopeP
rc->SetReadyToOptimize(); rc->SetReadyToOptimize();
auto ud = std::make_shared<UseDefs>(body, rc); auto ft = cast_intrusive<FuncType>(f->GetType());
auto ud = std::make_shared<UseDefs>(body, rc, ft);
ud->Analyze(); ud->Analyze();
if ( analysis_options.dump_uds ) if ( analysis_options.dump_uds )
@ -216,6 +237,9 @@ static void optimize_func(ScriptFunc* f, std::shared_ptr<ProfileFunc> pf, ScopeP
new_body = ud->RemoveUnused(); new_body = ud->RemoveUnused();
if ( analysis_options.dump_xform )
printf("Post removal of unused: %s\n", obj_desc(new_body.get()).c_str());
if ( new_body != body ) if ( new_body != body )
{ {
f->ReplaceBody(body, new_body); f->ReplaceBody(body, new_body);
@ -442,10 +466,10 @@ static void analyze_scripts_for_ZAM(std::unique_ptr<ProfileFuncs>& pfs)
} }
// Re-profile the functions, now without worrying about compatibility // Re-profile the functions, now without worrying about compatibility
// with compilation to C++. Note that the first profiling pass earlier // with compilation to C++.
// may have marked some of the functions as to-skip, so first clear
// those markings. Once we have full compile-to-C++ and ZAM support // The first profiling pass earlier may have marked some of the
// for all Zeek language features, we can remove the re-profiling here. // functions as to-skip, so clear those markings.
for ( auto& f : funcs ) for ( auto& f : funcs )
f.SetSkip(false); f.SetSkip(false);
@ -496,6 +520,7 @@ static void analyze_scripts_for_ZAM(std::unique_ptr<ProfileFuncs>& pfs)
for ( auto& f : funcs ) for ( auto& f : funcs )
{ {
auto func = f.Func(); auto func = f.Func();
bool is_lambda = lambdas.count(func) > 0;
if ( ! analysis_options.only_funcs.empty() || ! analysis_options.only_files.empty() ) if ( ! analysis_options.only_funcs.empty() || ! analysis_options.only_files.empty() )
{ {
@ -503,14 +528,17 @@ static void analyze_scripts_for_ZAM(std::unique_ptr<ProfileFuncs>& pfs)
continue; continue;
} }
else if ( ! analysis_options.compile_all && inl && inl->WasInlined(func) && else if ( ! analysis_options.compile_all && ! is_lambda && inl && inl->WasInlined(func) &&
func_used_indirectly.count(func) == 0 ) func_used_indirectly.count(func) == 0 )
{
// No need to compile as it won't be called directly. // No need to compile as it won't be called directly.
continue; continue;
}
auto new_body = f.Body(); auto new_body = f.Body();
optimize_func(func, f.ProfilePtr(), f.Scope(), new_body); optimize_func(func, f.ProfilePtr(), f.Scope(), new_body);
f.SetBody(new_body); f.SetBody(new_body);
did_one = true; did_one = true;
} }
@ -593,6 +621,9 @@ void analyze_scripts(bool no_unused_warnings)
// At this point we're done with C++ considerations, so instead // At this point we're done with C++ considerations, so instead
// are compiling to ZAM. // are compiling to ZAM.
analyze_scripts_for_ZAM(pfs); analyze_scripts_for_ZAM(pfs);
if ( reporter->Errors() > 0 )
reporter->FatalError("Optimized script execution aborted due to errors");
} }
void profile_script_execution() void profile_script_execution()

View file

@ -138,7 +138,6 @@ public:
std::shared_ptr<ProfileFunc> ProfilePtr() const { return pf; } std::shared_ptr<ProfileFunc> ProfilePtr() const { return pf; }
void SetBody(StmtPtr new_body) { body = std::move(new_body); } void SetBody(StmtPtr new_body) { body = std::move(new_body); }
// void SetProfile(std::shared_ptr<ProfileFunc> _pf);
void SetProfile(std::shared_ptr<ProfileFunc> _pf) { pf = std::move(_pf); } void SetProfile(std::shared_ptr<ProfileFunc> _pf) { pf = std::move(_pf); }
// The following provide a way of marking FuncInfo's as // The following provide a way of marking FuncInfo's as
@ -168,6 +167,16 @@ extern std::unordered_set<const Func*> non_recursive_funcs;
// Analyze a given function for optimization. // Analyze a given function for optimization.
extern void analyze_func(ScriptFuncPtr f); extern void analyze_func(ScriptFuncPtr f);
// Same, for lambdas.
extern void analyze_lambda(LambdaExpr* f);
// Same, for lambdas used in "when" statements. For these, analyze_lambda()
// has already been called.
extern void analyze_when_lambda(LambdaExpr* f);
// Whether a given script function is a "when" lambda.
extern bool is_when_lambda(const ScriptFunc* f);
// Analyze the given top-level statement(s) for optimization. Returns // Analyze the given top-level statement(s) for optimization. Returns
// a pointer to a FuncInfo for an argument-less quasi-function that can // a pointer to a FuncInfo for an argument-less quasi-function that can
// be Invoked, or its body executed directly, to execute the statements. // be Invoked, or its body executed directly, to execute the statements.

View file

@ -646,21 +646,26 @@ StmtPtr ReturnStmt::Duplicate()
ReturnStmt::ReturnStmt(ExprPtr arg_e, bool ignored) : ExprStmt(STMT_RETURN, std::move(arg_e)) { } ReturnStmt::ReturnStmt(ExprPtr arg_e, bool ignored) : ExprStmt(STMT_RETURN, std::move(arg_e)) { }
bool ReturnStmt::IsReduced(Reducer* c) const
{
if ( ! e || e->IsSingleton(c) )
return true;
return NonReduced(e.get());
}
StmtPtr ReturnStmt::DoReduce(Reducer* c) StmtPtr ReturnStmt::DoReduce(Reducer* c)
{ {
if ( ! e ) if ( ! e )
return ThisPtr(); return ThisPtr();
if ( c->Optimizing() ) if ( c->Optimizing() )
{
e = c->OptExpr(e); e = c->OptExpr(e);
return ThisPtr();
}
if ( ! e->IsSingleton(c) ) else if ( ! e->IsSingleton(c) )
{ {
StmtPtr red_e_stmt; StmtPtr red_e_stmt;
e = e->Reduce(c, red_e_stmt); e = e->ReduceToSingleton(c, red_e_stmt);
if ( red_e_stmt ) if ( red_e_stmt )
{ {
@ -934,34 +939,85 @@ StmtPtr AssertStmt::DoReduce(Reducer* c)
return make_intrusive<NullStmt>(); return make_intrusive<NullStmt>();
} }
StmtPtr WhenStmt::Duplicate() bool WhenInfo::HasUnreducedIDs(Reducer* c) const
{ {
FuncType::CaptureList* cl_dup = nullptr; for ( auto& cp : *cl )
if ( wi->Captures() )
{ {
cl_dup = new FuncType::CaptureList; auto cid = cp.Id();
*cl_dup = *wi->Captures();
if ( when_new_locals.count(cid.get()) == 0 && ! c->ID_IsReduced(cp.Id()) )
return true;
} }
auto new_wi = new WhenInfo(Cond(), cl_dup, IsReturn()); for ( auto& l : when_expr_locals )
new_wi->AddBody(Body()); if ( ! c->ID_IsReduced(l) )
new_wi->AddTimeout(TimeoutExpr(), TimeoutBody()); return true;
return SetSucc(new WhenStmt(wi)); return false;
} }
void WhenStmt::Inline(Inliner* inl) void WhenInfo::UpdateIDs(Reducer* c)
{ {
// Don't inline, since we currently don't correctly capture for ( auto& cp : *cl )
// the frames of closures. {
auto& cid = cp.Id();
if ( when_new_locals.count(cid.get()) == 0 )
cp.SetID(c->UpdateID(cid));
}
for ( auto& l : when_expr_locals )
l = c->UpdateID(l);
}
StmtPtr WhenStmt::Duplicate()
{
return SetSucc(new WhenStmt(new WhenInfo(wi)));
} }
bool WhenStmt::IsReduced(Reducer* c) const bool WhenStmt::IsReduced(Reducer* c) const
{ {
// We consider these always reduced because they're not if ( wi->HasUnreducedIDs(c) )
// candidates for any further optimization. return false;
return true;
if ( ! wi->Lambda()->IsReduced(c) )
return false;
if ( ! wi->TimeoutExpr() )
return true;
return wi->TimeoutExpr()->IsReduced(c);
}
StmtPtr WhenStmt::DoReduce(Reducer* c)
{
if ( ! c->Optimizing() )
{
wi->UpdateIDs(c);
(void)wi->Lambda()->ReduceToSingletons(c);
}
auto e = wi->TimeoutExpr();
if ( ! e )
return ThisPtr();
if ( c->Optimizing() )
wi->SetTimeoutExpr(c->OptExpr(e));
else if ( ! e->IsSingleton(c) )
{
StmtPtr red_e_stmt;
auto new_e = e->ReduceToSingleton(c, red_e_stmt);
wi->SetTimeoutExpr(new_e);
if ( red_e_stmt )
{
auto s = make_intrusive<StmtList>(red_e_stmt, ThisPtr());
return TransformMe(s, c);
}
}
return ThisPtr();
} }
CatchReturnStmt::CatchReturnStmt(StmtPtr _block, NameExprPtr _ret_var) : Stmt(STMT_CATCH_RETURN) CatchReturnStmt::CatchReturnStmt(StmtPtr _block, NameExprPtr _ret_var) : Stmt(STMT_CATCH_RETURN)

View file

@ -17,10 +17,11 @@ void UseDefSet::Dump() const
printf(" %s", u->Name()); printf(" %s", u->Name());
} }
UseDefs::UseDefs(StmtPtr _body, std::shared_ptr<Reducer> _rc) UseDefs::UseDefs(StmtPtr _body, std::shared_ptr<Reducer> _rc, FuncTypePtr _ft)
{ {
body = std::move(_body); body = std::move(_body);
rc = std::move(_rc); rc = std::move(_rc);
ft = std::move(_ft);
} }
void UseDefs::Analyze() void UseDefs::Analyze()
@ -164,6 +165,13 @@ bool UseDefs::CheckIfUnused(const Stmt* s, const ID* id, bool report)
if ( id->IsGlobal() ) if ( id->IsGlobal() )
return false; return false;
if ( auto& captures = ft->GetCaptures() )
{
for ( auto& c : *captures )
if ( c.Id() == id )
return false;
}
auto uds = FindSuccUsage(s); auto uds = FindSuccUsage(s);
if ( ! uds || ! uds->HasID(id) ) if ( ! uds || ! uds->HasID(id) )
{ {
@ -283,9 +291,7 @@ UDs UseDefs::PropagateUDs(const Stmt* s, UDs succ_UDs, const Stmt* succ_stmt, bo
auto true_UDs = PropagateUDs(i->TrueBranch(), succ_UDs, succ_stmt, second_pass); auto true_UDs = PropagateUDs(i->TrueBranch(), succ_UDs, succ_stmt, second_pass);
auto false_UDs = PropagateUDs(i->FalseBranch(), succ_UDs, succ_stmt, second_pass); auto false_UDs = PropagateUDs(i->FalseBranch(), succ_UDs, succ_stmt, second_pass);
auto uds = CreateUDs(s, UD_Union(cond_UDs, true_UDs, false_UDs)); return CreateUDs(s, UD_Union(cond_UDs, true_UDs, false_UDs));
return uds;
} }
case STMT_INIT: case STMT_INIT:
@ -295,13 +301,17 @@ UDs UseDefs::PropagateUDs(const Stmt* s, UDs succ_UDs, const Stmt* succ_stmt, bo
return UseUDs(s, succ_UDs); return UseUDs(s, succ_UDs);
case STMT_WHEN: case STMT_WHEN:
// ### Once we support compiling functions with "when" {
// statements in them, we'll need to revisit this. auto w = s->AsWhenStmt();
// For now, we don't worry about it (because the current auto wi = w->Info();
// "when" body semantics of deep-copy frames has different auto uds = UD_Union(succ_UDs, ExprUDs(wi->Lambda().get()));
// implications than potentially switching those shallow-copy
// frames). auto timeout = wi->TimeoutExpr();
return UseUDs(s, succ_UDs); if ( timeout )
uds = UD_Union(uds, ExprUDs(timeout.get()));
return CreateUDs(s, uds);
}
case STMT_SWITCH: case STMT_SWITCH:
{ {
@ -450,6 +460,7 @@ UDs UseDefs::ExprUDs(const Expr* e)
switch ( e->Tag() ) switch ( e->Tag() )
{ {
case EXPR_NAME: case EXPR_NAME:
case EXPR_LAMBDA:
AddInExprUDs(uds, e); AddInExprUDs(uds, e);
break; break;
@ -482,19 +493,23 @@ UDs UseDefs::ExprUDs(const Expr* e)
break; break;
} }
case EXPR_CONST: case EXPR_TABLE_CONSTRUCTOR:
break;
case EXPR_LAMBDA:
{ {
auto l = static_cast<const LambdaExpr*>(e); auto t = static_cast<const TableConstructorExpr*>(e);
auto ids = l->OuterIDs(); AddInExprUDs(uds, t->GetOp1().get());
auto& t_attrs = t->GetAttrs();
auto def_attr = t_attrs ? t_attrs->Find(ATTR_DEFAULT) : nullptr;
auto& def_expr = def_attr ? def_attr->GetExpr() : nullptr;
if ( def_expr && def_expr->Tag() == EXPR_LAMBDA )
uds = ExprUDs(def_expr.get());
for ( const auto& id : ids )
AddID(uds, id);
break; break;
} }
case EXPR_CONST:
break;
case EXPR_CALL: case EXPR_CALL:
{ {
auto c = e->AsCallExpr(); auto c = e->AsCallExpr();
@ -577,6 +592,14 @@ void UseDefs::AddInExprUDs(UDs uds, const Expr* e)
AddInExprUDs(uds, e->AsFieldExpr()->Op()); AddInExprUDs(uds, e->AsFieldExpr()->Op());
break; break;
case EXPR_LAMBDA:
{
auto outer_ids = e->AsLambdaExpr()->OuterIDs();
for ( auto& i : outer_ids )
AddID(uds, i);
break;
}
case EXPR_CONST: case EXPR_CONST:
// Nothing to do. // Nothing to do.
break; break;

View file

@ -51,7 +51,7 @@ class Reducer;
class UseDefs class UseDefs
{ {
public: public:
UseDefs(StmtPtr body, std::shared_ptr<Reducer> rc); UseDefs(StmtPtr body, std::shared_ptr<Reducer> rc, FuncTypePtr ft);
// Does a full pass over the function body's AST. We can wind // Does a full pass over the function body's AST. We can wind
// up doing this multiple times because when we use use-defs to // up doing this multiple times because when we use use-defs to
@ -173,6 +173,7 @@ private:
StmtPtr body; StmtPtr body;
std::shared_ptr<Reducer> rc; std::shared_ptr<Reducer> rc;
FuncTypePtr ft;
}; };
} // zeek::detail } // zeek::detail

View file

@ -258,9 +258,9 @@ bool ZAMCompiler::PruneUnused()
KillInst(i); KillInst(i);
} }
if ( inst->IsGlobalLoad() ) if ( inst->IsNonLocalLoad() )
{ {
// Any straight-line load of the same global // Any straight-line load of the same global/capture
// is redundant. // is redundant.
for ( unsigned int j = i + 1; j < insts1.size(); ++j ) for ( unsigned int j = i + 1; j < insts1.size(); ++j )
{ {
@ -277,14 +277,14 @@ bool ZAMCompiler::PruneUnused()
// Inbound branch ends block. // Inbound branch ends block.
break; break;
if ( i1->aux && i1->aux->can_change_globals ) if ( i1->aux && i1->aux->can_change_non_locals )
break; break;
if ( ! i1->IsGlobalLoad() ) if ( ! i1->IsNonLocalLoad() )
continue; continue;
if ( i1->v2 == inst->v2 ) if ( i1->v2 == inst->v2 && i1->IsGlobalLoad() == inst->IsGlobalLoad() )
{ // Same global { // Same global/capture
did_prune = true; did_prune = true;
KillInst(i1); KillInst(i1);
} }
@ -299,9 +299,10 @@ bool ZAMCompiler::PruneUnused()
// Variable is used, keep assignment. // Variable is used, keep assignment.
continue; continue;
if ( frame_denizens[slot]->IsGlobal() ) auto& id = frame_denizens[slot];
if ( id->IsGlobal() || IsCapture(id) )
{ {
// Extend the global's range to the end of the // Extend the global/capture's range to the end of the
// function. // function.
denizen_ending[slot] = insts1.back(); denizen_ending[slot] = insts1.back();
continue; continue;
@ -466,18 +467,30 @@ void ZAMCompiler::ComputeFrameLifetimes()
break; break;
} }
case OP_LAMBDA_VV:
{
auto aux = inst->aux;
int n = aux->n;
auto& slots = aux->slots;
for ( int i = 0; i < n; ++i )
ExtendLifetime(slots[i], EndOfLoop(inst, 1));
break;
}
default: default:
// Look for slots in auxiliary information. // Look for slots in auxiliary information.
auto aux = inst->aux; auto aux = inst->aux;
if ( ! aux || ! aux->slots ) if ( ! aux || ! aux->slots )
break; break;
for ( auto j = 0; j < aux->n; ++j ) int n = aux->n;
auto& slots = aux->slots;
for ( auto j = 0; j < n; ++j )
{ {
if ( aux->slots[j] < 0 ) if ( slots[j] < 0 )
continue; continue;
ExtendLifetime(aux->slots[j], EndOfLoop(inst, 1)); ExtendLifetime(slots[j], EndOfLoop(inst, 1));
} }
break; break;
} }
@ -759,7 +772,6 @@ void ZAMCompiler::ReMapVar(const ID* id, int slot, zeek_uint_t inst)
void ZAMCompiler::CheckSlotAssignment(int slot, const ZInstI* inst) void ZAMCompiler::CheckSlotAssignment(int slot, const ZInstI* inst)
{ {
ASSERT(slot >= 0 && static_cast<zeek_uint_t>(slot) < frame_denizens.size()); ASSERT(slot >= 0 && static_cast<zeek_uint_t>(slot) < frame_denizens.size());
// We construct temporaries such that their values are never used // We construct temporaries such that their values are never used
// earlier than their definitions in loop bodies. For other // earlier than their definitions in loop bodies. For other
// denizens, however, they can be, so in those cases we expand the // denizens, however, they can be, so in those cases we expand the
@ -915,47 +927,6 @@ const ZInstI* ZAMCompiler::EndOfLoop(const ZInstI* inst, int depth) const
return insts1[i]; return insts1[i];
} }
bool ZAMCompiler::VarIsAssigned(int slot) const
{
for ( auto& inst : insts1 )
if ( inst->live && VarIsAssigned(slot, inst) )
return true;
return false;
}
bool ZAMCompiler::VarIsAssigned(int slot, const ZInstI* i) const
{
// Special-case for table iterators, which assign to a bunch
// of variables but they're not immediately visible in the
// instruction layout.
if ( i->op == OP_NEXT_TABLE_ITER_VAL_VAR_VVV || i->op == OP_NEXT_TABLE_ITER_VV )
{
auto& iter_vars = i->aux->loop_vars;
for ( auto v : iter_vars )
if ( v == slot )
return true;
if ( i->op != OP_NEXT_TABLE_ITER_VAL_VAR_VVV )
return false;
// Otherwise fall through, since that flavor of iterate
// *does* also assign to slot 1.
}
if ( i->op == OP_NEXT_VECTOR_ITER_VAL_VAR_VVVV && i->v2 == slot )
return true;
if ( i->op_type == OP_VV_FRAME )
// We don't want to consider these as assigning to the
// variable, since the point of this method is to figure
// out which variables don't need storing to the frame
// because their internal value is never modified.
return false;
return i->AssignsToSlot1() && i->v1 == slot;
}
bool ZAMCompiler::VarIsUsed(int slot) const bool ZAMCompiler::VarIsUsed(int slot) const
{ {
for ( auto& inst : insts1 ) for ( auto& inst : insts1 )

View file

@ -92,6 +92,7 @@ private:
void Init(); void Init();
void InitGlobals(); void InitGlobals();
void InitArgs(); void InitArgs();
void InitCaptures();
void InitLocals(); void InitLocals();
void TrackMemoryManagement(); void TrackMemoryManagement();
@ -137,6 +138,7 @@ private:
const ZAMStmt CompileCatchReturn(const CatchReturnStmt* cr); const ZAMStmt CompileCatchReturn(const CatchReturnStmt* cr);
const ZAMStmt CompileStmts(const StmtList* sl); const ZAMStmt CompileStmts(const StmtList* sl);
const ZAMStmt CompileInit(const InitStmt* is); const ZAMStmt CompileInit(const InitStmt* is);
const ZAMStmt CompileWhen(const WhenStmt* ws);
const ZAMStmt CompileNext() { return GenGoTo(nexts.back()); } const ZAMStmt CompileNext() { return GenGoTo(nexts.back()); }
const ZAMStmt CompileBreak() { return GenGoTo(breaks.back()); } const ZAMStmt CompileBreak() { return GenGoTo(breaks.back()); }
@ -219,10 +221,15 @@ private:
const ZAMStmt CompileInExpr(const NameExpr* n1, const ListExpr* l, const NameExpr* n2, const ZAMStmt CompileInExpr(const NameExpr* n1, const ListExpr* l, const NameExpr* n2,
const ConstExpr* c); const ConstExpr* c);
const ZAMStmt CompileIndex(const NameExpr* n1, const NameExpr* n2, const ListExpr* l); const ZAMStmt CompileIndex(const NameExpr* n1, const NameExpr* n2, const ListExpr* l,
const ZAMStmt CompileIndex(const NameExpr* n1, const ConstExpr* c, const ListExpr* l); bool in_when);
const ZAMStmt CompileIndex(const NameExpr* n1, const ConstExpr* c, const ListExpr* l,
bool in_when);
const ZAMStmt CompileIndex(const NameExpr* n1, int n2_slot, const TypePtr& n2_type, const ZAMStmt CompileIndex(const NameExpr* n1, int n2_slot, const TypePtr& n2_type,
const ListExpr* l); const ListExpr* l, bool in_when);
const ZAMStmt BuildLambda(const NameExpr* n, LambdaExpr* le);
const ZAMStmt BuildLambda(int n_slot, LambdaExpr* le);
// Second argument is which instruction slot holds the branch target. // Second argument is which instruction slot holds the branch target.
const ZAMStmt GenCond(const Expr* e, int& branch_v); const ZAMStmt GenCond(const Expr* e, int& branch_v);
@ -350,8 +357,15 @@ private:
bool IsUnused(const IDPtr& id, const Stmt* where) const; bool IsUnused(const IDPtr& id, const Stmt* where) const;
bool IsCapture(const IDPtr& id) const { return IsCapture(id.get()); }
bool IsCapture(const ID* id) const;
int CaptureOffset(const IDPtr& id) const { return IsCapture(id.get()); }
int CaptureOffset(const ID* id) const;
void LoadParam(const ID* id); void LoadParam(const ID* id);
const ZAMStmt LoadGlobal(const ID* id); const ZAMStmt LoadGlobal(const ID* id);
const ZAMStmt LoadCapture(const ID* id);
int AddToFrame(const ID*); int AddToFrame(const ID*);
@ -445,14 +459,6 @@ private:
const ZInstI* BeginningOfLoop(const ZInstI* inst, int depth) const; const ZInstI* BeginningOfLoop(const ZInstI* inst, int depth) const;
const ZInstI* EndOfLoop(const ZInstI* inst, int depth) const; const ZInstI* EndOfLoop(const ZInstI* inst, int depth) const;
// True if any statement other than a frame sync assigns to the
// given slot.
bool VarIsAssigned(int slot) const;
// True if the given statement assigns to the given slot, and
// it's not a frame sync.
bool VarIsAssigned(int slot, const ZInstI* i) const;
// True if any statement other than a frame sync uses the given slot. // True if any statement other than a frame sync uses the given slot.
bool VarIsUsed(int slot) const; bool VarIsUsed(int slot) const;
@ -599,8 +605,10 @@ private:
// Used for communication between Frame1Slot and a subsequent // Used for communication between Frame1Slot and a subsequent
// AddInst. If >= 0, then upon adding the next instruction, // AddInst. If >= 0, then upon adding the next instruction,
// it should be followed by Store-Global for the given slot. // it should be followed by Store-Global or Store-Capture for
// the given slot.
int pending_global_store = -1; int pending_global_store = -1;
int pending_capture_store = -1;
}; };
// Invokes after compiling all of the function bodies. // Invokes after compiling all of the function bodies.

View file

@ -33,20 +33,9 @@ void ZAMCompiler::Init()
{ {
InitGlobals(); InitGlobals();
InitArgs(); InitArgs();
InitCaptures();
InitLocals(); InitLocals();
#if 0
// Complain about unused aggregates ... but not if we're inlining,
// as that can lead to optimizations where they wind up being unused
// but the original logic for using them was sound.
if ( ! analysis_options.inliner )
for ( auto a : pf->Inits() )
{
if ( pf->Locals().find(a) == pf->Locals().end() )
reporter->Warning("%s unused", a->Name());
}
#endif
TrackMemoryManagement(); TrackMemoryManagement();
non_recursive = non_recursive_funcs.count(func) > 0; non_recursive = non_recursive_funcs.count(func) > 0;
@ -92,12 +81,25 @@ void ZAMCompiler::InitArgs()
pop_scope(); pop_scope();
} }
void ZAMCompiler::InitCaptures()
{
for ( auto c : pf->Captures() )
(void)AddToFrame(c);
}
void ZAMCompiler::InitLocals() void ZAMCompiler::InitLocals()
{ {
// Assign slots for locals (which includes temporaries). // Assign slots for locals (which includes temporaries).
for ( auto l : pf->Locals() ) for ( auto l : pf->Locals() )
{ {
if ( IsCapture(l) )
continue;
if ( pf->WhenLocals().count(l) > 0 )
continue;
auto non_const_l = const_cast<ID*>(l); auto non_const_l = const_cast<ID*>(l);
// Don't add locals that were already added because they're // Don't add locals that were already added because they're
// parameters. // parameters.
// //
@ -203,11 +205,6 @@ StmtPtr ZAMCompiler::CompileBody()
// Could erase insts1 here to recover memory, but it's handy // Could erase insts1 here to recover memory, but it's handy
// for debugging. // for debugging.
#if 0
if ( non_recursive )
func->UseStaticFrame();
#endif
auto zb = make_intrusive<ZBody>(func->Name(), this); auto zb = make_intrusive<ZBody>(func->Name(), this);
zb->SetInsts(insts2); zb->SetInsts(insts2);

View file

@ -4,6 +4,7 @@
#include "zeek/Desc.h" #include "zeek/Desc.h"
#include "zeek/Reporter.h" #include "zeek/Reporter.h"
#include "zeek/script_opt/ProfileFunc.h"
#include "zeek/script_opt/ZAM/Compile.h" #include "zeek/script_opt/ZAM/Compile.h"
namespace zeek::detail namespace zeek::detail
@ -176,12 +177,6 @@ const ZAMStmt ZAMCompiler::CompileAssignExpr(const AssignExpr* e)
auto r2 = rhs->GetOp2(); auto r2 = rhs->GetOp2();
auto r3 = rhs->GetOp3(); auto r3 = rhs->GetOp3();
if ( rhs->Tag() == EXPR_LAMBDA )
{
// reporter->Error("lambda expressions not supported for compiling");
return ErrorStmt();
}
if ( rhs->Tag() == EXPR_NAME ) if ( rhs->Tag() == EXPR_NAME )
return AssignVV(lhs, rhs->AsNameExpr()); return AssignVV(lhs, rhs->AsNameExpr());
@ -213,6 +208,9 @@ const ZAMStmt ZAMCompiler::CompileAssignExpr(const AssignExpr* e)
if ( rhs->Tag() == EXPR_ANY_INDEX ) if ( rhs->Tag() == EXPR_ANY_INDEX )
return AnyIndexVVi(lhs, r1->AsNameExpr(), rhs->AsAnyIndexExpr()->Index()); return AnyIndexVVi(lhs, r1->AsNameExpr(), rhs->AsAnyIndexExpr()->Index());
if ( rhs->Tag() == EXPR_LAMBDA )
return BuildLambda(lhs, rhs->AsLambdaExpr());
if ( rhs->Tag() == EXPR_COND && r1->GetType()->Tag() == TYPE_VECTOR ) if ( rhs->Tag() == EXPR_COND && r1->GetType()->Tag() == TYPE_VECTOR )
return Bool_Vec_CondVVVV(lhs, r1->AsNameExpr(), r2->AsNameExpr(), r3->AsNameExpr()); return Bool_Vec_CondVVVV(lhs, r1->AsNameExpr(), r2->AsNameExpr(), r3->AsNameExpr());
@ -285,7 +283,18 @@ const ZAMStmt ZAMCompiler::CompileAssignToIndex(const NameExpr* lhs, const Index
: IndexVecIntSelectVVV(lhs, n, index); : IndexVecIntSelectVVV(lhs, n, index);
} }
return const_aggr ? IndexVCL(lhs, con, indexes_expr) : IndexVVL(lhs, n, indexes_expr); if ( rhs->IsInsideWhen() )
{
if ( const_aggr )
return WhenIndexVCL(lhs, con, indexes_expr);
else
return WhenIndexVVL(lhs, n, indexes_expr);
}
if ( const_aggr )
return IndexVCL(lhs, con, indexes_expr);
else
return IndexVVL(lhs, n, indexes_expr);
} }
const ZAMStmt ZAMCompiler::CompileFieldLHSAssignExpr(const FieldLHSAssignExpr* e) const ZAMStmt ZAMCompiler::CompileFieldLHSAssignExpr(const FieldLHSAssignExpr* e)
@ -616,26 +625,28 @@ const ZAMStmt ZAMCompiler::CompileInExpr(const NameExpr* n1, const ListExpr* l,
return AddInst(z); return AddInst(z);
} }
const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, const NameExpr* n2, const ListExpr* l) const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, const NameExpr* n2, const ListExpr* l,
bool in_when)
{ {
return CompileIndex(n1, FrameSlot(n2), n2->GetType(), l); return CompileIndex(n1, FrameSlot(n2), n2->GetType(), l, in_when);
} }
const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n, const ConstExpr* c, const ListExpr* l) const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n, const ConstExpr* c, const ListExpr* l,
bool in_when)
{ {
auto tmp = TempForConst(c); auto tmp = TempForConst(c);
return CompileIndex(n, tmp, c->GetType(), l); return CompileIndex(n, tmp, c->GetType(), l, in_when);
} }
const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, int n2_slot, const TypePtr& n2t, const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, int n2_slot, const TypePtr& n2t,
const ListExpr* l) const ListExpr* l, bool in_when)
{ {
ZInstI z; ZInstI z;
int n = l->Exprs().length(); int n = l->Exprs().length();
auto n2tag = n2t->Tag(); auto n2tag = n2t->Tag();
if ( n == 1 ) if ( n == 1 && ! in_when )
{ {
auto ind = l->Exprs()[0]; auto ind = l->Exprs()[0];
auto var_ind = ind->Tag() == EXPR_NAME; auto var_ind = ind->Tag() == EXPR_NAME;
@ -677,12 +688,28 @@ const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, int n2_slot, const T
if ( n3 ) if ( n3 )
{ {
int n3_slot = FrameSlot(n3); int n3_slot = FrameSlot(n3);
auto zop = is_any ? OP_INDEX_ANY_VEC_VVV : OP_INDEX_VEC_VVV;
ZOp zop;
if ( in_when )
zop = OP_WHEN_INDEX_VEC_VVV;
else if ( is_any )
zop = OP_INDEX_ANY_VEC_VVV;
else
zop = OP_INDEX_VEC_VVV;
z = ZInstI(zop, Frame1Slot(n1, zop), n2_slot, n3_slot); z = ZInstI(zop, Frame1Slot(n1, zop), n2_slot, n3_slot);
} }
else else
{ {
auto zop = is_any ? OP_INDEX_ANY_VECC_VVV : OP_INDEX_VECC_VVV; ZOp zop;
if ( in_when )
zop = OP_WHEN_INDEX_VECC_VVV;
else if ( is_any )
zop = OP_INDEX_ANY_VECC_VVV;
else
zop = OP_INDEX_VECC_VVV;
z = ZInstI(zop, Frame1Slot(n1, zop), n2_slot, c); z = ZInstI(zop, Frame1Slot(n1, zop), n2_slot, c);
z.op_type = OP_VVV_I3; z.op_type = OP_VVV_I3;
} }
@ -720,13 +747,13 @@ const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, int n2_slot, const T
switch ( n2tag ) switch ( n2tag )
{ {
case TYPE_VECTOR: case TYPE_VECTOR:
op = OP_INDEX_VEC_SLICE_VV; op = in_when ? OP_WHEN_INDEX_VEC_SLICE_VV : OP_INDEX_VEC_SLICE_VV;
z = ZInstI(op, Frame1Slot(n1, op), n2_slot); z = ZInstI(op, Frame1Slot(n1, op), n2_slot);
z.SetType(n2t); z.SetType(n2t);
break; break;
case TYPE_TABLE: case TYPE_TABLE:
op = OP_TABLE_INDEX_VV; op = in_when ? OP_WHEN_TABLE_INDEX_VV : OP_TABLE_INDEX_VV;
z = ZInstI(op, Frame1Slot(n1, op), n2_slot); z = ZInstI(op, Frame1Slot(n1, op), n2_slot);
z.SetType(n1->GetType()); z.SetType(n1->GetType());
break; break;
@ -747,6 +774,38 @@ const ZAMStmt ZAMCompiler::CompileIndex(const NameExpr* n1, int n2_slot, const T
return AddInst(z); return AddInst(z);
} }
const ZAMStmt ZAMCompiler::BuildLambda(const NameExpr* n, LambdaExpr* le)
{
return BuildLambda(Frame1Slot(n, OP1_WRITE), le);
}
const ZAMStmt ZAMCompiler::BuildLambda(int n_slot, LambdaExpr* le)
{
auto& captures = le->GetCaptures();
int ncaptures = captures ? captures->size() : 0;
auto aux = new ZInstAux(ncaptures);
aux->primary_func = le->PrimaryFunc();
aux->lambda_name = le->Name();
aux->id_val = le->Ingredients()->GetID();
for ( int i = 0; i < ncaptures; ++i )
{
auto& id_i = (*captures)[i].Id();
if ( pf->WhenLocals().count(id_i.get()) > 0 )
aux->Add(i, nullptr);
else
aux->Add(i, FrameSlot(id_i), id_i->GetType());
}
auto z = ZInstI(OP_LAMBDA_VV, n_slot, le->PrimaryFunc()->FrameSize());
z.op_type = OP_VV_I2;
z.aux = aux;
return AddInst(z);
}
const ZAMStmt ZAMCompiler::AssignVecElems(const Expr* e) const ZAMStmt ZAMCompiler::AssignVecElems(const Expr* e)
{ {
auto index_assign = e->AsIndexAssignExpr(); auto index_assign = e->AsIndexAssignExpr();
@ -887,16 +946,17 @@ const ZAMStmt ZAMCompiler::AssignToCall(const ExprStmt* e)
const ZAMStmt ZAMCompiler::DoCall(const CallExpr* c, const NameExpr* n) const ZAMStmt ZAMCompiler::DoCall(const CallExpr* c, const NameExpr* n)
{ {
auto func = c->Func()->AsNameExpr(); auto func = c->Func()->AsNameExpr();
auto func_id = func->Id(); auto func_id = func->IdPtr();
auto& args = c->Args()->Exprs(); auto& args = c->Args()->Exprs();
int nargs = args.length(); int nargs = args.length();
int call_case = nargs; int call_case = nargs;
bool indirect = ! func_id->IsGlobal() || ! func_id->GetVal(); bool indirect = ! func_id->IsGlobal() || ! func_id->GetVal();
bool in_when = c->IsInWhen();
if ( indirect ) if ( indirect || in_when )
call_case = -1; // force default of CallN call_case = -1; // force default of some flavor of CallN
auto nt = n ? n->GetType()->Tag() : TYPE_VOID; auto nt = n ? n->GetType()->Tag() : TYPE_VOID;
auto n_slot = n ? Frame1Slot(n, OP1_WRITE) : -1; auto n_slot = n ? Frame1Slot(n, OP1_WRITE) : -1;
@ -973,8 +1033,17 @@ const ZAMStmt ZAMCompiler::DoCall(const CallExpr* c, const NameExpr* n)
break; break;
default: default:
if ( indirect ) if ( in_when )
{
if ( indirect )
op = OP_WHENINDCALLN_VV;
else
op = OP_WHENCALLN_V;
}
else if ( indirect )
op = n ? OP_INDCALLN_VV : OP_INDCALLN_V; op = n ? OP_INDCALLN_VV : OP_INDCALLN_V;
else else
op = n ? OP_CALLN_V : OP_CALLN_X; op = n ? OP_CALLN_V : OP_CALLN_X;
break; break;
@ -982,7 +1051,9 @@ const ZAMStmt ZAMCompiler::DoCall(const CallExpr* c, const NameExpr* n)
if ( n ) if ( n )
{ {
op = AssignmentFlavor(op, nt); if ( ! in_when )
op = AssignmentFlavor(op, nt);
auto n_slot = Frame1Slot(n, OP1_WRITE); auto n_slot = Frame1Slot(n, OP1_WRITE);
if ( indirect ) if ( indirect )
@ -1023,10 +1094,13 @@ const ZAMStmt ZAMCompiler::DoCall(const CallExpr* c, const NameExpr* n)
if ( ! z.aux ) if ( ! z.aux )
z.aux = new ZInstAux(0); z.aux = new ZInstAux(0);
z.aux->can_change_globals = true; z.aux->can_change_non_locals = true;
z.call_expr = c; z.call_expr = c;
if ( in_when )
z.SetType(n->GetType());
if ( ! indirect || func_id->IsGlobal() ) if ( ! indirect || func_id->IsGlobal() )
{ {
z.aux->id_val = func_id; z.aux->id_val = func_id;
@ -1035,19 +1109,6 @@ const ZAMStmt ZAMCompiler::DoCall(const CallExpr* c, const NameExpr* n)
z.func = func_id->GetVal()->AsFunc(); z.func = func_id->GetVal()->AsFunc();
} }
if ( n )
{
auto id = n->Id();
if ( id->IsGlobal() )
{
AddInst(z);
auto global_slot = global_id_to_info[id];
z = ZInstI(OP_STORE_GLOBAL_V, global_slot);
z.op_type = OP_V_I1;
z.t = globalsI[global_slot].id->GetType();
}
}
return AddInst(z); return AddInst(z);
} }
@ -1062,6 +1123,31 @@ const ZAMStmt ZAMCompiler::ConstructTable(const NameExpr* n, const Expr* e)
z.t = tt; z.t = tt;
z.attrs = e->AsTableConstructorExpr()->GetAttrs(); z.attrs = e->AsTableConstructorExpr()->GetAttrs();
auto zstmt = AddInst(z);
auto def_attr = z.attrs ? z.attrs->Find(ATTR_DEFAULT) : nullptr;
if ( ! def_attr || def_attr->GetExpr()->Tag() != EXPR_LAMBDA )
return zstmt;
auto def_lambda = def_attr->GetExpr()->AsLambdaExpr();
auto dl_t = def_lambda->GetType()->AsFuncType();
auto& captures = dl_t->GetCaptures();
if ( ! captures )
return zstmt;
// What a pain. The table's default value is a lambda that has
// captures. The semantics of this are that the captures are
// evaluated at table-construction time. We need to build the
// lambda and assign it as the table's default.
auto slot = NewSlot(true); // since func_val's are managed
(void)BuildLambda(slot, def_lambda);
z = GenInst(OP_SET_TABLE_DEFAULT_LAMBDA_VV, n, slot);
z.op_type = OP_VV;
z.t = def_lambda->GetType();
return AddInst(z); return AddInst(z);
} }

View file

@ -149,6 +149,9 @@ const ZAMStmt ZAMCompiler::AddInst(const ZInstI& inst, bool suppress_non_local)
if ( suppress_non_local ) if ( suppress_non_local )
return ZAMStmt(top_main_inst); return ZAMStmt(top_main_inst);
// Ensure we haven't confused ourselves about any pending stores.
ASSERT(pending_global_store == -1 || pending_capture_store == -1);
if ( pending_global_store >= 0 ) if ( pending_global_store >= 0 )
{ {
auto gs = pending_global_store; auto gs = pending_global_store;
@ -161,6 +164,27 @@ const ZAMStmt ZAMCompiler::AddInst(const ZInstI& inst, bool suppress_non_local)
return AddInst(store_inst); return AddInst(store_inst);
} }
if ( pending_capture_store >= 0 )
{
auto cs = pending_capture_store;
pending_capture_store = -1;
auto& cv = *func->GetType()->AsFuncType()->GetCaptures();
auto& c_id = cv[cs].Id();
ZOp op;
if ( ZVal::IsManagedType(c_id->GetType()) )
op = OP_STORE_MANAGED_CAPTURE_VV;
else
op = OP_STORE_CAPTURE_VV;
auto store_inst = ZInstI(op, RawSlot(c_id.get()), cs);
store_inst.op_type = OP_VV_I2;
return AddInst(store_inst);
}
return ZAMStmt(top_main_inst); return ZAMStmt(top_main_inst);
} }

View file

@ -12,7 +12,7 @@
# The Gen-ZAM utility processes this file to generate numerous C++ inclusion # The Gen-ZAM utility processes this file to generate numerous C++ inclusion
# files that are then compiled into Zeek. These files span the range of (1) # files that are then compiled into Zeek. These files span the range of (1)
# hooks that enable run-time generation of ZAM code to execute ASTs (which # hooks that enable run-time generation of ZAM code to execute ASTs (which
# have first been transformed to "reduced" form, (2) specifications of the # have first been transformed to "reduced" form), (2) specifications of the
# properties of the different instructions, (3) code to evaluate (execute) # properties of the different instructions, (3) code to evaluate (execute)
# each instruction, and (4) macros (C++ #define's) to aid in writing that # each instruction, and (4) macros (C++ #define's) to aid in writing that
# code. See Gen-ZAM.h for a list of the different inclusion files. # code. See Gen-ZAM.h for a list of the different inclusion files.
@ -947,11 +947,19 @@ eval EvalIndexVecIntSelect(z.c, frame[z.v2])
op Index op Index
type VVL type VVL
custom-method return CompileIndex(n1, n2, l); custom-method return CompileIndex(n1, n2, l, false);
op Index op Index
type VCL type VCL
custom-method return CompileIndex(n, c, l); custom-method return CompileIndex(n, c, l, false);
op WhenIndex
type VVL
custom-method return CompileIndex(n1, n2, l, true);
op WhenIndex
type VCL
custom-method return CompileIndex(n, c, l, true);
internal-op Index-Vec internal-op Index-Vec
type VVV type VVV
@ -988,22 +996,51 @@ internal-op Index-Any-VecC
type VVV type VVV
eval EvalIndexAnyVec(z.v3) eval EvalIndexAnyVec(z.v3)
internal-op Index-Vec-Slice macro WhenIndexResCheck()
type VV auto& res = frame[z.v1].vector_val;
eval auto vec = frame[z.v2].vector_val; if ( res && IndexExprWhen::evaluating > 0 )
IndexExprWhen::results.push_back({NewRef{}, res});
internal-op When-Index-Vec
type VVV
eval EvalIndexAnyVec(frame[z.v3].uint_val)
WhenIndexResCheck()
internal-op When-Index-VecC
type VVV
eval EvalIndexAnyVec(z.v3)
WhenIndexResCheck()
macro EvalVecSlice()
auto vec = frame[z.v2].vector_val;
auto lv = z.aux->ToListVal(frame); auto lv = z.aux->ToListVal(frame);
auto v = index_slice(vec, lv.get()); auto v = index_slice(vec, lv.get());
Unref(frame[z.v1].vector_val); Unref(frame[z.v1].vector_val);
frame[z.v1].vector_val = v.release(); frame[z.v1].vector_val = v.release();
internal-op Index-Vec-Slice
type VV
eval EvalVecSlice()
internal-op When-Index-Vec-Slice
type VV
eval EvalVecSlice()
WhenIndexResCheck()
internal-op Table-Index internal-op Table-Index
type VV type VV
eval EvalTableIndex(z.aux->ToListVal(frame)) eval EvalTableIndex(z.aux->ToListVal(frame))
AssignV1(BuildVal(v, z.t)) AssignV1(BuildVal(v, z.t))
internal-op When-Table-Index
type VV
eval EvalTableIndex(z.aux->ToListVal(frame))
if ( IndexExprWhen::evaluating > 0 )
IndexExprWhen::results.emplace_back(v);
AssignV1(BuildVal(v, z.t))
macro EvalTableIndex(index) macro EvalTableIndex(index)
auto v2 = index; auto v = frame[z.v2].table_val->FindOrDefault(index);
auto v = frame[z.v2].table_val->FindOrDefault(v2);
if ( ! v ) if ( ! v )
{ {
ZAM_run_time_error(z.loc, "no such index"); ZAM_run_time_error(z.loc, "no such index");
@ -1092,6 +1129,15 @@ eval ConstructTableOrSetPre()
} }
ConstructTableOrSetPost() ConstructTableOrSetPost()
# When tables are constructed, if their &default is a lambda with captures
# then we need to explicitly set up the default.
internal-op Set-Table-Default-Lambda
type VV
op1-read
eval auto& tbl = frame[z.v1].table_val;
auto lambda = frame[z.v2].ToVal(z.t);
tbl->InitDefaultVal(lambda);
direct-unary-op Set-Constructor ConstructSet direct-unary-op Set-Constructor ConstructSet
internal-op Construct-Set internal-op Construct-Set
@ -1527,6 +1573,49 @@ assign-val v
indirect-call indirect-call
num-call-args n num-call-args n
# A call made in a "when" context. These always have assignment targets.
# To keep things simple, we just use one generic flavor (for N arguments,
# doing a less-streamlined-but-simpler Val-based assignment).
macro WhenCall(func)
if ( ! func )
throw ZAMDelayedCallException();
auto& lhs = frame[z.v1];
auto trigger = f->GetTrigger();
Val* v = trigger ? trigger->Lookup(z.call_expr) : nullptr;
ValPtr vp;
if ( v )
vp = {NewRef{}, v};
else
{
auto aux = z.aux;
auto current_assoc = f->GetTriggerAssoc();
auto n = aux->n;
std::vector<ValPtr> args;
for ( auto i = 0; i < n; ++i )
args.push_back(aux->ToVal(frame, i));
f->SetCall(z.call_expr);
vp = func->Invoke(&args, f);
f->SetTriggerAssoc(current_assoc);
if ( ! vp )
throw ZAMDelayedCallException();
}
if ( z.is_managed )
ZVal::DeleteManagedType(lhs);
lhs = ZVal(vp, z.t);
internal-op WhenCallN
type V
side-effects
eval WhenCall(z.func)
internal-op WhenIndCallN
type VV
side-effects
eval auto sel = z.v2;
auto func = (sel < 0) ? z.aux->id_val->GetVal()->AsFunc() : frame[sel].AsFunc();
WhenCall(func)
########## Statements ########## ########## Statements ##########
macro EvalScheduleArgs(time, is_delta, build_args) macro EvalScheduleArgs(time, is_delta, build_args)
@ -1956,6 +2045,35 @@ eval auto tt = cast_intrusive<TableType>(z.t);
Unref(frame[z.v1].table_val); Unref(frame[z.v1].table_val);
frame[z.v1].table_val = t; frame[z.v1].table_val = t;
op When
type V
op1-read
eval BuildWhen(-1.0)
op When-Timeout
type VV
op1-read
eval BuildWhen(frame[z.v2].double_val)
op When-Timeout
type VC
op1-read
eval BuildWhen(z.c.double_val)
macro BuildWhen(timeout)
auto& aux = z.aux;
auto wi = aux->wi;
FuncPtr func{NewRef{}, frame[z.v1].func_val};
auto lambda = make_intrusive<FuncVal>(func);
wi->Instantiate(lambda);
std::vector<ValPtr> local_aggrs;
for ( int i = 0; i < aux->n; ++i )
{
auto v = aux->ToVal(frame, i);
if ( v )
local_aggrs.push_back(v);
}
new trigger::Trigger(wi, timeout, wi->WhenExprGlobals(), local_aggrs, f, z.loc);
######################################## ########################################
# Internal # Internal
@ -1994,7 +2112,7 @@ assign-val v
eval auto v = globals[z.v2].id->GetVal(); eval auto v = globals[z.v2].id->GetVal();
if ( ! v ) if ( ! v )
{ {
ZAM_run_time_error(z.loc, "value used but not set", z.aux->id_val); ZAM_run_time_error(z.loc, "value used but not set", z.aux->id_val.get());
break; break;
} }
@ -2007,12 +2125,41 @@ eval auto& v = frame[z.v1].type_val;
auto t = globals[z.v2].id->GetType(); auto t = globals[z.v2].id->GetType();
v = new TypeVal(t, true); v = new TypeVal(t, true);
internal-op Load-Capture
type VV
eval frame[z.v1] = f->GetFunction()->GetCapturesVec()[z.v2];
internal-op Load-Managed-Capture
type VV
eval auto& lhs = frame[z.v1];
auto& rhs = f->GetFunction()->GetCapturesVec()[z.v2];
zeek::Ref(rhs.ManagedVal());
ZVal::DeleteManagedType(lhs);
lhs = rhs;
internal-op Store-Global internal-op Store-Global
op1-internal op1-internal
type V type V
eval auto& g = globals[z.v1]; eval auto& g = globals[z.v1];
g.id->SetVal(frame[g.slot].ToVal(z.t)); g.id->SetVal(frame[g.slot].ToVal(z.t));
# Both of these have the LHS as v2 not v1, to keep with existing
# conventions of OP_VV_I2 op type (as opposed to OP_VV_I1_V2, which doesn't
# currently exist, and would be a pain to add).
internal-op Store-Capture
op1-read
type VV
eval f->GetFunction()->GetCapturesVec()[z.v2] = frame[z.v1];
internal-op Store-Managed-Capture
op1-read
type VV
eval auto& lhs = f->GetFunction()->GetCapturesVec()[z.v2];
auto& rhs = frame[z.v1];
zeek::Ref(rhs.ManagedVal());
ZVal::DeleteManagedType(lhs);
lhs = rhs;
internal-op Copy-To internal-op Copy-To
type VC type VC
@ -2029,6 +2176,37 @@ eval flow = FLOW_BREAK;
pc = end_pc; pc = end_pc;
continue; continue;
# Slot 2 gives frame size.
internal-op Lambda
type VV
eval auto& aux = z.aux;
auto& primary_func = aux->primary_func;
auto& body = primary_func->GetBodies()[0].stmts;
ASSERT(body->Tag() == STMT_ZAM);
auto lamb = make_intrusive<ScriptFunc>(aux->id_val);
lamb->AddBody(body, z.v2);
lamb->SetName(aux->lambda_name.c_str());
if ( aux->n > 0 )
{
auto captures = std::make_unique<std::vector<ZVal>>();
for ( auto i = 0; i < aux->n; ++i )
{
auto slot = aux->slots[i];
if ( slot >= 0 )
{
auto& cp = frame[aux->slots[i]];
if ( aux->is_managed[i] )
zeek::Ref(cp.ManagedVal());
captures->push_back(cp);
}
else
// Used for when-locals.
captures->push_back(ZVal());
}
lamb->CreateCaptures(std::move(captures));
}
ZVal::DeleteManagedType(frame[z.v1]);
frame[z.v1].func_val = lamb.release();
######################################## ########################################
# Built-in Functions # Built-in Functions

View file

@ -41,35 +41,29 @@ the _Logging Framework_, for example, and thus you won't see much improvement.
* Those two factors add up to gains very often on the order of only 10-15%, * Those two factors add up to gains very often on the order of only 10-15%,
rather than something a lot more dramatic. rather than something a lot more dramatic.
* In addition, there are some
[types of scripts that currently can't be compiled](#Scripts-that-cannot-be-compiled),
and thus will remain interpreted. If your processing bottlenecks in such
scripts, you won't see much in the way of gains.
<br> <br>
## Known Issues ## Known Issues
Here we list various issues with using script optimization, including both Here we list various issues with using script optimization, including both
deficiencies (problems to eventually fix) and incompatibilities (differences deficiencies (things that don't work as well as you might like)
in behavior from the default of script interpretation, not necessarily and incompatibilities (differences in behavior from the default
fixable). For each, the corresponding list is roughly ordered from of script interpretation).
you're-most-likely-to-care-about-it to you're-less-likely-to-care, though
of course this varies for different users.
<br> <br>
### Deficiencies to eventually fix: ### Deficiencies:
* Error messages in compiled scripts have diminished identifying * Run-time error messages in compiled scripts have diminished identifying
information. information.
* The optimizer assumes you have ensured initialization of your variables. * The optimizer assumes you have ensured initialization of your variables.
If your script uses a variable that hasn't been set, the compiled code may If your script uses a variable that hasn't been set, the compiled code may
crash or behave aberrantly. You can use the `-u` command-line flag to find such potential usage issues. crash or behave aberrantly. You can use the `-u` command-line flag to find such potential usage issues.
* Certain complex "when" expressions may fail to reevaluate when elements * When printing scripts (such as in some error messages), the names of
of the expression are modified by compiled scripts. variables often reflect internal temporaries rather than the original
variables.
<br> <br>
@ -77,30 +71,11 @@ of the expression are modified by compiled scripts.
* ZAM ignores `assert` statements. * ZAM ignores `assert` statements.
* When printing scripts (such as in some error messages), the names of
variables often reflect internal temporaries rather than the original
variables.
* The `same_object()` BiF will always deem two non-container values as * The `same_object()` BiF will always deem two non-container values as
different. different.
<br> <br>
### Scripts that cannot be compiled:
The ZAM optimizer does not compile scripts that include "when" statements or
lambda expressions. These will take substantial work to support. It also
will not inline such scripts, nor will it inline scripts that are either
directly or indirectly recursive.
You can get a list of non-compilable scripts using
`-O ZAM -O report-uncompilable`. For recursive scripts, use
`-O report-recursive` (no `-O ZAM` required, since it doesn't apply to the
alternative optimization, `-O gen-C++`).
<br>
## Script Optimization Options ## Script Optimization Options
Users will generally simply use `-O ZAM` to invoke the script optimizer. Users will generally simply use `-O ZAM` to invoke the script optimizer.
@ -120,7 +95,7 @@ issues:
|`optimize-AST` | Optimize the (transform) AST; implies `xform`.| |`optimize-AST` | Optimize the (transform) AST; implies `xform`.|
|`profile-ZAM` | Generate to _stdout_ a ZAM execution profile. (Requires configuring with `--enable-debug`.)| |`profile-ZAM` | Generate to _stdout_ a ZAM execution profile. (Requires configuring with `--enable-debug`.)|
|`report-recursive` | Report on recursive functions and exit.| |`report-recursive` | Report on recursive functions and exit.|
|`report-uncompilable` | Report on uncompilable functions and exit.| |`report-uncompilable` | Report on uncompilable functions and exit. For ZAM, all functions should be compilable.|
|`xform` | Transform scripts to "reduced" form.| |`xform` | Transform scripts to "reduced" form.|
<br> <br>

View file

@ -60,6 +60,9 @@ const ZAMStmt ZAMCompiler::CompileStmt(const Stmt* s)
case STMT_INIT: case STMT_INIT:
return CompileInit(static_cast<const InitStmt*>(s)); return CompileInit(static_cast<const InitStmt*>(s));
case STMT_WHEN:
return CompileWhen(static_cast<const WhenStmt*>(s));
case STMT_NULL: case STMT_NULL:
return EmptyStmt(); return EmptyStmt();
@ -1093,6 +1096,60 @@ const ZAMStmt ZAMCompiler::CompileInit(const InitStmt* is)
return last; return last;
} }
const ZAMStmt ZAMCompiler::CompileWhen(const WhenStmt* ws)
{
auto wi = ws->Info();
auto timeout = wi->TimeoutExpr();
auto lambda = NewSlot(true);
(void)BuildLambda(lambda, wi->Lambda().get());
std::vector<IDPtr> local_aggr_slots;
for ( auto& l : wi->WhenExprLocals() )
if ( IsAggr(l->GetType()->Tag()) )
local_aggr_slots.push_back(l);
int n = local_aggr_slots.size();
auto aux = new ZInstAux(n);
aux->wi = wi;
for ( auto i = 0; i < n; ++i )
{
auto la = local_aggr_slots[i];
aux->Add(i, FrameSlot(la), la->GetType());
}
ZInstI z;
if ( timeout )
{
if ( timeout->Tag() == EXPR_NAME )
{
auto ns = FrameSlot(timeout->AsNameExpr());
z = ZInstI(OP_WHEN_TIMEOUT_VV, lambda, ns);
}
else
{
ASSERT(timeout->Tag() == EXPR_CONST);
z = ZInstI(OP_WHEN_TIMEOUT_VC, lambda, timeout->AsConstExpr());
}
}
else
z = ZInstI(OP_WHEN_V, lambda);
z.aux = aux;
if ( ws->IsReturn() )
{
(void)AddInst(z);
z = ZInstI(OP_RETURN_C);
z.c = ZVal();
}
return AddInst(z);
}
const ZAMStmt ZAMCompiler::InitRecord(IDPtr id, RecordType* rt) const ZAMStmt ZAMCompiler::InitRecord(IDPtr id, RecordType* rt)
{ {
auto z = ZInstI(OP_INIT_RECORD_V, FrameSlot(id)); auto z = ZInstI(OP_INIT_RECORD_V, FrameSlot(id));

View file

@ -20,20 +20,6 @@ bool ZAM_error = false;
bool is_ZAM_compilable(const ProfileFunc* pf, const char** reason) bool is_ZAM_compilable(const ProfileFunc* pf, const char** reason)
{ {
if ( pf->NumLambdas() > 0 )
{
if ( reason )
*reason = "use of lambda";
return false;
}
if ( pf->NumWhenStmts() > 0 )
{
if ( reason )
*reason = "use of \"when\"";
return false;
}
auto b = pf->ProfiledBody(); auto b = pf->ProfiledBody();
auto is_hook = pf->ProfiledFunc()->Flavor() == FUNC_FLAVOR_HOOK; auto is_hook = pf->ProfiledFunc()->Flavor() == FUNC_FLAVOR_HOOK;
if ( b && ! script_is_valid(b, is_hook) ) if ( b && ! script_is_valid(b, is_hook) )

View file

@ -24,6 +24,17 @@ bool ZAMCompiler::IsUnused(const IDPtr& id, const Stmt* where) const
return ! usage || ! usage->HasID(id.get()); return ! usage || ! usage->HasID(id.get());
} }
bool ZAMCompiler::IsCapture(const ID* id) const
{
const auto& c = pf->CapturesOffsets();
return c.find(id) != c.end();
}
int ZAMCompiler::CaptureOffset(const ID* id) const
{
return pf->CapturesOffsets().find(id)->second;
}
void ZAMCompiler::LoadParam(const ID* id) void ZAMCompiler::LoadParam(const ID* id)
{ {
if ( id->IsType() ) if ( id->IsType() )
@ -64,7 +75,24 @@ const ZAMStmt ZAMCompiler::LoadGlobal(const ID* id)
// We use the id_val for reporting used-but-not-set errors. // We use the id_val for reporting used-but-not-set errors.
z.aux = new ZInstAux(0); z.aux = new ZInstAux(0);
z.aux->id_val = id; z.aux->id_val = {NewRef{}, const_cast<ID*>(id)};
return AddInst(z, true);
}
const ZAMStmt ZAMCompiler::LoadCapture(const ID* id)
{
ZOp op;
if ( ZVal::IsManagedType(id->GetType()) )
op = OP_LOAD_MANAGED_CAPTURE_VV;
else
op = OP_LOAD_CAPTURE_VV;
auto slot = RawSlot(id);
ZInstI z(op, slot, CaptureOffset(id));
z.op_type = OP_VV_I2;
return AddInst(z, true); return AddInst(z, true);
} }
@ -83,6 +111,9 @@ int ZAMCompiler::FrameSlot(const ID* id)
if ( id->IsGlobal() ) if ( id->IsGlobal() )
(void)LoadGlobal(id); (void)LoadGlobal(id);
else if ( IsCapture(id) )
(void)LoadCapture(id);
return slot; return slot;
} }
@ -103,6 +134,13 @@ int ZAMCompiler::Frame1Slot(const ID* id, ZAMOp1Flavor fl)
if ( id->IsGlobal() ) if ( id->IsGlobal() )
pending_global_store = global_id_to_info[id]; pending_global_store = global_id_to_info[id];
else if ( IsCapture(id) )
pending_capture_store = CaptureOffset(id);
// Make sure we don't think we're storing to both a global and
// a capture.
ASSERT(pending_global_store == -1 || pending_capture_store == -1);
return slot; return slot;
} }

View file

@ -31,6 +31,11 @@ namespace zeek::detail
using std::vector; using std::vector;
// Thrown when a call inside a "when" delays.
class ZAMDelayedCallException : public InterpreterException
{
};
static bool did_init = false; static bool did_init = false;
// Count of how often each type of ZOP executed, and how much CPU it // Count of how often each type of ZOP executed, and how much CPU it
@ -188,10 +193,10 @@ ZBody::~ZBody()
void ZBody::SetInsts(vector<ZInst*>& _insts) void ZBody::SetInsts(vector<ZInst*>& _insts)
{ {
ninst = _insts.size(); end_pc = _insts.size();
auto insts_copy = new ZInst[ninst]; auto insts_copy = new ZInst[end_pc];
for ( auto i = 0U; i < ninst; ++i ) for ( auto i = 0U; i < end_pc; ++i )
insts_copy[i] = *_insts[i]; insts_copy[i] = *_insts[i];
insts = insts_copy; insts = insts_copy;
@ -201,10 +206,10 @@ void ZBody::SetInsts(vector<ZInst*>& _insts)
void ZBody::SetInsts(vector<ZInstI*>& instsI) void ZBody::SetInsts(vector<ZInstI*>& instsI)
{ {
ninst = instsI.size(); end_pc = instsI.size();
auto insts_copy = new ZInst[ninst]; auto insts_copy = new ZInst[end_pc];
for ( auto i = 0U; i < ninst; ++i ) for ( auto i = 0U; i < end_pc; ++i )
{ {
auto& iI = *instsI[i]; auto& iI = *instsI[i];
insts_copy[i] = iI; insts_copy[i] = iI;
@ -223,7 +228,7 @@ void ZBody::InitProfile()
{ {
inst_count = new vector<int>; inst_count = new vector<int>;
inst_CPU = new vector<double>; inst_CPU = new vector<double>;
for ( auto i = 0U; i < ninst; ++i ) for ( auto i = 0U; i < end_pc; ++i )
{ {
inst_count->push_back(0); inst_count->push_back(0);
inst_CPU->push_back(0.0); inst_CPU->push_back(0.0);
@ -240,7 +245,7 @@ ValPtr ZBody::Exec(Frame* f, StmtFlowType& flow)
double t = analysis_options.profile_ZAM ? util::curr_CPU_time() : 0.0; double t = analysis_options.profile_ZAM ? util::curr_CPU_time() : 0.0;
#endif #endif
auto val = DoExec(f, 0, flow); auto val = DoExec(f, flow);
#ifdef DEBUG #ifdef DEBUG
if ( analysis_options.profile_ZAM ) if ( analysis_options.profile_ZAM )
@ -250,10 +255,9 @@ ValPtr ZBody::Exec(Frame* f, StmtFlowType& flow)
return val; return val;
} }
ValPtr ZBody::DoExec(Frame* f, int start_pc, StmtFlowType& flow) ValPtr ZBody::DoExec(Frame* f, StmtFlowType& flow)
{ {
int pc = start_pc; int pc = 0;
const int end_pc = ninst;
// Return value, or nil if none. // Return value, or nil if none.
const ZVal* ret_u = nullptr; const ZVal* ret_u = nullptr;
@ -288,6 +292,9 @@ ValPtr ZBody::DoExec(Frame* f, int start_pc, StmtFlowType& flow)
flow = FLOW_RETURN; // can be over-written by a Hook-Break flow = FLOW_RETURN; // can be over-written by a Hook-Break
// Clear any leftover error state.
ZAM_error = false;
while ( pc < end_pc && ! ZAM_error ) while ( pc < end_pc && ! ZAM_error )
{ {
auto& z = insts[pc]; auto& z = insts[pc];
@ -364,9 +371,6 @@ ValPtr ZBody::DoExec(Frame* f, int start_pc, StmtFlowType& flow)
delete[] frame; delete[] frame;
} }
// Clear any error state.
ZAM_error = false;
return result; return result;
} }
@ -444,7 +448,7 @@ void ZBody::Dump() const
printf("Final code:\n"); printf("Final code:\n");
for ( unsigned i = 0; i < ninst; ++i ) for ( unsigned i = 0; i < end_pc; ++i )
{ {
auto& inst = insts[i]; auto& inst = insts[i];
printf("%d: ", i); printf("%d: ", i);
@ -467,25 +471,6 @@ TraversalCode ZBody::Traverse(TraversalCallback* cb) const
HANDLE_TC_STMT_POST(tc); HANDLE_TC_STMT_POST(tc);
} }
ValPtr ZAMResumption::Exec(Frame* f, StmtFlowType& flow)
{
return am->DoExec(f, xfer_pc, flow);
}
void ZAMResumption::StmtDescribe(ODesc* d) const
{
d->Add("<resumption of compiled code>");
}
TraversalCode ZAMResumption::Traverse(TraversalCallback* cb) const
{
TraversalCode tc = cb->PreStmt(this);
HANDLE_TC_STMT_PRE(tc);
tc = cb->PostStmt(this);
HANDLE_TC_STMT_POST(tc);
}
// Unary vector operation of v1 <vec-op> v2. // Unary vector operation of v1 <vec-op> v2.
static void vec_exec(ZOp op, TypePtr t, VectorVal*& v1, const VectorVal* v2, const ZInst& z) static void vec_exec(ZOp op, TypePtr t, VectorVal*& v1, const VectorVal* v2, const ZInst& z)
{ {

View file

@ -52,12 +52,10 @@ public:
void ProfileExecution() const; void ProfileExecution() const;
protected: protected:
friend class ZAMResumption;
// Initializes profiling information, if needed. // Initializes profiling information, if needed.
void InitProfile(); void InitProfile();
ValPtr DoExec(Frame* f, int start_pc, StmtFlowType& flow); ValPtr DoExec(Frame* f, StmtFlowType& flow);
// Run-time checking for "any" type being consistent with // Run-time checking for "any" type being consistent with
// expected typed. Returns true if the type match is okay. // expected typed. Returns true if the type match is okay.
@ -73,7 +71,7 @@ private:
const char* func_name = nullptr; const char* func_name = nullptr;
const ZInst* insts = nullptr; const ZInst* insts = nullptr;
unsigned int ninst = 0; unsigned int end_pc = 0;
FrameReMap frame_denizens; FrameReMap frame_denizens;
int frame_size; int frame_size;
@ -117,29 +115,6 @@ private:
CaseMaps<std::string> str_cases; CaseMaps<std::string> str_cases;
}; };
// This is a statement that resumes execution into a code block in a
// ZBody. Used for deferred execution for "when" statements.
class ZAMResumption : public Stmt
{
public:
ZAMResumption(ZBody* _am, int _xfer_pc) : Stmt(STMT_ZAM_RESUMPTION), am(_am), xfer_pc(_xfer_pc)
{
}
ValPtr Exec(Frame* f, StmtFlowType& flow) override;
StmtPtr Duplicate() override { return {NewRef{}, this}; }
void StmtDescribe(ODesc* d) const override;
protected:
TraversalCode Traverse(TraversalCallback* cb) const override;
ZBody* am;
int xfer_pc = 0;
};
// Prints the execution profile. // Prints the execution profile.
extern void report_ZOP_profile(); extern void report_ZOP_profile();

View file

@ -383,16 +383,20 @@ bool ZInstI::IsDirectAssignment() const
switch ( op ) switch ( op )
{ {
case OP_ASSIGN_VV_N:
case OP_ASSIGN_VV_A: case OP_ASSIGN_VV_A:
case OP_ASSIGN_VV_D:
case OP_ASSIGN_VV_F:
case OP_ASSIGN_VV_I:
case OP_ASSIGN_VV_L:
case OP_ASSIGN_VV_N:
case OP_ASSIGN_VV_O: case OP_ASSIGN_VV_O:
case OP_ASSIGN_VV_P: case OP_ASSIGN_VV_P:
case OP_ASSIGN_VV_R: case OP_ASSIGN_VV_R:
case OP_ASSIGN_VV_S: case OP_ASSIGN_VV_S:
case OP_ASSIGN_VV_F:
case OP_ASSIGN_VV_T: case OP_ASSIGN_VV_T:
case OP_ASSIGN_VV_U:
case OP_ASSIGN_VV_V: case OP_ASSIGN_VV_V:
case OP_ASSIGN_VV_L: case OP_ASSIGN_VV_a:
case OP_ASSIGN_VV_f: case OP_ASSIGN_VV_f:
case OP_ASSIGN_VV_t: case OP_ASSIGN_VV_t:
case OP_ASSIGN_VV: case OP_ASSIGN_VV:
@ -403,6 +407,21 @@ bool ZInstI::IsDirectAssignment() const
} }
} }
bool ZInstI::HasCaptures() const
{
switch ( op )
{
case OP_LAMBDA_VV:
case OP_WHEN_V:
case OP_WHEN_TIMEOUT_VV:
case OP_WHEN_TIMEOUT_VC:
return true;
default:
return false;
}
}
bool ZInstI::HasSideEffects() const bool ZInstI::HasSideEffects() const
{ {
return op_side_effects[op]; return op_side_effects[op];
@ -647,6 +666,11 @@ bool ZInstI::IsGlobalLoad() const
return global_ops.count(op) > 0; return global_ops.count(op) > 0;
} }
bool ZInstI::IsCaptureLoad() const
{
return op == OP_LOAD_CAPTURE_VV || op == OP_LOAD_MANAGED_CAPTURE_VV;
}
void ZInstI::InitConst(const ConstExpr* ce) void ZInstI::InitConst(const ConstExpr* ce)
{ {
auto v = ce->ValuePtr(); auto v = ce->ValuePtr();

View file

@ -5,6 +5,7 @@
#pragma once #pragma once
#include "zeek/Desc.h" #include "zeek/Desc.h"
#include "zeek/Func.h"
#include "zeek/script_opt/ZAM/BuiltInSupport.h" #include "zeek/script_opt/ZAM/BuiltInSupport.h"
#include "zeek/script_opt/ZAM/Support.h" #include "zeek/script_opt/ZAM/Support.h"
#include "zeek/script_opt/ZAM/ZOp.h" #include "zeek/script_opt/ZAM/ZOp.h"
@ -220,6 +221,9 @@ public:
// True if this instruction is of the form "v1 = v2". // True if this instruction is of the form "v1 = v2".
bool IsDirectAssignment() const; bool IsDirectAssignment() const;
// True if this instruction includes captures in its aux slots.
bool HasCaptures() const;
// True if this instruction has side effects when executed, so // True if this instruction has side effects when executed, so
// should not be pruned even if it has a dead assignment. // should not be pruned even if it has a dead assignment.
bool HasSideEffects() const; bool HasSideEffects() const;
@ -247,9 +251,17 @@ public:
// the ZAM frame. // the ZAM frame.
bool IsGlobalLoad() const; bool IsGlobalLoad() const;
// True if the instruction corresponds to loading a capture into
// the ZAM frame.
bool IsCaptureLoad() const;
// True if the instruction does not correspond to a load from the
// ZAM frame.
bool IsNonLocalLoad() const { return IsGlobalLoad() || IsCaptureLoad(); }
// True if the instruction corresponds to some sort of load, // True if the instruction corresponds to some sort of load,
// either from the interpreter frame or of a global. // either from the interpreter frame or of a global/capture.
bool IsLoad() const { return op_type == OP_VV_FRAME || IsGlobalLoad(); } bool IsLoad() const { return op_type == OP_VV_FRAME || IsNonLocalLoad(); }
// True if the instruction corresponds to storing a global. // True if the instruction corresponds to storing a global.
bool IsGlobalStore() const { return op == OP_STORE_GLOBAL_V; } bool IsGlobalStore() const { return op == OP_STORE_GLOBAL_V; }
@ -317,6 +329,7 @@ public:
slots = ints = new int[n]; slots = ints = new int[n];
constants = new ValPtr[n]; constants = new ValPtr[n];
types = new TypePtr[n]; types = new TypePtr[n];
is_managed = new bool[n];
} }
} }
@ -325,6 +338,7 @@ public:
delete[] ints; delete[] ints;
delete[] constants; delete[] constants;
delete[] types; delete[] types;
delete[] is_managed;
delete[] cat_args; delete[] cat_args;
} }
@ -384,6 +398,7 @@ public:
ints[i] = slot; ints[i] = slot;
constants[i] = nullptr; constants[i] = nullptr;
types[i] = t; types[i] = t;
is_managed[i] = t ? ZVal::IsManagedType(t) : false;
} }
// Same but for constants. // Same but for constants.
@ -392,6 +407,7 @@ public:
ints[i] = -1; ints[i] = -1;
constants[i] = c; constants[i] = c;
types[i] = nullptr; types[i] = nullptr;
is_managed[i] = false;
} }
// Member variables. We could add accessors for manipulating // Member variables. We could add accessors for manipulating
@ -404,24 +420,37 @@ public:
// if not, it's nil). // if not, it's nil).
// //
// We track associated types, too, enabling us to use // We track associated types, too, enabling us to use
// ZVal::ToVal to convert frame slots or constants to ValPtr's. // ZVal::ToVal to convert frame slots or constants to ValPtr's;
// and, as a performance optimization, whether those types
// indicate the slot needs to be managed.
int n; // size of arrays int n; // size of arrays
int* slots = nullptr; // either nil or points to ints int* slots = nullptr; // either nil or points to ints
int* ints = nullptr; int* ints = nullptr;
ValPtr* constants = nullptr; ValPtr* constants = nullptr;
TypePtr* types = nullptr; TypePtr* types = nullptr;
bool* is_managed = nullptr;
// Ingredients associated with lambdas ...
ScriptFuncPtr primary_func;
// ... and its name.
std::string lambda_name;
// For "when" statements. Needs to be non-const so we can
// Instantiate() it as needed.
WhenInfo* wi;
// A parallel array for the cat() built-in replacement. // A parallel array for the cat() built-in replacement.
std::unique_ptr<CatArg>* cat_args = nullptr; std::unique_ptr<CatArg>* cat_args = nullptr;
// Used for accessing function names. // Used for accessing function names.
const ID* id_val = nullptr; IDPtr id_val = nullptr;
// Whether the instruction can lead to globals changing. // Whether the instruction can lead to globals/captures changing.
// Currently only needed by the optimizer, but convenient // Currently only needed by the optimizer, but convenient to
// to store here. // store here.
bool can_change_globals = false; bool can_change_non_locals = false;
// The following is only used for OP_CONSTRUCT_KNOWN_RECORD_V, // The following is only used for OP_CONSTRUCT_KNOWN_RECORD_V,
// to map elements in slots/constants/types to record field offsets. // to map elements in slots/constants/types to record field offsets.

View file

@ -1,3 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 8: required field Foo$hello is missing in JSON (from_json({"t":null}, <internal>::#0)) error in <...>/from_json.zeek, line 8: required field Foo$hello is missing in JSON (from_json({"t":null}, <internal>::#0, from_json_default_key_mapper))
error in <...>/from_json.zeek, line 9: required field Foo$hello is null in JSON (from_json({"hello": null, "t": true}, <internal>::#2)) error in <...>/from_json.zeek, line 9: required field Foo$hello is null in JSON (from_json({"hello": null, "t": true}, <internal>::#2, from_json_default_key_mapper))

View file

@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 7: required field Foo$id_field is missing in JSON (from_json({"id-field": "Hello!"}, <internal>::#0, from_json_default_key_mapper))

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
[v=<uninitialized>, valid=F]
[v=[id_field=Hello!], valid=T]

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
T
[ietf_mud_mud=[mud_version=1, mud_url=https://lighting.example.com/lightbulb2000, last_update=2019-01-28T11:20:51+01:00, cache_validity=48, is_supported=T, systeminfo=The BMS Example Light Bulb, from_device_policy=[access_lists=[access_list=[[name=mud-76100-v6fr]]]], to_device_policy=[access_lists=[access_list=[[name=mud-76100-v6to]]]]], ietf_access_control_list_acls=[acl=[[name=mud-76100-v6to, _type=ipv6-acl-type, aces=[ace=[[name=cl0-todev, matches=[ipv6=[ietf_acldns_dst_dnsname=<uninitialized>, ietf_mud_direction_initiated=<uninitialized>, destination_port=<uninitialized>], tcp=[ietf_acldns_dst_dnsname=<uninitialized>, ietf_mud_direction_initiated=from-device, destination_port=<uninitialized>]]]]]], [name=mud-76100-v6fr, _type=ipv6-acl-type, aces=[ace=[[name=cl0-frdev, matches=[ipv6=[ietf_acldns_dst_dnsname=test.example.com, ietf_mud_direction_initiated=<uninitialized>, destination_port=<uninitialized>], tcp=[ietf_acldns_dst_dnsname=<uninitialized>, ietf_mud_direction_initiated=from-device, destination_port=[operator=eq, _port=443]]]]]]]]]]

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 4: from_json() requires a type argument (from_json([], 10)) error in <...>/from_json.zeek, line 4: from_json() requires a type argument (from_json([], 10, from_json_default_key_mapper))

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 4: JSON parse error: Missing a closing quotation mark in string. Offset: 5 (from_json({"hel, <internal>::#0)) error in <...>/from_json.zeek, line 4: JSON parse error: Missing a closing quotation mark in string. Offset: 5 (from_json({"hel, <internal>::#0, from_json_default_key_mapper))

View file

@ -1,3 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 9: cannot convert JSON type 'array' to Zeek type 'bool' (from_json([], <internal>::#0)) error in <...>/from_json.zeek, line 9: cannot convert JSON type 'array' to Zeek type 'bool' (from_json([], <internal>::#0, from_json_default_key_mapper))
error in <...>/from_json.zeek, line 10: cannot convert JSON type 'string' to Zeek type 'bool' (from_json({"a": "hello"}, <internal>::#2)) error in <...>/from_json.zeek, line 10: cannot convert JSON type 'string' to Zeek type 'bool' (from_json({"a": "hello"}, <internal>::#2, from_json_default_key_mapper))

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 4: tables are not supported (from_json([], <internal>::#0)) error in <...>/from_json.zeek, line 4: tables are not supported (from_json([], <internal>::#0, from_json_default_key_mapper))

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 5: wrong port format, must be <...>/(tcp|udp|icmp|unknown)/ (from_json("80", <internal>::#0)) error in <...>/from_json.zeek, line 5: wrong port format, must be <...>/(tcp|udp|icmp|unknown)/ (from_json("80", <internal>::#0, from_json_default_key_mapper))

View file

@ -1,3 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 5: index type doesn't match (from_json([[1, false], [2]], <internal>::#0)) error in <...>/from_json.zeek, line 5: index type doesn't match (from_json([[1, false], [2]], <internal>::#0, from_json_default_key_mapper))
error in <...>/from_json.zeek, line 6: cannot convert JSON type 'number' to Zeek type 'bool' (from_json([[1, false], [2, 1]], <internal>::#2)) error in <...>/from_json.zeek, line 6: cannot convert JSON type 'number' to Zeek type 'bool' (from_json([[1, false], [2, 1]], <internal>::#2, from_json_default_key_mapper))

View file

@ -1,3 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error: error compiling pattern /^?(.|\n)*(([[:print:]]{-}[[:alnum:]]foo))/ error: error compiling pattern /^?(.|\n)*(([[:print:]]{-}[[:alnum:]]foo))/
error in <...>/from_json.zeek, line 5: error compiling pattern (from_json("/([[:print:]]{-}[[:alnum:]]foo)/", <internal>::#0)) error in <...>/from_json.zeek, line 5: error compiling pattern (from_json("/([[:print:]]{-}[[:alnum:]]foo)/", <internal>::#0, from_json_default_key_mapper))

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/from_json.zeek, line 7: 'Yellow' is not a valid enum for 'Color'. (from_json("Yellow", <internal>::#0)) error in <...>/from_json.zeek, line 7: 'Yellow' is not a valid enum for 'Color'. (from_json("Yellow", <internal>::#0, from_json_default_key_mapper))

View file

@ -2,8 +2,8 @@
error in <...>/store.zeek, line 780: Failed to attach master store backend_failure: (Broker::__create_master(../fail, Broker::SQLITE, Broker::options.2)) error in <...>/store.zeek, line 780: Failed to attach master store backend_failure: (Broker::__create_master(../fail, Broker::SQLITE, Broker::options.2))
error in <...>/store.zeek, line 780: Could not create Broker master store '../fail' (Broker::__create_master(../fail, Broker::SQLITE, Broker::options.2)) error in <...>/store.zeek, line 780: Could not create Broker master store '../fail' (Broker::__create_master(../fail, Broker::SQLITE, Broker::options.2))
error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{}) error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{})
error in <...>/store.zeek, line 794: invalid Broker store handle (Broker::__close(Broker::h) and broker::store::{}) error in <...>/store.zeek, line 794: invalid Broker store handle (Broker::__close(Broker::h.12) and broker::store::{})
error in <...>/store.zeek, line 794: invalid Broker store handle (Broker::__close(Broker::h) and broker::store::{}) error in <...>/store.zeek, line 794: invalid Broker store handle (Broker::__close(Broker::h.21) and broker::store::{})
error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{}) error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{})
error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{}) error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{})
error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{}) error in <...>/create-failure.zeek, line 49: invalid Broker store handle (Broker::keys(s) and broker::store::{})

View file

@ -0,0 +1,15 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
### NOTE: This file has been sorted with diff-sort.
[f(F)]
[f(T)]
[zeek_init()]
f() done, no exception, T
g() done, no exception, T
received termination signal
runtime error in <...>/when-interpreter-exceptions.zeek, line 103: field value missing: $notset
runtime error in <...>/when-interpreter-exceptions.zeek, line 47: field value missing: $notset
runtime error in <...>/when-interpreter-exceptions.zeek, line 72: field value missing: $notset
runtime error in <...>/when-interpreter-exceptions.zeek, line 91: field value missing: $notset
timeout
timeout g(), F
timeout g(), T

View file

@ -0,0 +1,8 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in <...>/when-capture-errors.zeek, lines 19-22: orig1 is used inside "when" statement but not captured (when (0 < g) { print orig1})
error in <...>/when-capture-errors.zeek, lines 25-28: orig3 is used inside "when" statement but not captured (when (0 < g || orig3) { print g})
error in <...>/when-capture-errors.zeek, lines 34-38: orig1 is used inside "when" statement but not captured (when (0 < g) { print g} timeout 1.0 sec { print orig1})
error in <...>/when-capture-errors.zeek, lines 66-70: orig2 is used inside "when" statement but not captured (when [orig1](0 < g) { print orig1} timeout 1.0 sec { print orig2})
error in <...>/when-capture-errors.zeek, lines 76-80: orig3 is captured but not used inside "when" statement (when [orig1, orig2, orig3](0 < g) { print orig1} timeout 1.0 sec { print orig2})
error in <...>/when-capture-errors.zeek, line 83: no such local identifier: l1
error in <...>/when-capture-errors.zeek, line 89: no such local identifier: l2

View file

@ -1,2 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
expression error in <...>/uninitialized-local.zeek, line 16: value used but not set (my_string) expression error in <...>/uninitialized-local.zeek, line 19: value used but not set (my_string)

View file

@ -2,7 +2,7 @@
error in <...>/when-capture-errors.zeek, lines 19-22: orig1 is used inside "when" statement but not captured (when (0 < g) { print orig1}) error in <...>/when-capture-errors.zeek, lines 19-22: orig1 is used inside "when" statement but not captured (when (0 < g) { print orig1})
error in <...>/when-capture-errors.zeek, lines 25-28: orig3 is used inside "when" statement but not captured (when (0 < g || orig3) { print g}) error in <...>/when-capture-errors.zeek, lines 25-28: orig3 is used inside "when" statement but not captured (when (0 < g || orig3) { print g})
error in <...>/when-capture-errors.zeek, lines 34-38: orig1 is used inside "when" statement but not captured (when (0 < g) { print g} timeout 1.0 sec { print orig1}) error in <...>/when-capture-errors.zeek, lines 34-38: orig1 is used inside "when" statement but not captured (when (0 < g) { print g} timeout 1.0 sec { print orig1})
error in <...>/when-capture-errors.zeek, lines 66-70: orig2 is used inside "when" statement but not captured (when (0 < g) { print orig1} timeout 1.0 sec { print orig2}) error in <...>/when-capture-errors.zeek, lines 66-70: orig2 is used inside "when" statement but not captured (when [orig1](0 < g) { print orig1} timeout 1.0 sec { print orig2})
error in <...>/when-capture-errors.zeek, lines 76-80: orig3 is captured but not used inside "when" statement (when (0 < g) { print orig1} timeout 1.0 sec { print orig2}) error in <...>/when-capture-errors.zeek, lines 76-80: orig3 is captured but not used inside "when" statement (when [orig1, orig2, orig3](0 < g) { print orig1} timeout 1.0 sec { print orig2})
error in <...>/when-capture-errors.zeek, line 83: no such local identifier: l1 error in <...>/when-capture-errors.zeek, line 83: no such local identifier: l1
error in <...>/when-capture-errors.zeek, line 89: no such local identifier: l2 error in <...>/when-capture-errors.zeek, line 89: no such local identifier: l2

View file

@ -1,6 +1,6 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
expression error in <...>/when-unitialized-rhs.zeek, line 12: value used but not set (crashMe) expression error in <...>/when-unitialized-rhs.zeek, line 13: value used but not set (crashMe)
expression error in <...>/when-unitialized-rhs.zeek, line 17: value used but not set (x) expression error in <...>/when-unitialized-rhs.zeek, line 18: value used but not set (x)
1 1
2 2
3 3

View file

@ -0,0 +1,23 @@
# A version of uninitialized-local.zeek suitable for ZAM's behavior.
# @TEST-REQUIRES: test "${ZEEK_ZAM}" == "1"
# @TEST-EXEC-FAIL: zeek -b %INPUT >out 2>&1
event testit() &priority=10
{
local my_count: count = 10;
}
event testit()
{
# my_string's value occupies same Frame offset as my_count's from above
# handler, but execution of this handler body should still "initialize"
# it to a null value instead of referring to a left-over value of my_count.
local my_string: string;
local my_vector: vector of string;
my_vector[0] = my_string;
}
event zeek_init()
{
event testit();
}

View file

@ -1,3 +1,6 @@
# Not suitable for ZAM since it spots the error at compile time and exits
# with an error status code.
# @TEST-REQUIRES: test "${ZEEK_ZAM}" != "1"
# @TEST-EXEC: zeek -b %INPUT >out 2>&1 # @TEST-EXEC: zeek -b %INPUT >out 2>&1
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out # @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out

View file

@ -1,5 +1,6 @@
# C++-compiled scripts don't check for missing functions - not clear it's # Compiled scripts don't fully check for missing functions - not clear it's
# worth fixing that. # worth fixing that.
# @TEST-REQUIRES: test "${ZEEK_ZAM}" != "1"
# @TEST-REQUIRES: test "${ZEEK_USE_CPP}" != "1" # @TEST-REQUIRES: test "${ZEEK_USE_CPP}" != "1"
# @TEST-EXEC: zeek -b -r $TRACES/wikipedia.trace %INPUT >out 2>&1 # @TEST-EXEC: zeek -b -r $TRACES/wikipedia.trace %INPUT >out 2>&1
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out # @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out

View file

@ -1 +1 @@
d59caff708b41db11fa0cbfe0b1f95b46c3e700e 828845c99306c6d5d6811fa42987de5b16f530b9

View file

@ -1 +1 @@
7162c907aa25e155ea841710ef30b65afb578c3f b121bfe4d869f1f5e334505b970cd456558ef6a1