Merge remote-tracking branch 'origin/topic/vern/ast-opt'

- Removed a stray `printf()` from script-opt Reduce code

* origin/topic/vern/ast-opt: (23 commits)
  Tweak minor const-ref/std::move things in script-opt code
  Add alternative 'xform' baseline for test: broker.store.create-failure
  Add explicit return value to Reducer::SameOp()
  fixed AST transformation logic for boolean expressions
  don't know how I overlooked these minor test suite updates
  "opt" btest baseline updates due to AST optimizations changing printed code
  "opt" baseline exceptions due to incompatibility with optimize-AST and -u
  new "opt" btest alternative
  update to "xform" test suite baseline due to recent line number changes
  logic for driving AST optimization when requested
  methods implementing AST optimization (aliasing, constant propagation, CSE)
  helper class checking if common-subexpression elimination opportunity is valid
  helper function for comparing collections of definition points
  track more information about temporary variables
  simplify and tidy up some interfaces
  enable setting "-O optimize-AST" option
  fix for reducing operations on boolean vectors
  control whether checking for type-equivalence generates warnings
  remove unneeded virtual method (same as inherited method)
  accessors for additional Expr subclasses
  ...
This commit is contained in:
Jon Siwek 2021-03-08 18:28:22 -08:00
commit f2d3bf3037
26 changed files with 15968 additions and 146 deletions

View file

