mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
1062 lines
34 KiB
C++
1062 lines
34 KiB
C++
// See the file "COPYING" in the main distribution directory for copyright.
|
|
|
|
// Logic associated with optimization of the low-level Abstract Machine,
|
|
// i.e., code improvement that's done after the compiler has generated
|
|
// an initial, complete intermediary function body.
|
|
|
|
#include "zeek/Desc.h"
|
|
#include "zeek/Reporter.h"
|
|
#include "zeek/input.h"
|
|
#include "zeek/script_opt/Reduce.h"
|
|
#include "zeek/script_opt/ScriptOpt.h"
|
|
#include "zeek/script_opt/ZAM/Compile.h"
|
|
|
|
namespace zeek::detail {
|
|
|
|
// Tracks per function its maximum remapped interpreter frame size. We
|
|
// can't do this when compiling individual functions since for event handlers
|
|
// and hooks it needs to be computed across all of their bodies.
|
|
//
|
|
// Note, this is now not actually needed, because we no longer use any
|
|
// interpreter frame entries other than those for the function's arguments.
|
|
// We keep the code in case that changes, for example when deciding to
|
|
// compile functions that include "return when" conditions.
|
|
std::unordered_map<const Func*, int> remapped_intrp_frame_sizes;
|
|
|
|
void finalize_functions(const std::vector<FuncInfo>& funcs) {
|
|
// Given we've now compiled all of the function bodies, we can reset
|
|
// the interpreter frame sizes to what's actually used. This can be
|
|
// a huge win for massively inlined event handlers, which otherwise
|
|
// can have frames sized for 100s of variables, none of which (other
|
|
// than the arguments) need TLC such as via calls to Frame::Reset().
|
|
|
|
// Find any functions with bodies that weren't compiled and
|
|
// make sure we don't reduce their frame size.
|
|
std::unordered_set<const Func*> leave_alone;
|
|
|
|
for ( auto& f : funcs )
|
|
if ( f.Body() && f.Body()->Tag() != STMT_ZAM )
|
|
// This function has a body that wasn't compiled,
|
|
// don't mess with its size.
|
|
leave_alone.insert(f.Func());
|
|
|
|
for ( auto& f : funcs ) {
|
|
auto func = f.Func();
|
|
|
|
if ( leave_alone.count(func) > 0 )
|
|
continue;
|
|
|
|
if ( remapped_intrp_frame_sizes.count(func) == 0 )
|
|
// No entry for this function, keep current frame size.
|
|
continue;
|
|
|
|
auto& ft = func->GetType();
|
|
auto& params = ft->Params();
|
|
func->SetFrameSize(params->NumFields());
|
|
|
|
// Don't bother processing any future instances.
|
|
leave_alone.insert(func);
|
|
}
|
|
}
|
|
|
|
// The following is for activating detailed dumping for debugging
|
|
// optimizer problems.
|
|
static bool dump_intermediaries = false;
|
|
|
|
void ZAMCompiler::OptimizeInsts() {
|
|
// Do accounting for targeted statements.
|
|
for ( auto& i : insts1 ) {
|
|
if ( i->target && i->target->live )
|
|
++(i->target->num_labels);
|
|
}
|
|
|
|
TallySwitchTargets(int_casesI);
|
|
TallySwitchTargets(uint_casesI);
|
|
TallySwitchTargets(double_casesI);
|
|
TallySwitchTargets(str_casesI);
|
|
|
|
for ( unsigned int i = 0; i < insts1.size(); ++i )
|
|
if ( insts1[i]->op == OP_NOP )
|
|
// We can always get rid of these.
|
|
KillInst(i);
|
|
|
|
if ( analysis_options.dump_ZAM ) {
|
|
printf("Original ZAM code for %s:\n", func->GetName().c_str());
|
|
DumpInsts1(nullptr);
|
|
}
|
|
|
|
bool something_changed;
|
|
|
|
do {
|
|
something_changed = false;
|
|
|
|
while ( RemoveDeadCode() ) {
|
|
something_changed = true;
|
|
|
|
if ( dump_intermediaries ) {
|
|
printf("Removed some dead code:\n");
|
|
DumpInsts1(nullptr);
|
|
}
|
|
}
|
|
|
|
while ( CollapseGoTos() ) {
|
|
something_changed = true;
|
|
|
|
if ( dump_intermediaries ) {
|
|
printf("Did some collapsing:\n");
|
|
DumpInsts1(nullptr);
|
|
}
|
|
}
|
|
|
|
ComputeFrameLifetimes();
|
|
|
|
if ( PruneUnused() ) {
|
|
something_changed = true;
|
|
|
|
if ( dump_intermediaries ) {
|
|
printf("Did some pruning:\n");
|
|
DumpInsts1(nullptr);
|
|
}
|
|
}
|
|
} while ( something_changed );
|
|
|
|
ReMapFrame();
|
|
ReMapInterpreterFrame();
|
|
}
|
|
|
|
template<typename T>
|
|
void ZAMCompiler::TallySwitchTargets(const CaseMapsI<T>& switches) {
|
|
for ( auto& targs : switches )
|
|
for ( auto& targ : targs )
|
|
++(targ.second->num_labels);
|
|
}
|
|
|
|
bool ZAMCompiler::RemoveDeadCode() {
|
|
if ( insts1.empty() )
|
|
return false;
|
|
|
|
bool did_removal = false;
|
|
|
|
// Note, loops up to the last instruction but not including it.
|
|
for ( unsigned int i = 0; i < insts1.size() - 1; ++i ) {
|
|
auto& i0 = insts1[i];
|
|
|
|
if ( ! i0->live )
|
|
continue;
|
|
|
|
if ( analysis_options.no_ZAM_control_flow_opt )
|
|
continue;
|
|
|
|
auto i1 = NextLiveInst(i0);
|
|
|
|
// Look for degenerate branches.
|
|
auto t = i0->target;
|
|
|
|
if ( t == pending_inst && ! i1 ) {
|
|
// This is a branch-to-end, and that's where we'll
|
|
// wind up anyway.
|
|
KillInst(i0);
|
|
did_removal = true;
|
|
continue;
|
|
}
|
|
|
|
if ( t && t->inst_num > i0->inst_num && (! i1 || t->inst_num <= i1->inst_num) ) {
|
|
// This is effectively a branch to the next instruction.
|
|
// We can remove it *unless* the instruction has side effects.
|
|
// Conditionals don't, but loop-iteration-advancement
|
|
// instructions do.
|
|
if ( ! i0->IsLoopIterationAdvancement() ) {
|
|
KillInst(i0);
|
|
did_removal = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( i0->DoesNotContinue() && i1 && i1->num_labels == 0 ) {
|
|
// i1 can't be reached - nor anything unlabeled
|
|
// after it.
|
|
KillInsts(i1);
|
|
did_removal = true;
|
|
}
|
|
}
|
|
|
|
return did_removal;
|
|
}
|
|
|
|
bool ZAMCompiler::CollapseGoTos() {
|
|
if ( analysis_options.no_ZAM_control_flow_opt )
|
|
return false;
|
|
|
|
bool did_change = false;
|
|
|
|
for ( auto& i0 : insts1 ) {
|
|
auto orig_t = i0->target;
|
|
|
|
if ( ! i0->live || ! orig_t || orig_t == pending_inst )
|
|
continue;
|
|
|
|
// Resolve branch chains. We both do a version that
|
|
// follows branches (to jump to the end of any chains),
|
|
// and one that does (so we can do num_labels bookkeeping
|
|
// for our initial target).
|
|
auto first_branch = FirstLiveInst(orig_t, false);
|
|
if ( ! first_branch )
|
|
// We're jump-to-end, so there's no possibility of
|
|
// a chain.
|
|
continue;
|
|
|
|
auto t = FirstLiveInst(orig_t, true);
|
|
|
|
if ( ! t )
|
|
t = pending_inst;
|
|
|
|
if ( t != orig_t ) {
|
|
// Update branch.
|
|
if ( first_branch->live )
|
|
--first_branch->num_labels;
|
|
i0->target = t;
|
|
++t->num_labels;
|
|
did_change = true;
|
|
}
|
|
}
|
|
|
|
return did_change;
|
|
}
|
|
|
|
bool ZAMCompiler::PruneUnused() {
|
|
bool did_prune = false;
|
|
|
|
for ( unsigned int i = 0; i < insts1.size(); ++i ) {
|
|
auto inst = insts1[i];
|
|
|
|
if ( ! inst->live ) {
|
|
ASSERT(inst->num_labels == 0);
|
|
continue;
|
|
}
|
|
|
|
if ( i == insts1.size() - 1 && inst->op == OP_RETURN_X ) {
|
|
// A non-value return at the very end of the body
|
|
// doesn't actually do anything.
|
|
did_prune = true;
|
|
KillInst(i);
|
|
continue;
|
|
}
|
|
|
|
if ( inst->IsLoad() && ! VarIsUsed(inst->v1) ) {
|
|
did_prune = true;
|
|
KillInst(i);
|
|
continue;
|
|
}
|
|
|
|
if ( inst->IsNonLocalLoad() ) {
|
|
// Any straight-line load of the same global/capture
|
|
// is redundant.
|
|
for ( unsigned int j = i + 1; j < insts1.size(); ++j ) {
|
|
auto i1 = insts1[j];
|
|
|
|
if ( ! i1->live )
|
|
continue;
|
|
|
|
if ( i1->DoesNotContinue() )
|
|
// End of straight-line block.
|
|
break;
|
|
|
|
if ( i1->num_labels > 0 )
|
|
// Inbound branch ends block.
|
|
break;
|
|
|
|
if ( i1->aux && i1->aux->can_change_non_locals )
|
|
break;
|
|
|
|
if ( ! i1->IsNonLocalLoad() )
|
|
continue;
|
|
|
|
if ( i1->v2 == inst->v2 && i1->IsGlobalLoad() == inst->IsGlobalLoad() ) { // Same global/capture
|
|
did_prune = true;
|
|
KillInst(i1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! inst->AssignsToSlot1() )
|
|
continue;
|
|
|
|
int slot = inst->v1;
|
|
if ( denizen_ending.count(slot) > 0 )
|
|
// Variable is used, keep assignment.
|
|
continue;
|
|
|
|
auto& id = frame_denizens[slot];
|
|
if ( id->IsGlobal() || IsCapture(id) ) {
|
|
// Extend the global/capture's range to the end of the
|
|
// function.
|
|
denizen_ending[slot] = insts1.back();
|
|
continue;
|
|
}
|
|
|
|
// Assignment to a local that isn't otherwise used.
|
|
if ( ! inst->HasSideEffects() ) {
|
|
did_prune = true;
|
|
// We don't use this assignment.
|
|
KillInst(i);
|
|
continue;
|
|
}
|
|
|
|
// If we get here then there's a dead assignment but we
|
|
// can't remove the instruction entirely because it has
|
|
// side effects. Transform the instruction into its flavor
|
|
// that doesn't make an assignment.
|
|
if ( assignmentless_op.count(inst->op) == 0 )
|
|
reporter->InternalError("inconsistency in re-flavoring instruction with side effects");
|
|
|
|
inst->op_type = assignmentless_op_class[inst->op];
|
|
inst->op = assignmentless_op[inst->op];
|
|
|
|
inst->v1 = inst->v2;
|
|
inst->v2 = inst->v3;
|
|
inst->v3 = inst->v4;
|
|
|
|
// While we didn't prune the instruction, we did prune the
|
|
// assignment, so we'll want to reassess variable lifetimes.
|
|
did_prune = true;
|
|
}
|
|
|
|
return did_prune;
|
|
}
|
|
|
|
void ZAMCompiler::ComputeFrameLifetimes() {
|
|
// Start analysis from scratch, since we might do this repeatedly.
|
|
inst_beginnings.clear();
|
|
inst_endings.clear();
|
|
|
|
denizen_beginning.clear();
|
|
denizen_ending.clear();
|
|
|
|
for ( unsigned int i = 0; i < insts1.size(); ++i ) {
|
|
auto inst = insts1[i];
|
|
if ( ! inst->live )
|
|
continue;
|
|
|
|
if ( inst->AssignsToSlot1() )
|
|
CheckSlotAssignment(inst->v1, inst);
|
|
|
|
// Some special-casing.
|
|
switch ( inst->op ) {
|
|
case OP_NEXT_TABLE_ITER_fb:
|
|
case OP_NEXT_TABLE_ITER_VAL_VAR_Vfb: {
|
|
// These assign to an arbitrary long list of variables.
|
|
auto& iter_vars = inst->aux->loop_vars;
|
|
auto depth = inst->loop_depth;
|
|
|
|
for ( auto v : iter_vars ) {
|
|
if ( v < 0 )
|
|
// This happens for '_' dummy
|
|
continue;
|
|
|
|
CheckSlotAssignment(v, inst);
|
|
|
|
// Also mark it as usage throughout the
|
|
// loop. Otherwise, we risk pruning the
|
|
// variable if it happens to not be used
|
|
// (which will mess up the iteration logic)
|
|
// or doubling it up with some other value
|
|
// inside the loop (which will fail when
|
|
// the loop var has memory management
|
|
// associated with it).
|
|
ExtendLifetime(v, EndOfLoop(inst, depth));
|
|
}
|
|
|
|
// No need to check the additional "var" associated
|
|
// with OP_NEXT_TABLE_ITER_VAL_VAR_Vfb as that's
|
|
// a slot-1 assignment. However, similar to other
|
|
// loop variables, mark this as a usage.
|
|
if ( inst->op == OP_NEXT_TABLE_ITER_VAL_VAR_Vfb )
|
|
ExtendLifetime(inst->v1, EndOfLoop(inst, depth));
|
|
} break;
|
|
|
|
case OP_NEXT_TABLE_ITER_NO_VARS_fb: break;
|
|
|
|
case OP_NEXT_TABLE_ITER_VAL_VAR_NO_VARS_Vfb: {
|
|
auto depth = inst->loop_depth;
|
|
ExtendLifetime(inst->v1, EndOfLoop(inst, depth));
|
|
} break;
|
|
|
|
case OP_NEXT_VECTOR_ITER_VAL_VAR_VVsb: {
|
|
CheckSlotAssignment(inst->v2, inst);
|
|
|
|
auto depth = inst->loop_depth;
|
|
ExtendLifetime(inst->v1, EndOfLoop(inst, depth));
|
|
ExtendLifetime(inst->v2, EndOfLoop(inst, depth));
|
|
} break;
|
|
|
|
case OP_NEXT_VECTOR_BLANK_ITER_VAL_VAR_Vsb: {
|
|
auto depth = inst->loop_depth;
|
|
ExtendLifetime(inst->v1, EndOfLoop(inst, depth));
|
|
} break;
|
|
|
|
case OP_NEXT_VECTOR_ITER_Vsb:
|
|
case OP_NEXT_STRING_ITER_Vsb:
|
|
// Sometimes loops are written that don't actually
|
|
// use the iteration variable. However, we still
|
|
// need to mark the variable as having usage
|
|
// throughout the loop, lest we elide the iteration
|
|
// instruction. An alternative would be to transform
|
|
// such iterators into variable-less versions. That
|
|
// optimization hardly seems worth the trouble, though,
|
|
// given the presumed rarity of such loops.
|
|
ExtendLifetime(inst->v1, EndOfLoop(inst, inst->loop_depth));
|
|
break;
|
|
|
|
case OP_NEXT_VECTOR_BLANK_ITER_sb:
|
|
case OP_NEXT_STRING_BLANK_ITER_sb: break;
|
|
|
|
case OP_INIT_TABLE_LOOP_Vf:
|
|
case OP_INIT_VECTOR_LOOP_Vs:
|
|
case OP_INIT_STRING_LOOP_Vs: {
|
|
// For all of these, the scope of the aggregate being
|
|
// looped over is the entire loop, even if it doesn't
|
|
// directly appear in it, and not just the initializer.
|
|
// For all three, the aggregate is in v1.
|
|
ASSERT(i < insts1.size() - 1);
|
|
auto succ = insts1[i + 1];
|
|
ASSERT(succ->live);
|
|
auto depth = succ->loop_depth;
|
|
ExtendLifetime(inst->v1, EndOfLoop(succ, depth));
|
|
|
|
// Important: we skip the usual UsesSlots analysis
|
|
// below since we've already set it, and don't want
|
|
// to perturb ExtendLifetime's consistency check.
|
|
continue;
|
|
}
|
|
|
|
case OP_STORE_GLOBAL_g: {
|
|
// Use of the global goes to here.
|
|
auto slot = frame_layout1[globalsI[inst->v1].id.get()];
|
|
ExtendLifetime(slot, EndOfLoop(inst, 1));
|
|
break;
|
|
}
|
|
|
|
case OP_DETERMINE_TYPE_MATCH_VV: {
|
|
auto aux = inst->aux;
|
|
int n = aux->n;
|
|
for ( int i = 0; i < n; ++i ) {
|
|
auto slot_i = aux->elems[i].Slot();
|
|
if ( slot_i >= 0 ) {
|
|
CheckSlotAssignment(slot_i, inst);
|
|
// The variable gets used in the switch that
|
|
// immediately follows this instruction, hence
|
|
// "i + 1" in the following.
|
|
ExtendLifetime(slot_i, insts1[i + 1]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_LAMBDA_Vi: {
|
|
auto aux = inst->aux;
|
|
int n = aux->n;
|
|
for ( int i = 0; i < n; ++i ) {
|
|
auto slot_i = aux->elems[i].Slot();
|
|
if ( slot_i >= 0 )
|
|
ExtendLifetime(slot_i, EndOfLoop(inst, 1));
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// Look for slots in auxiliary information.
|
|
auto aux = inst->aux;
|
|
if ( ! aux || ! aux->elems_has_slots )
|
|
break;
|
|
|
|
int n = aux->n;
|
|
for ( auto j = 0; j < n; ++j ) {
|
|
auto slot_j = aux->elems[j].Slot();
|
|
if ( slot_j < 0 )
|
|
continue;
|
|
|
|
ExtendLifetime(slot_j, EndOfLoop(inst, 1));
|
|
}
|
|
break;
|
|
}
|
|
|
|
int s1, s2, s3, s4;
|
|
|
|
if ( ! inst->UsesSlots(s1, s2, s3, s4) )
|
|
continue;
|
|
|
|
CheckSlotUse(s1, inst);
|
|
CheckSlotUse(s2, inst);
|
|
CheckSlotUse(s3, inst);
|
|
CheckSlotUse(s4, inst);
|
|
}
|
|
}
|
|
|
|
void ZAMCompiler::ReMapFrame() {
|
|
// General approach: go sequentially through the instructions,
|
|
// see which variables begin their lifetime at each, and at
|
|
// that point remap the variables to a suitable frame slot.
|
|
|
|
frame1_to_frame2.resize(frame_layout1.size(), -1);
|
|
managed_slotsI.clear();
|
|
|
|
for ( zeek_uint_t i = 0; i < insts1.size(); ++i ) {
|
|
auto inst = insts1[i];
|
|
|
|
if ( inst_beginnings.count(inst) == 0 )
|
|
continue;
|
|
|
|
auto vars = inst_beginnings[inst];
|
|
for ( auto v : vars ) {
|
|
// Don't remap variables whose values aren't actually used.
|
|
int slot = frame_layout1[v];
|
|
if ( denizen_ending.count(slot) > 0 )
|
|
ReMapVar(v, slot, i);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// Low-level debugging code.
|
|
printf("%s frame remapping:\n", func->Name());
|
|
|
|
for ( unsigned int i = 0; i < shared_frame_denizens.size(); ++i )
|
|
{
|
|
auto& s = shared_frame_denizens[i];
|
|
printf("*%d (%s) %lu [%d->%d]:",
|
|
i, s.is_managed ? "M" : "N",
|
|
s.ids.size(), s.id_start[0], s.scope_end);
|
|
|
|
for ( auto j = 0; j < s.ids.size(); ++j )
|
|
printf(" %s (%d)", s.ids[j]->Name(), s.id_start[j]);
|
|
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
// Update the globals we track, where we prune globals that
|
|
// didn't wind up being used.
|
|
std::vector<GlobalInfo> used_globals;
|
|
std::vector<int> remapped_globals;
|
|
|
|
for ( auto& g : globalsI ) {
|
|
g.slot = frame1_to_frame2[g.slot];
|
|
if ( g.slot >= 0 ) {
|
|
remapped_globals.push_back(used_globals.size());
|
|
used_globals.push_back(g);
|
|
}
|
|
else
|
|
remapped_globals.push_back(-1);
|
|
}
|
|
|
|
globalsI = used_globals;
|
|
|
|
// Gulp - now rewrite every instruction to update its slot usage.
|
|
// In the process, if an instruction becomes a direct assignment
|
|
// of <slot-n> = <slot-n>, then we remove it.
|
|
|
|
int n1_slots = frame1_to_frame2.size();
|
|
|
|
for ( unsigned int i = 0; i < insts1.size(); ++i ) {
|
|
auto inst = insts1[i];
|
|
|
|
if ( ! inst->live )
|
|
continue;
|
|
|
|
if ( inst->AssignsToSlot1() ) {
|
|
auto v1 = inst->v1;
|
|
ASSERT(v1 >= 0 && v1 < n1_slots);
|
|
inst->v1 = frame1_to_frame2[v1];
|
|
}
|
|
|
|
// Handle special cases.
|
|
switch ( inst->op ) {
|
|
case OP_INIT_TABLE_LOOP_Vf:
|
|
case OP_NEXT_TABLE_ITER_fb:
|
|
case OP_NEXT_TABLE_ITER_VAL_VAR_Vfb: {
|
|
// Rewrite iteration variables. Strictly speaking we only
|
|
// need to do this for the INIT, not the NEXT, since the
|
|
// latter currently doesn't access the variables directly but
|
|
// instead uses pointers set up by the INIT. We do both types
|
|
// here, though, to keep things consistent and to help avoid
|
|
// surprises if the implementation changes in the future.
|
|
auto& iter_vars = inst->aux->loop_vars;
|
|
for ( auto& v : iter_vars ) {
|
|
if ( v < 0 )
|
|
continue;
|
|
ASSERT(v < n1_slots);
|
|
v = frame1_to_frame2[v];
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
// Update slots in auxiliary information.
|
|
auto aux = inst->aux;
|
|
if ( ! aux || ! aux->elems_has_slots )
|
|
break;
|
|
|
|
for ( auto j = 0; j < aux->n; ++j ) {
|
|
auto slot = aux->elems[j].Slot();
|
|
|
|
if ( slot < 0 )
|
|
// This is instead a constant.
|
|
continue;
|
|
|
|
auto new_slot = frame1_to_frame2[slot];
|
|
|
|
if ( new_slot < 0 ) {
|
|
ODesc d;
|
|
inst->loc->Loc()->Describe(&d);
|
|
reporter->Error("%s: value used but not set: %s", d.Description(),
|
|
frame_denizens[slot]->Name());
|
|
}
|
|
|
|
aux->elems[j].SetSlot(new_slot);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( inst->IsGlobalLoad() ) {
|
|
// Slot v2 of these is the index into globals[]
|
|
// rather than a frame.
|
|
int g = inst->v2;
|
|
ASSERT(remapped_globals[g] >= 0);
|
|
inst->v2 = remapped_globals[g];
|
|
|
|
// We *don't* want to UpdateSlots below as that's
|
|
// based on interpreting v2 as slots rather than an
|
|
// index into globals.
|
|
continue;
|
|
}
|
|
|
|
if ( inst->IsGlobalStore() ) { // Slot v1 of these is the index into globals[].
|
|
int g = inst->v1;
|
|
ASSERT(remapped_globals[g] >= 0);
|
|
inst->v1 = remapped_globals[g];
|
|
|
|
// We don't have any other slots to update.
|
|
continue;
|
|
}
|
|
|
|
inst->UpdateSlots(frame1_to_frame2);
|
|
|
|
if ( inst->IsDirectAssignment() && inst->v1 == inst->v2 )
|
|
KillInst(i);
|
|
}
|
|
|
|
frame_sizeI = shared_frame_denizens.size();
|
|
}
|
|
|
|
void ZAMCompiler::ReMapInterpreterFrame() {
|
|
// First, track function parameters. We could elide this if we
|
|
// decide to alter the calling sequence for compiled functions.
|
|
auto args = scope->OrderedVars();
|
|
int nparam = func->GetType()->Params()->NumFields();
|
|
int next_interp_slot = 0;
|
|
|
|
for ( const auto& a : args ) {
|
|
if ( --nparam < 0 )
|
|
break;
|
|
|
|
ASSERT(a->Offset() == next_interp_slot);
|
|
++next_interp_slot;
|
|
}
|
|
|
|
// Update frame sizes for functions that might have more than
|
|
// one body.
|
|
auto f = func.get();
|
|
if ( remapped_intrp_frame_sizes.count(f) == 0 || remapped_intrp_frame_sizes[f] < next_interp_slot )
|
|
remapped_intrp_frame_sizes[f] = next_interp_slot;
|
|
}
|
|
|
|
void ZAMCompiler::ReMapVar(const ID* id, int slot, zeek_uint_t inst) {
|
|
// A greedy algorithm for this is to simply find the first suitable
|
|
// frame slot. We do that with one twist: we also look for a
|
|
// compatible slot for which its current end-of-scope is exactly
|
|
// the start-of-scope for the new identifier. The advantage of
|
|
// doing so is that this commonly occurs for code like "a.1 = a"
|
|
// from resolving parameters to inlined functions, and if "a.1" and
|
|
// "a" share the same slot then we can elide the assignment.
|
|
//
|
|
// In principle we could perhaps do better than greedy using a more
|
|
// powerful allocation method like graph coloring. However, far and
|
|
// away the bulk of our variables are short-lived temporaries,
|
|
// for which greedy should work fine.
|
|
//
|
|
// Note, we also need to make sure that denizens sharing a slot
|
|
// are all consistently either managed, or non-managed, types.
|
|
// One subtlety in this regard is that identifiers that are types
|
|
// should always be deemed "managed", even if the type they refer
|
|
// to is not managed, because what matters for uses of those
|
|
// identifiers is interpreting them as "any" values having an
|
|
// internal type of TYPE_TYPE.
|
|
bool is_managed = ZVal::IsManagedType(id->GetType()) || id->IsType();
|
|
|
|
int apt_slot = -1;
|
|
for ( unsigned int i = 0; i < shared_frame_denizens.size(); ++i ) {
|
|
auto& s = shared_frame_denizens[i];
|
|
|
|
// Note that the following test is <= rather than <.
|
|
// This is because assignment in instructions happens after
|
|
// using any variables to compute the value to assign.
|
|
// ZAM instructions are careful to allow operands and
|
|
// assignment destinations to refer to the same slot.
|
|
|
|
if ( s.scope_end <= static_cast<int>(inst) && s.is_managed == is_managed ) { // It's compatible.
|
|
if ( s.scope_end == static_cast<int>(inst) ) { // It ends right on the money.
|
|
apt_slot = i;
|
|
break;
|
|
}
|
|
|
|
else if ( apt_slot < 0 )
|
|
// We haven't found a candidate yet, take
|
|
// this one, but keep looking.
|
|
apt_slot = i;
|
|
}
|
|
}
|
|
|
|
int scope_end = denizen_ending[slot]->inst_num;
|
|
|
|
if ( apt_slot < 0 ) {
|
|
// No compatible existing slot. Create a new one.
|
|
apt_slot = shared_frame_denizens.size();
|
|
|
|
FrameSharingInfo info;
|
|
info.is_managed = is_managed;
|
|
shared_frame_denizens.push_back(info);
|
|
|
|
if ( is_managed )
|
|
managed_slotsI.push_back(apt_slot);
|
|
}
|
|
|
|
auto& s = shared_frame_denizens[apt_slot];
|
|
|
|
s.ids.push_back(id);
|
|
s.id_start.push_back(inst);
|
|
s.scope_end = scope_end;
|
|
|
|
frame1_to_frame2[slot] = apt_slot;
|
|
}
|
|
|
|
void ZAMCompiler::CheckSlotAssignment(int slot, const ZInstI* inst) {
|
|
ASSERT(slot >= 0 && static_cast<zeek_uint_t>(slot) < frame_denizens.size());
|
|
// We construct temporaries such that their values are never used
|
|
// earlier than their definitions in loop bodies. For other
|
|
// denizens, however, they can be, so in those cases we expand the
|
|
// lifetime beginning to the start of any loop region.
|
|
if ( ! reducer->IsTemporary(frame_denizens[slot]) )
|
|
inst = BeginningOfLoop(inst, 1);
|
|
|
|
SetLifetimeStart(slot, inst);
|
|
}
|
|
|
|
void ZAMCompiler::SetLifetimeStart(int slot, const ZInstI* inst) {
|
|
if ( denizen_beginning.count(slot) > 0 ) {
|
|
// Beginning of denizen's lifetime already seen, nothing
|
|
// more to do other than check for consistency.
|
|
ASSERT(denizen_beginning[slot]->inst_num <= inst->inst_num);
|
|
}
|
|
|
|
else { // denizen begins here
|
|
denizen_beginning[slot] = inst;
|
|
|
|
if ( inst_beginnings.count(inst) == 0 )
|
|
// Need to create a set to track the denizens
|
|
// beginning at the instruction.
|
|
inst_beginnings[inst] = {};
|
|
|
|
inst_beginnings[inst].insert(frame_denizens[slot]);
|
|
}
|
|
}
|
|
|
|
void ZAMCompiler::CheckSlotUse(int slot, const ZInstI* inst) {
|
|
if ( slot < 0 )
|
|
return;
|
|
|
|
ASSERT(static_cast<zeek_uint_t>(slot) < frame_denizens.size());
|
|
|
|
if ( denizen_beginning.count(slot) == 0 ) {
|
|
ODesc d;
|
|
inst->loc->Loc()->Describe(&d);
|
|
reporter->Error("%s: value used but not set: %s", d.Description(), frame_denizens[slot]->Name());
|
|
}
|
|
|
|
// See comment above about temporaries not having their values
|
|
// extend around loop bodies. HOWEVER if a temporary is defined
|
|
// at a lower loop depth than that for this instruction, then we
|
|
// extend its lifetime to the end of this instruction's loop.
|
|
if ( reducer->IsTemporary(frame_denizens[slot]) ) {
|
|
ASSERT(denizen_beginning.count(slot) > 0);
|
|
int definition_depth = denizen_beginning[slot]->loop_depth;
|
|
|
|
if ( inst->loop_depth > definition_depth )
|
|
inst = EndOfLoop(inst, inst->loop_depth);
|
|
}
|
|
else
|
|
inst = EndOfLoop(inst, 1);
|
|
|
|
ExtendLifetime(slot, inst);
|
|
}
|
|
|
|
void ZAMCompiler::ExtendLifetime(int slot, const ZInstI* inst) {
|
|
ASSERT(slot >= 0);
|
|
auto id = frame_denizens[slot];
|
|
auto& t = id->GetType();
|
|
|
|
if ( denizen_ending.count(slot) > 0 ) {
|
|
// End of denizen's lifetime already seen. Check for
|
|
// consistency and then extend as needed.
|
|
|
|
auto old_inst = denizen_ending[slot];
|
|
|
|
// Don't complain for temporaries that already have
|
|
// extended lifetimes, as that can happen if they're
|
|
// used as a "for" loop-over target, which already
|
|
// extends lifetime across the body of the loop.
|
|
if ( inst->loop_depth > 0 && reducer->IsTemporary(frame_denizens[slot]) &&
|
|
old_inst->inst_num >= inst->inst_num )
|
|
return;
|
|
|
|
// We expect to only be increasing the slot's lifetime ...
|
|
// *unless* we're inside a nested loop, in which case
|
|
// the slot might have already been extended to the
|
|
// end of the outer loop.
|
|
ASSERT(old_inst->inst_num <= inst->inst_num || inst->loop_depth > 1);
|
|
|
|
if ( old_inst->inst_num < inst->inst_num ) { // Extend.
|
|
inst_endings[old_inst].erase(frame_denizens[slot]);
|
|
|
|
if ( inst_endings.count(inst) == 0 )
|
|
inst_endings[inst] = {};
|
|
|
|
inst_endings[inst].insert(frame_denizens[slot]);
|
|
denizen_ending.at(slot) = inst;
|
|
}
|
|
}
|
|
|
|
else { // first time seeing a use of this denizen
|
|
denizen_ending[slot] = inst;
|
|
|
|
if ( inst_endings.count(inst) == 0 ) {
|
|
IDSet denizens;
|
|
inst_endings[inst] = denizens;
|
|
}
|
|
|
|
inst_endings[inst].insert(frame_denizens[slot]);
|
|
}
|
|
}
|
|
|
|
const ZInstI* ZAMCompiler::BeginningOfLoop(const ZInstI* inst, int depth) const {
|
|
auto i = inst->inst_num;
|
|
|
|
while ( i >= 0 && insts1[i]->loop_depth >= depth )
|
|
--i;
|
|
|
|
if ( i == inst->inst_num )
|
|
return inst;
|
|
|
|
// We moved backwards to just beyond a loop that inst is part of.
|
|
// Move to that loop's (live) beginning.
|
|
++i;
|
|
while ( i != inst->inst_num && ! insts1[i]->live )
|
|
++i;
|
|
|
|
return insts1[i];
|
|
}
|
|
|
|
const ZInstI* ZAMCompiler::EndOfLoop(const ZInstI* inst, int depth) const {
|
|
auto i = inst->inst_num;
|
|
|
|
while ( i < int(insts1.size()) && insts1[i]->loop_depth >= depth )
|
|
++i;
|
|
|
|
if ( i == inst->inst_num )
|
|
return inst;
|
|
|
|
// We moved forwards to just beyond a loop that inst is part of.
|
|
// Move to that loop's (live) end.
|
|
--i;
|
|
while ( i != inst->inst_num && ! insts1[i]->live )
|
|
--i;
|
|
|
|
return insts1[i];
|
|
}
|
|
|
|
bool ZAMCompiler::VarIsUsed(int slot) const {
|
|
for ( auto& inst : insts1 ) {
|
|
if ( inst->live && inst->UsesSlot(slot) )
|
|
return true;
|
|
|
|
auto aux = inst->aux;
|
|
if ( aux && aux->elems_has_slots ) {
|
|
for ( int j = 0; j < aux->n; ++j )
|
|
if ( aux->elems[j].Slot() == slot )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ZInstI* ZAMCompiler::FirstLiveInst(ZInstI* i, bool follow_gotos) {
|
|
if ( i == pending_inst )
|
|
return nullptr;
|
|
|
|
auto n = FirstLiveInst(i->inst_num, follow_gotos);
|
|
if ( n < insts1.size() )
|
|
return insts1[n];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
zeek_uint_t ZAMCompiler::FirstLiveInst(zeek_uint_t i, bool follow_gotos) {
|
|
zeek_uint_t num_inspected = 0;
|
|
while ( i < insts1.size() ) {
|
|
auto i0 = insts1[i];
|
|
if ( i0->live ) {
|
|
if ( follow_gotos && i0->IsUnconditionalBranch() ) {
|
|
if ( ++num_inspected > insts1.size() ) {
|
|
reporter->Error("%s contains an infinite loop", func->GetName().c_str());
|
|
return i;
|
|
}
|
|
|
|
i = i0->target->inst_num;
|
|
continue;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
++i;
|
|
++num_inspected;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
void ZAMCompiler::KillInst(zeek_uint_t i) {
|
|
auto inst = insts1[i];
|
|
|
|
ASSERT(inst->live);
|
|
|
|
inst->live = false;
|
|
auto t = inst->target;
|
|
if ( t ) {
|
|
if ( t->live ) {
|
|
--(t->num_labels);
|
|
ASSERT(t->num_labels >= 0);
|
|
}
|
|
else
|
|
ASSERT(t->num_labels == 0);
|
|
}
|
|
|
|
int num_labels = inst->num_labels;
|
|
// We're about to transfer its labels.
|
|
inst->num_labels = 0;
|
|
|
|
if ( inst->IsUnconditionalBranch() ) {
|
|
ASSERT(t);
|
|
|
|
// No direct flow after this point ... unless we're
|
|
// branching to the next immediate live instruction.
|
|
auto after_inst = NextLiveInst(inst, true);
|
|
auto live_target = FirstLiveInst(t, true);
|
|
|
|
if ( after_inst != live_target ) {
|
|
// No flow after inst. Don't propagate its labels.
|
|
// Given that, it had better not have any!
|
|
ASSERT(num_labels == 0);
|
|
}
|
|
}
|
|
|
|
ZInstI* succ = nullptr;
|
|
|
|
if ( num_labels > 0 ) {
|
|
for ( auto j = i + 1; j < insts1.size(); ++j ) {
|
|
if ( insts1[j]->live ) {
|
|
succ = insts1[j];
|
|
break;
|
|
}
|
|
}
|
|
if ( succ )
|
|
succ->num_labels += num_labels;
|
|
}
|
|
|
|
// Look into propagating control flow info.
|
|
if ( inst->aux && ! inst->aux->cft.empty() ) {
|
|
auto& cft = inst->aux->cft;
|
|
|
|
if ( cft.count(CFT_ELSE) > 0 ) {
|
|
// Push forward unless this was the end of the block.
|
|
if ( cft.count(CFT_BLOCK_END) == 0 ) {
|
|
ASSERT(succ);
|
|
AddCFT(succ, CFT_ELSE);
|
|
}
|
|
else
|
|
// But if it *was* the end of the block, remove that block.
|
|
--cft[CFT_BLOCK_END];
|
|
}
|
|
|
|
if ( cft.count(CFT_BREAK) > 0 ) {
|
|
// ### Factor this with the following
|
|
// Propagate breaks backwards.
|
|
int j = i;
|
|
while ( --j >= 0 )
|
|
if ( insts1[j]->live )
|
|
break;
|
|
|
|
ASSERT(j >= 0);
|
|
|
|
// Make sure the CFT entry is created.
|
|
AddCFT(insts1[j], CFT_BREAK);
|
|
|
|
auto be_cnt = cft[CFT_BREAK];
|
|
--be_cnt; // we already did one above
|
|
insts1[j]->aux->cft[CFT_BREAK] += be_cnt;
|
|
}
|
|
|
|
if ( cft.count(CFT_BLOCK_END) > 0 ) {
|
|
// Propagate block-ends backwards.
|
|
int j = i;
|
|
while ( --j >= 0 )
|
|
if ( insts1[j]->live )
|
|
break;
|
|
|
|
ASSERT(j >= 0);
|
|
|
|
// Make sure the CFT entry is created.
|
|
AddCFT(insts1[j], CFT_BLOCK_END);
|
|
|
|
auto be_cnt = cft[CFT_BLOCK_END];
|
|
--be_cnt; // we already did one above
|
|
insts1[j]->aux->cft[CFT_BLOCK_END] += be_cnt;
|
|
}
|
|
|
|
// If's can be killed because their bodies become empty,
|
|
// break's because they just lead to their following instruction,
|
|
// and next's if they become dead code.
|
|
// However, loop's and next's should not be killed.
|
|
ASSERT(cft.count(CFT_LOOP) == 0);
|
|
ASSERT(cft.count(CFT_LOOP_COND) == 0);
|
|
}
|
|
}
|
|
|
|
void ZAMCompiler::KillInsts(zeek_uint_t i) {
|
|
auto inst = insts1[i];
|
|
|
|
ASSERT(inst->num_labels == 0);
|
|
|
|
KillInst(i);
|
|
|
|
for ( auto j = i + 1; j < insts1.size(); ++j ) {
|
|
auto succ = insts1[j];
|
|
if ( succ->live ) {
|
|
if ( succ->num_labels == 0 )
|
|
KillInst(j);
|
|
else
|
|
// Found viable succeeding code.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace zeek::detail
|