@ -1,13 +1,13 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "ID.h"
#include "Var.h"
#include "Scope.h"
#include "Expr.h"
#include "Stmt.h"
#include "Desc.h"
#include "ProfileFunc.h"
#include "Reporter.h"
#include "zeek/ID.h"
#include "zeek/Var.h"
#include "zeek/Scope.h"
#include "zeek/Expr.h"
#include "zeek/Stmt.h"
#include "zeek/Desc.h"
#include "zeek/Reporter.h"
#include "zeek/script_opt/ProfileFunc.h"
#include "zeek/script_opt/Reduce.h"
#include "zeek/script_opt/TempVar.h"
@ -15,17 +15,6 @@
namespace zeek::detail {
Reducer::Reducer(Scope* s)
{
scope = s;
}
Reducer::~Reducer()
{
for ( int i = 0; i < temps.length(); ++i )
delete temps[i];
}
ExprPtr Reducer::GenTemporaryExpr(const TypePtr& t, ExprPtr rhs)
{
auto e = make_intrusive<NameExpr>(GenTemporary(t, rhs));
@ -38,7 +27,7 @@ NameExprPtr Reducer::UpdateName(NameExprPtr n)
if ( NameIsReduced(n.get()) )
return n;
return make_intrusive<NameExpr>(FindNewLocal(n.get()));
return make_intrusive<NameExpr>(FindNewLocal(n));
}
bool Reducer::NameIsReduced(const NameExpr* n) const
@ -80,7 +69,7 @@ bool Reducer::IDsAreReduced(const IDPList* ids) const
bool Reducer::IDsAreReduced(const std::vector<IDPtr>& ids) const
{
for ( auto& id : ids )
for ( const auto& id : ids )
if ( ! ID_IsReduced(id) )
return false;
@ -92,7 +81,7 @@ IDPtr Reducer::UpdateID(IDPtr id)
if ( ID_IsReduced(id) )
return id;
return FindNewLocal(id.get());
return FindNewLocal(id);
}
bool Reducer::ID_IsReduced(const ID* id) const
@ -101,9 +90,9 @@ bool Reducer::ID_IsReduced(const ID* id) const
IsNewLocal(id);
}
NameExprPtr Reducer::GenInlineBlockName(IDPtr id)
NameExprPtr Reducer::GenInlineBlockName(const IDPtr& id)
{
return make_intrusive<NameExpr>(GenLocal(id.get()));
return make_intrusive<NameExpr>(GenLocal(id));
}
NameExprPtr Reducer::PushInlineBlock(TypePtr type)
@ -138,65 +127,566 @@ bool Reducer::SameVal(const Val* v1, const Val* v2) const
return v1 == v2;
}
IDPtr Reducer::GenTemporary(const TypePtr& t, ExprPtr rhs)
ExprPtr Reducer::NewVarUsage(IDPtr var, const DefPoints* dps, const Expr* orig)
{
if ( Optimizing() )
reporter->InternalError("Generating a new temporary while optimizing");
if ( ! dps )
reporter->InternalError("null defpoints in NewVarUsage");
auto temp = new TempVar(temps.length(), t, rhs);
IDPtr temp_id = install_ID(temp->Name(), "<internal>", false, false);
auto var_usage = make_intrusive<NameExpr>(var);
SetDefPoints(var_usage.get(), dps);
TrackExprReplacement(orig, var_usage.get());
temp->SetID(temp_id);
temp_id->SetType(t);
temps.append(temp);
ids_to_temps[temp_id.get()] = temp;
return temp_id;
return var_usage;
}
IDPtr Reducer::FindNewLocal(ID* id)
const DefPoints* Reducer::GetDefPoints(const NameExpr* var)
{
auto mapping = orig_to_new_locals.find(id);
auto dps = FindDefPoints(var);
if ( mapping != orig_to_new_locals.end() )
return mapping->second;
if ( ! dps )
{
auto id = var->Id();
auto di = mgr->GetConstID_DI(id);
auto rds = mgr->GetPreMaxRDs(GetRDLookupObj(var));
return GenLocal(id);
dps = rds->GetDefPoints(di);
SetDefPoints(var, dps);
}
return dps;
}
IDPtr Reducer::GenLocal(ID* orig)
const DefPoints* Reducer::FindDefPoints(const NameExpr* var) const
{
if ( Optimizing() )
reporter->InternalError("Generating a new local while optimizing");
char buf[8192];
int n = new_locals.size();
snprintf(buf, sizeof buf, "%s.%d", orig->Name(), n);
IDPtr local_id = install_ID(buf, "<internal>", false, false);
local_id->SetType(orig->GetType());
local_id->SetAttrs(orig->GetAttrs());
new_locals.insert(local_id.get());
orig_to_new_locals[orig] = local_id;
return local_id;
}
bool Reducer::IsNewLocal(const ID* id) const
{
ID* non_const_ID = (ID*) id; // I don't get why C++ requires this
return new_locals.count(non_const_ID) != 0;
}
TempVar* Reducer::FindTemporary(const ID* id) const
{
auto tmp = ids_to_temps.find(id);
if ( tmp == ids_to_temps.end() )
auto dps = var_usage_to_DPs.find(var);
if ( dps == var_usage_to_DPs.end() )
return nullptr;
else
return tmp->second;
return dps->second;
}
void Reducer::SetDefPoints(const NameExpr* var, const DefPoints* dps)
{
var_usage_to_DPs[var] = dps;
}
bool Reducer::SameOp(const Expr* op1, const Expr* op2)
{
if ( op1 == op2 )
return true;
if ( op1->Tag() != op2->Tag() )
return false;
if ( op1->Tag() == EXPR_NAME )
{
// Needs to be both the same identifier and in contexts
// where the identifier has the same definition points.
auto op1_n = op1->AsNameExpr();
auto op2_n = op2->AsNameExpr();
auto op1_id = op1_n->Id();
auto op2_id = op2_n->Id();
if ( op1_id != op2_id )
return false;
auto op1_dps = GetDefPoints(op1_n);
auto op2_dps = GetDefPoints(op2_n);
return same_DPs(op1_dps, op2_dps);
}
else if ( op1->Tag() == EXPR_CONST )
{
auto op1_c = op1->AsConstExpr();
auto op2_c = op2->AsConstExpr();
auto op1_v = op1_c->Value();
auto op2_v = op2_c->Value();
return SameVal(op1_v, op2_v);
}
else if ( op1->Tag() == EXPR_LIST )
{
auto op1_l = op1->AsListExpr()->Exprs();
auto op2_l = op2->AsListExpr()->Exprs();
if ( op1_l.length() != op2_l.length() )
return false;
for ( auto i = 0; i < op1_l.length(); ++i )
if ( ! SameExpr(op1_l[i], op2_l[i]) )
return false;
return true;
}
reporter->InternalError("bad singleton tag");
return false;
}
bool Reducer::SameExpr(const Expr* e1, const Expr* e2)
{
if ( e1 == e2 )
return true;
if ( e1->Tag() != e2->Tag() )
return false;
if ( ! same_type(e1->GetType(), e2->GetType()) )
return false;
switch ( e1->Tag() ) {
case EXPR_NAME:
case EXPR_CONST:
return SameOp(e1, e2);
case EXPR_CLONE:
case EXPR_RECORD_CONSTRUCTOR:
case EXPR_TABLE_CONSTRUCTOR:
case EXPR_SET_CONSTRUCTOR:
case EXPR_VECTOR_CONSTRUCTOR:
case EXPR_EVENT:
case EXPR_SCHEDULE:
// These always generate a new value.
return false;
case EXPR_INCR:
case EXPR_DECR:
case EXPR_AND_AND:
case EXPR_OR_OR:
case EXPR_ASSIGN:
case EXPR_FIELD_ASSIGN:
case EXPR_INDEX_SLICE_ASSIGN:
// All of these should have been translated into something
// else.
reporter->InternalError("Unexpected tag in Reducer::SameExpr");
case EXPR_ANY_INDEX:
{
auto a1 = e1->AsAnyIndexExpr();
auto a2 = e2->AsAnyIndexExpr();
if ( a1->Index() != a2->Index() )
return false;
return SameOp(a1->GetOp1(), a2->GetOp1());
}
case EXPR_FIELD:
{
auto f1 = e1->AsFieldExpr();
auto f2 = e2->AsFieldExpr();
if ( f1->Field() != f2->Field() )
return false;
return SameOp(f1->GetOp1(), f2->GetOp1());
}
case EXPR_HAS_FIELD:
{
auto f1 = e1->AsHasFieldExpr();
auto f2 = e2->AsHasFieldExpr();
if ( f1->Field() != f2->Field() )
return false;
return SameOp(f1->GetOp1(), f2->GetOp1());
}
case EXPR_LIST:
{
auto l1 = e1->AsListExpr()->Exprs();
auto l2 = e2->AsListExpr()->Exprs();
ASSERT(l1.length() == l2.length());
for ( int i = 0; i < l1.length(); ++i )
if ( ! SameExpr(l1[i], l2[i]) )
return false;
return true;
}
case EXPR_CALL:
{
auto c1 = e1->AsCallExpr();
auto c2 = e2->AsCallExpr();
auto f1 = c1->Func();
auto f2 = c2->Func();
if ( f1 != f2 )
return false;
if ( ! f1->IsPure() )
return false;
return SameExpr(c1->Args(), c2->Args());
}
case EXPR_LAMBDA:
return false;
case EXPR_IS:
{
if ( ! SameOp(e1->GetOp1(), e2->GetOp1()) )
return false;
auto i1 = e1->AsIsExpr();
auto i2 = e2->AsIsExpr();
return same_type(i1->TestType(), i2->TestType());
}
default:
if ( ! e1->GetOp1() )
reporter->InternalError("Bad default in Reducer::SameExpr");
if ( ! SameOp(e1->GetOp1(), e2->GetOp1()) )
return false;
if ( e1->GetOp2() && ! SameOp(e1->GetOp2(), e2->GetOp2()) )
return false;
if ( e1->GetOp3() && ! SameOp(e1->GetOp3(), e2->GetOp3()) )
return false;
return true;
}
}
IDPtr Reducer::FindExprTmp(const Expr* rhs, const Expr* a,
const std::shared_ptr<const TempVar>& lhs_tmp)
{
for ( const auto& et_i : expr_temps )
{
if ( et_i->Alias() || ! et_i->IsActive() || et_i == lhs_tmp )
// This can happen due to re-reduction while
// optimizing.
continue;
auto et_i_expr = et_i->RHS();
if ( SameExpr(rhs, et_i_expr) )
{
// We have an apt candidate. Make sure its value
// always makes it here.
auto id = et_i->Id().get();
// We use 'a' in the following rather than rhs
// because the RHS can get rewritten (for example,
// due to folding) after we generate RDs, and
// thus might not have any.
if ( ! mgr->HasSinglePreMinRD(a, id) )
// The temporary's value isn't guaranteed
// to make it here.
continue;
// Make sure there aren't ambiguities due to
// possible modifications to aggregates.
if ( ! ExprValid(id, et_i_expr, a) )
continue;
return et_i->Id();
}
}
return nullptr;
}
bool Reducer::ExprValid(const ID* id, const Expr* e1, const Expr* e2) const
{
// Here are the considerations for expression validity.
//
// * None of the operands used in the given expression can
// have been assigned.
//
// * If the expression yields an aggregate, or one of the
// operands in the expression is an aggregate, then there
// must not be any assignments to aggregates of the same
// type(s). This is to deal with possible aliases.
//
// * Same goes to modifications of aggregates via "add" or "delete".
//
// * No propagation of expressions that are based on aggregates
// across function calls.
//
// * No propagation of expressions that are based on globals
// across calls.
// Tracks which ID's are germane for our analysis.
std::vector<const ID*> ids;
ids.push_back(id);
// Identify variables involved in the expression.
CheckIDs(e1->GetOp1().get(), ids);
CheckIDs(e1->GetOp2().get(), ids);
CheckIDs(e1->GetOp3().get(), ids);
if ( e1->Tag() == EXPR_NAME )
ids.push_back(e1->AsNameExpr()->Id());
CSE_ValidityChecker vc(ids, e1, e2);
reduction_root->Traverse(&vc);
return vc.IsValid();
}
void Reducer::CheckIDs(const Expr* e, std::vector<const ID*>& ids) const
{
if ( ! e )
return;
if ( e->Tag() == EXPR_LIST )
{
const auto& e_l = e->AsListExpr()->Exprs();
for ( auto i = 0; i < e_l.length(); ++i )
CheckIDs(e_l[i], ids);
}
else if ( e->Tag() == EXPR_NAME )
ids.push_back(e->AsNameExpr()->Id());
}
bool Reducer::IsCSE(const AssignExpr* a, const NameExpr* lhs, const Expr* rhs)
{
auto a_max_rds = mgr->GetPostMaxRDs(GetRDLookupObj(a));
auto lhs_id = lhs->Id();
auto lhs_tmp = FindTemporary(lhs_id); // nil if LHS not a temporary
auto rhs_tmp = FindExprTmp(rhs, a, lhs_tmp);
ExprPtr new_rhs;
if ( rhs_tmp )
{ // We already have a temporary
auto tmp_di = mgr->GetConstID_DI(rhs_tmp.get());
auto dps = a_max_rds->GetDefPoints(tmp_di);
new_rhs = NewVarUsage(rhs_tmp, dps, rhs);
rhs = new_rhs.get();
}
if ( lhs_tmp )
{
if ( rhs->Tag() == EXPR_CONST )
{ // mark temporary as just being a constant
lhs_tmp->SetConst(rhs->AsConstExpr());
return true;
}
if ( rhs->Tag() == EXPR_NAME )
{
auto rhs_id = rhs->AsNameExpr()->IdPtr();
auto rhs_tmp_var = FindTemporary(rhs_id.get());
if ( rhs_tmp_var && rhs_tmp_var->Const() )
{ // temporary can be replaced with constant
lhs_tmp->SetConst(rhs_tmp_var->Const());
return true;
}
// Treat the LHS as either an alias for the RHS,
// or as a constant if the RHS is a constant in
// this context.
auto rhs_di = mgr->GetConstID_DI(rhs_id.get());
auto dps = a_max_rds->GetDefPoints(rhs_di);
auto rhs_const = CheckForConst(rhs_id, dps);
if ( rhs_const )
lhs_tmp->SetConst(rhs_const);
else
lhs_tmp->SetAlias(rhs_id, dps);
return true;
}
// Track where we define the temporary.
auto lhs_di = mgr->GetConstID_DI(lhs_id);
auto dps = a_max_rds->GetDefPoints(lhs_di);
if ( lhs_tmp->DPs() && ! same_DPs(lhs_tmp->DPs(), dps) )
reporter->InternalError("double DPs for temporary");
lhs_tmp->SetDPs(dps);
SetDefPoints(lhs, dps);
expr_temps.emplace_back(lhs_tmp);
}
return false;
}
const ConstExpr* Reducer::CheckForConst(const IDPtr& id,
const DefPoints* dps) const
{
if ( ! dps || dps->length() == 0 )
// This can happen for access to uninitialized values.
return nullptr;
if ( dps->length() != 1 )
// Multiple definitions of the variable reach to this
// location. In theory we could check whether they *all*
// provide the same constant, but that hardly seems likely.
return nullptr;
// Identifier has a unique definition.
auto dp = (*dps)[0];
const Expr* e = nullptr;
if ( dp.Tag() == STMT_DEF )
{
auto s = dp.StmtVal();
if ( s->Tag() == STMT_CATCH_RETURN )
{
// Change 's' to refer to the associated assignment
// statement, if any.
auto cr = s->AsCatchReturnStmt();
s = cr->AssignStmt().get();
if ( ! s )
return nullptr;
}
if ( s->Tag() != STMT_EXPR )
// Defined in a statement other than an assignment.
return nullptr;
e = s->AsExprStmt()->StmtExpr();
}
else if ( dp.Tag() == EXPR_DEF )
e = dp.ExprVal();
else
return nullptr;
if ( e->Tag() != EXPR_ASSIGN )
// Not sure why this would happen, other than EXPR_APPEND_TO,
// but in any case not an expression we can mine for a
// constant.
return nullptr;
auto rhs = e->GetOp2();
if ( rhs->Tag() != EXPR_CONST )
return nullptr;
return rhs->AsConstExpr();
}
void Reducer::TrackExprReplacement(const Expr* orig, const Expr* e)
{
new_expr_to_orig[e] = orig;
}
const Obj* Reducer::GetRDLookupObj(const Expr* e) const
{
auto orig_e = new_expr_to_orig.find(e);
if ( orig_e == new_expr_to_orig.end() )
return e;
else
return orig_e->second;
}
ExprPtr Reducer::OptExpr(Expr* e)
{
StmtPtr opt_stmts;
auto opt_e = e->Reduce(this, opt_stmts);
if ( opt_stmts )
reporter->InternalError("Generating new statements while optimizing");
if ( opt_e->Tag() == EXPR_NAME )
return UpdateExpr(opt_e);
return opt_e;
}
ExprPtr Reducer::UpdateExpr(ExprPtr e)
{
if ( e->Tag() != EXPR_NAME )
return OptExpr(e);
auto n = e->AsNameExpr();
auto id = n->Id();
if ( id->IsGlobal() )
return e;
auto tmp_var = FindTemporary(id);
if ( ! tmp_var )
{
auto max_rds = mgr->GetPreMaxRDs(GetRDLookupObj(n));
IDPtr id_ptr = {NewRef{}, id};
auto di = mgr->GetConstID_DI(id);
auto dps = max_rds->GetDefPoints(di);
auto is_const = CheckForConst(id_ptr, dps);
if ( is_const )
{
// Remember this variable as one whose value
// we used for constant propagation. That
// ensures we can subsequently not complain
// about it being assigned but not used (though
// we can still omit the assignment).
constant_vars.insert(id);
return make_intrusive<ConstExpr>(is_const->ValuePtr());
}
return e;
}
if ( tmp_var->Const() )
return make_intrusive<ConstExpr>(tmp_var->Const()->ValuePtr());
auto alias = tmp_var->Alias();
if ( alias )
{
// Make sure that the definition points for the
// alias here are the same as when the alias
// was created.
auto alias_tmp = FindTemporary(alias.get());
if ( alias_tmp )
{
while ( alias_tmp->Alias() )
{
// Alias chains can occur due to
// re-reduction while optimizing.
auto a_id = alias_tmp->Id();
if ( a_id == id )
return e;
alias_tmp = FindTemporary(alias_tmp->Id().get());
}
// Temporaries always have only one definition point,
// so no need to check for consistency.
auto new_usage = NewVarUsage(alias, alias_tmp->DPs(), e.get());
return new_usage;
}
auto e_max_rds = mgr->GetPreMaxRDs(GetRDLookupObj(e.get()));
auto alias_di = mgr->GetConstID_DI(alias.get());
auto alias_dps = e_max_rds->GetDefPoints(alias_di);
if ( same_DPs(alias_dps, tmp_var->DPs()) )
return NewVarUsage(alias, alias_dps, e.get());
else
return e;
}
auto rhs = tmp_var->RHS();
if ( rhs->Tag() != EXPR_CONST )
return e;
auto c = rhs->AsConstExpr();
return make_intrusive<ConstExpr>(c->ValuePtr());
}
StmtPtr Reducer::MergeStmts(const NameExpr* lhs, ExprPtr rhs, Stmt* succ_stmt)
@ -258,9 +748,291 @@ StmtPtr Reducer::MergeStmts(const NameExpr* lhs, ExprPtr rhs, Stmt* succ_stmt)
return make_intrusive<ExprStmt>(merge_e);
}
void Reducer::TrackExprReplacement(const Expr* orig, const Expr* e)
IDPtr Reducer::GenTemporary(const TypePtr& t, ExprPtr rhs)
{
new_expr_to_orig[e] = orig;
if ( Optimizing() )
reporter->InternalError("Generating a new temporary while optimizing");
if ( omitted_stmts.size() > 0 )
reporter->InternalError("Generating a new temporary while pruning statements");
auto temp = std::make_shared<TempVar>(temps.size(), t, rhs);
IDPtr temp_id = install_ID(temp->Name(), "<internal>", false, false);
temp->SetID(temp_id);
temp_id->SetType(t);
temps.push_back(temp);
ids_to_temps[temp_id.get()] = temp;
return temp_id;
}
IDPtr Reducer::FindNewLocal(const IDPtr& id)
{
auto mapping = orig_to_new_locals.find(id.get());
if ( mapping != orig_to_new_locals.end() )
return mapping->second;
return GenLocal(id);
}
IDPtr Reducer::GenLocal(const IDPtr& orig)
{
if ( Optimizing() )
reporter->InternalError("Generating a new local while optimizing");
if ( omitted_stmts.size() > 0 )
reporter->InternalError("Generating a new local while pruning statements");
char buf[8192];
int n = new_locals.size();
snprintf(buf, sizeof buf, "%s.%d", orig->Name(), n);
IDPtr local_id = install_ID(buf, "<internal>", false, false);
local_id->SetType(orig->GetType());
local_id->SetAttrs(orig->GetAttrs());
new_locals.insert(local_id.get());
orig_to_new_locals[orig.get()] = local_id;
return local_id;
}
bool Reducer::IsNewLocal(const ID* id) const
{
ID* non_const_ID = (ID*) id; // I don't get why C++ requires this
return new_locals.count(non_const_ID) != 0;
}
std::shared_ptr<TempVar> Reducer::FindTemporary(const ID* id) const
{
auto tmp = ids_to_temps.find(id);
if ( tmp == ids_to_temps.end() )
return nullptr;
else
return tmp->second;
}
CSE_ValidityChecker::CSE_ValidityChecker(const std::vector<const ID*>& _ids,
const Expr* _start_e, const Expr* _end_e)
: ids(_ids)
{
start_e = _start_e;
end_e = _end_e;
// Track whether this is a record assignment, in which case
// we're attuned to assignments to the same field for the
// same type of record.
if ( start_e->Tag() == EXPR_FIELD )
{
field = start_e->AsFieldExpr()->Field();
// Track the type of the record, too, so we don't confuse
// field references to different records that happen to
// have the same offset as potential aliases.
field_type = start_e->GetOp1()->GetType();
}
else
field = -1; // flags that there's no relevant field
}
TraversalCode CSE_ValidityChecker::PreStmt(const Stmt* s)
{
if ( s->Tag() == STMT_ADD || s->Tag() == STMT_DELETE )
in_aggr_mod_stmt = true;
return TC_CONTINUE;
}
TraversalCode CSE_ValidityChecker::PostStmt(const Stmt* s)
{
if ( s->Tag() == STMT_ADD || s->Tag() == STMT_DELETE )
in_aggr_mod_stmt = false;
return TC_CONTINUE;
}
TraversalCode CSE_ValidityChecker::PreExpr(const Expr* e)
{
if ( e == start_e )
{
ASSERT(! have_start_e);
have_start_e = true;
// Don't analyze the expression, as it's our starting
// point and we don't want to conflate its properties
// with those of any intervening expression.
return TC_CONTINUE;
}
if ( e == end_e )
{
if ( ! have_start_e )
reporter->InternalError("CSE_ValidityChecker: saw end but not start");
ASSERT(! have_end_e);
have_end_e = true;
// ... and we're now done.
return TC_ABORTALL;
}
if ( ! have_start_e )
// We don't yet have a starting point.
return TC_CONTINUE;
// We have a starting point, and not yet an ending point.
auto t = e->Tag();
switch ( t ) {
case EXPR_ASSIGN:
{
auto lhs_ref = e->GetOp1()->AsRefExprPtr();
auto lhs = lhs_ref->GetOp1()->AsNameExpr();
if ( CheckID(ids, lhs->Id(), false) )
{
is_valid = false;
return TC_ABORTALL;
}
// Note, we don't use CheckAggrMod() because this
// is a plain assignment. It might be changing a variable's
// binding to an aggregate, but it's not changing the
// aggregate itself.
}
break;
case EXPR_INDEX_ASSIGN:
{
auto lhs_aggr = e->GetOp1();
auto lhs_aggr_id = lhs_aggr->AsNameExpr()->Id();
if ( CheckID(ids, lhs_aggr_id, true) || CheckAggrMod(ids, e) )
{
is_valid = false;
return TC_ABORTALL;
}
}
break;
case EXPR_FIELD_LHS_ASSIGN:
{
auto lhs = e->GetOp1();
auto lhs_aggr_id = lhs->AsNameExpr()->Id();
auto lhs_field = e->AsFieldLHSAssignExpr()->Field();
if ( lhs_field == field &&
same_type(lhs_aggr_id->GetType(), field_type) )
{
// Potential assignment to the same field as for
// our expression of interest. Even if the
// identifier involved is not one we have our eye
// on, due to aggregate aliasing this could be
// altering the value of our expression, so bail.
is_valid = false;
return TC_ABORTALL;
}
if ( CheckID(ids, lhs_aggr_id, true) || CheckAggrMod(ids, e) )
{
is_valid = false;
return TC_ABORTALL;
}
}
break;
case EXPR_CALL:
{
for ( auto i : ids )
if ( i->IsGlobal() || IsAggr(i->GetType()) )
{
is_valid = false;
return TC_ABORTALL;
}
}
break;
default:
if ( in_aggr_mod_stmt && (t == EXPR_INDEX || t == EXPR_FIELD) )
{
auto aggr = e->GetOp1();
auto aggr_id = aggr->AsNameExpr()->Id();
if ( CheckID(ids, aggr_id, true) )
{
is_valid = false;
return TC_ABORTALL;
}
}
break;
}
return TC_CONTINUE;
}
bool CSE_ValidityChecker::CheckID(const std::vector<const ID*>& ids,
const ID* id, bool ignore_orig) const
{
// Only check type info for aggregates.
auto id_t = IsAggr(id->GetType()) ? id->GetType() : nullptr;
for ( auto i : ids )
{
if ( ignore_orig && i == ids.front() )
continue;
if ( id == i )
return true; // reassignment
if ( id_t && same_type(id_t, i->GetType()) )
// Same-type aggregate.
return true;
}
return false;
}
bool CSE_ValidityChecker::CheckAggrMod(const std::vector<const ID*>& ids,
const Expr* e) const
{
const auto& e_i_t = e->GetType();
if ( IsAggr(e_i_t) )
{
// This assignment sets an aggregate value.
// Look for type matches.
for ( auto i : ids )
if ( same_type(e_i_t, i->GetType()) )
return true;
}
return false;
}
bool same_DPs(const DefPoints* dp1, const DefPoints* dp2)
{
if ( dp1 == dp2 )
return true;
if ( ! dp1 || ! dp2 )
return false;
// Given how we construct DPs, they should be element-by-element
// equivalent; we don't have to worry about reordering.
if ( dp1->length() != dp2->length() )
return false;
for ( auto i = 0; i < dp1->length(); ++i )
if ( ! (*dp1)[i].SameAs((*dp2)[i]) )
return false;
return true;
}