// See the file "COPYING" in the main distribution directory for copyright. #include "zeek/script_opt/ZAM/ZInst.h" #include "zeek/Desc.h" #include "zeek/Func.h" #include "zeek/Reporter.h" using std::string; namespace zeek::detail { void ZInst::Dump(zeek_uint_t inst_num, const FrameReMap* mappings) const { // printf("v%d ", n); auto id1 = VName(1, inst_num, mappings); auto id2 = VName(2, inst_num, mappings); auto id3 = VName(3, inst_num, mappings); auto id4 = VName(4, inst_num, mappings); Dump(id1, id2, id3, id4); } void ZInst::Dump(const string& id1, const string& id2, const string& id3, const string& id4) const { printf("%s ", ZOP_name(op)); // printf("(%s) ", op_type_name(op_type)); if ( t && 0 ) printf("(%s) ", type_name(t->Tag())); switch ( op_type ) { case OP_X: break; case OP_V: printf("%s", id1.c_str()); break; case OP_VV: printf("%s, %s", id1.c_str(), id2.c_str()); break; case OP_VVV: printf("%s, %s, %s", id1.c_str(), id2.c_str(), id3.c_str()); break; case OP_VVVV: printf("%s, %s, %s, %s", id1.c_str(), id2.c_str(), id3.c_str(), id4.c_str()); break; case OP_VVVC: printf("%s, %s, %s, %s", id1.c_str(), id2.c_str(), id3.c_str(), ConstDump().c_str()); break; case OP_C: printf("%s", ConstDump().c_str()); break; case OP_VC: printf("%s, %s", id1.c_str(), ConstDump().c_str()); break; case OP_VVC: printf("%s, %s, %s", id1.c_str(), id2.c_str(), ConstDump().c_str()); break; case OP_V_I1: printf("%d", v1); break; case OP_VC_I1: printf("%d %s", v1, ConstDump().c_str()); break; case OP_VV_FRAME: printf("%s, interpreter frame[%d]", id1.c_str(), v2); break; case OP_VV_I2: printf("%s, %d", id1.c_str(), v2); break; case OP_VV_I1_I2: printf("%d, %d", v1, v2); break; case OP_VVC_I2: printf("%s, %d, %s", id1.c_str(), v2, ConstDump().c_str()); break; case OP_VVV_I3: printf("%s, %s, %d", id1.c_str(), id2.c_str(), v3); break; case OP_VVV_I2_I3: printf("%s, %d, %d", id1.c_str(), v2, v3); break; case OP_VVVV_I4: printf("%s, %s, %s, %d", id1.c_str(), id2.c_str(), id3.c_str(), v4); break; case OP_VVVV_I3_I4: printf("%s, %s, %d, %d", id1.c_str(), id2.c_str(), v3, v4); break; case OP_VVVV_I2_I3_I4: printf("%s, %d, %d, %d", id1.c_str(), v2, v3, v4); break; case OP_VVVC_I3: printf("%s, %s, %d, %s", id1.c_str(), id2.c_str(), v3, ConstDump().c_str()); break; case OP_VVVC_I2_I3: printf("%s, %d, %d, %s", id1.c_str(), v2, v3, ConstDump().c_str()); break; case OP_VVVC_I1_I2_I3: printf("%d, %d, %d, %s", v1, v2, v3, ConstDump().c_str()); break; } if ( func ) printf(" (func %s)", func->Name()); printf("\n"); } int ZInst::NumFrameSlots() const { switch ( op_type ) { case OP_X: case OP_C: case OP_V_I1: case OP_VC_I1: case OP_VV_I1_I2: case OP_VVVC_I1_I2_I3: return 0; case OP_V: case OP_VC: case OP_VV_FRAME: case OP_VV_I2: case OP_VVC_I2: case OP_VVV_I2_I3: case OP_VVVC_I2_I3: case OP_VVVV_I2_I3_I4: return 1; case OP_VV: case OP_VVC: case OP_VVV_I3: case OP_VVVC_I3: case OP_VVVV_I3_I4: return 2; case OP_VVV: case OP_VVVC: case OP_VVVV_I4: return 3; case OP_VVVV: return 4; } return -1; } int ZInst::NumSlots() const { switch ( op_type ) { case OP_C: case OP_X: return 0; case OP_V: case OP_V_I1: case OP_VC: case OP_VC_I1: return 1; case OP_VV: case OP_VVC: case OP_VV_FRAME: case OP_VV_I2: case OP_VVC_I2: case OP_VV_I1_I2: return 2; case OP_VVV: case OP_VVV_I3: case OP_VVV_I2_I3: case OP_VVVC: case OP_VVVC_I3: case OP_VVVC_I2_I3: case OP_VVVC_I1_I2_I3: return 3; case OP_VVVV: case OP_VVVV_I4: case OP_VVVV_I3_I4: case OP_VVVV_I2_I3_I4: return 4; } return -1; } string ZInst::VName(int n, zeek_uint_t inst_num, const FrameReMap* mappings) const { if ( n > NumFrameSlots() ) return ""; int slot = n == 1 ? v1 : (n == 2 ? v2 : (n == 3 ? v3 : v4)); if ( slot < 0 ) return ""; // Find which identifier manifests at this instruction. ASSERT(slot >= 0 && static_cast(slot) < mappings->size()); auto& map = (*mappings)[slot]; unsigned int i; for ( i = 0; i < map.id_start.size(); ++i ) { // If the slot is right at the boundary between two identifiers, then // it matters whether this is an assigned slot (starts right here) vs. // not assigned (ignore change right at the boundary and stick with // older value). auto target_inst = AssignsToSlot(n) ? inst_num + 1 : inst_num; if ( map.id_start[i] >= target_inst ) // Went too far. break; } if ( i < map.id_start.size() ) { ASSERT(i > 0); } auto id = map.names.empty() ? map.ids[i - 1]->Name() : map.names[i - 1]; return util::fmt("%d (%s)", slot, id); } ValPtr ZInst::ConstVal() const { switch ( op_type ) { case OP_C: case OP_VC: case OP_VC_I1: case OP_VVC: case OP_VVC_I2: case OP_VVVC: case OP_VVVC_I3: case OP_VVVC_I2_I3: case OP_VVVC_I1_I2_I3: return c.ToVal(t); case OP_X: case OP_V: case OP_VV: case OP_VVV: case OP_VVVV: case OP_V_I1: case OP_VV_FRAME: case OP_VV_I2: case OP_VV_I1_I2: case OP_VVV_I3: case OP_VVV_I2_I3: case OP_VVVV_I4: case OP_VVVV_I3_I4: case OP_VVVV_I2_I3_I4: return nullptr; } return nullptr; } bool ZInst::IsLoopIterationAdvancement() const { switch ( op ) { case OP_NEXT_TABLE_ITER_VV: case OP_NEXT_TABLE_ITER_NO_VARS_VV: case OP_NEXT_TABLE_ITER_VAL_VAR_VVV: case OP_NEXT_TABLE_ITER_VAL_VAR_NO_VARS_VVV: case OP_NEXT_VECTOR_ITER_VVV: case OP_NEXT_VECTOR_BLANK_ITER_VV: case OP_NEXT_VECTOR_ITER_VAL_VAR_VVVV: case OP_NEXT_VECTOR_BLANK_ITER_VAL_VAR_VVV: case OP_NEXT_STRING_ITER_VVV: case OP_NEXT_STRING_BLANK_ITER_VV: return true; default: return false; } } bool ZInst::AssignsToSlot1() const { switch ( op_type ) { case OP_X: case OP_C: case OP_V_I1: case OP_VC_I1: case OP_VV_I1_I2: case OP_VVVC_I1_I2_I3: return false; // We use this ginormous set of cases rather than "default" so // that when we add a new operand type, we have to consider // its behavior here. (Same for many of the other switch's // used for ZInst/ZinstI.) case OP_V: case OP_VC: case OP_VV_FRAME: case OP_VV_I2: case OP_VVC_I2: case OP_VVV_I2_I3: case OP_VVVC_I2_I3: case OP_VVVV_I2_I3_I4: case OP_VV: case OP_VVC: case OP_VVV_I3: case OP_VVVV_I3_I4: case OP_VVVC_I3: case OP_VVV: case OP_VVVC: case OP_VVVV_I4: case OP_VVVV: auto fl = op1_flavor[op]; return fl == OP1_WRITE || fl == OP1_READ_WRITE; } return false; } bool ZInst::AssignsToSlot(int slot) const { switch ( op ) { case OP_NEXT_VECTOR_ITER_VAL_VAR_VVVV: return slot == 1 || slot == 2; default: return slot == 1 && AssignsToSlot1(); } } string ZInst::ConstDump() const { auto v = ConstVal(); ODesc d; d.Clear(); v->Describe(&d); return d.Description(); } void ZInstI::Dump(const FrameMap* frame_ids, const FrameReMap* remappings) const { int n = NumFrameSlots(); // printf("v%d ", n); auto id1 = VName(1, frame_ids, remappings); auto id2 = VName(2, frame_ids, remappings); auto id3 = VName(3, frame_ids, remappings); auto id4 = VName(4, frame_ids, remappings); ZInst::Dump(id1, id2, id3, id4); } string ZInstI::VName(int n, const FrameMap* frame_ids, const FrameReMap* remappings) const { if ( n > NumFrameSlots() ) return ""; int slot = n == 1 ? v1 : (n == 2 ? v2 : (n == 3 ? v3 : v4)); if ( slot < 0 ) return ""; const ID* id; if ( remappings && live ) { // Find which identifier manifests at this instruction. ASSERT(slot >= 0 && static_cast(slot) < remappings->size()); auto& map = (*remappings)[slot]; unsigned int i; auto inst_num_u = static_cast(inst_num); for ( i = 0; i < map.id_start.size(); ++i ) { // See discussion for ZInst::VName, though this is // a tad different since we have the general notion // of AssignsToSlot(). if ( AssignsToSlot(n) ) { if ( map.id_start[i] > inst_num_u ) break; } else if ( map.id_start[i] >= inst_num_u ) // Went too far. break; } if ( i < map.id_start.size() ) { ASSERT(i > 0); } // For ZInstI's, map.ids is always populated. id = map.ids[i - 1]; } else id = (*frame_ids)[slot]; return util::fmt("%d (%s)", slot, id->Name()); } bool ZInstI::DoesNotContinue() const { switch ( op ) { case OP_GOTO_V: case OP_HOOK_BREAK_X: case OP_RETURN_C: case OP_RETURN_V: case OP_RETURN_X: return true; default: return false; } } bool ZInstI::IsDirectAssignment() const { if ( op_type != OP_VV ) return false; switch ( op ) { 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_P: case OP_ASSIGN_VV_R: case OP_ASSIGN_VV_S: case OP_ASSIGN_VV_T: case OP_ASSIGN_VV_U: case OP_ASSIGN_VV_V: case OP_ASSIGN_VV_a: case OP_ASSIGN_VV_f: case OP_ASSIGN_VV_t: case OP_ASSIGN_VV: return true; default: return false; } } 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 { return op_side_effects[op]; } bool ZInstI::UsesSlot(int slot) const { auto fl = op1_flavor[op]; auto v1_relevant = fl == OP1_READ || fl == OP1_READ_WRITE; auto v1_match = v1_relevant && v1 == slot; switch ( op_type ) { case OP_X: case OP_C: case OP_V_I1: case OP_VC_I1: case OP_VV_I1_I2: case OP_VVVC_I1_I2_I3: return false; case OP_V: case OP_VC: case OP_VV_FRAME: case OP_VV_I2: case OP_VVC_I2: case OP_VVV_I2_I3: case OP_VVVC_I2_I3: case OP_VVVV_I2_I3_I4: return v1_match; case OP_VV: case OP_VVC: case OP_VVV_I3: case OP_VVVV_I3_I4: case OP_VVVC_I3: return v1_match || v2 == slot; case OP_VVV: case OP_VVVC: case OP_VVVV_I4: return v1_match || v2 == slot || v3 == slot; case OP_VVVV: return v1_match || v2 == slot || v3 == slot || v4 == slot; } return false; } bool ZInstI::UsesSlots(int& s1, int& s2, int& s3, int& s4) const { s1 = s2 = s3 = s4 = -1; auto fl = op1_flavor[op]; auto v1_relevant = fl == OP1_READ || fl == OP1_READ_WRITE; switch ( op_type ) { case OP_X: case OP_C: case OP_V_I1: case OP_VC_I1: case OP_VV_I1_I2: case OP_VVVC_I1_I2_I3: return false; case OP_V: case OP_VC: case OP_VV_FRAME: case OP_VV_I2: case OP_VVC_I2: case OP_VVV_I2_I3: case OP_VVVC_I2_I3: case OP_VVVV_I2_I3_I4: if ( ! v1_relevant ) return false; s1 = v1; return true; case OP_VV: case OP_VVC: case OP_VVV_I3: case OP_VVVV_I3_I4: case OP_VVVC_I3: s1 = v2; if ( v1_relevant ) s2 = v1; return true; case OP_VVV: case OP_VVVC: case OP_VVVV_I4: s1 = v2; s2 = v3; if ( v1_relevant ) s3 = v1; return true; case OP_VVVV: s1 = v2; s2 = v3; s3 = v4; if ( v1_relevant ) s4 = v1; return true; } return false; } void ZInstI::UpdateSlots(std::vector& slot_mapping) { switch ( op_type ) { case OP_X: case OP_C: case OP_V_I1: case OP_VC_I1: case OP_VV_I1_I2: case OP_VVVC_I1_I2_I3: return; // so we don't do any v1 remapping. case OP_V: case OP_VC: case OP_VV_FRAME: case OP_VV_I2: case OP_VVC_I2: case OP_VVV_I2_I3: case OP_VVVC_I2_I3: case OP_VVVV_I2_I3_I4: break; case OP_VV: case OP_VVC: case OP_VVV_I3: case OP_VVVV_I3_I4: case OP_VVVC_I3: v2 = slot_mapping[v2]; break; case OP_VVV: case OP_VVVC: case OP_VVVV_I4: v2 = slot_mapping[v2]; v3 = slot_mapping[v3]; break; case OP_VVVV: v2 = slot_mapping[v2]; v3 = slot_mapping[v3]; v4 = slot_mapping[v4]; break; } // Note, unlike for UsesSlots() we do *not* include OP1_READ_WRITE // here, because such instructions will already have v1 remapped // given it's an assignment target. if ( op1_flavor[op] == OP1_READ && v1 >= 0 ) v1 = slot_mapping[v1]; } bool ZInstI::IsGlobalLoad() const { if ( op == OP_LOAD_GLOBAL_TYPE_VV ) // These don't have flavors. return true; static std::unordered_set global_ops; if ( global_ops.empty() ) { // Initialize the set. for ( int t = 0; t < NUM_TYPES; ++t ) { TypeTag tag = TypeTag(t); ZOp global_op_flavor = AssignmentFlavor(OP_LOAD_GLOBAL_VV, tag, false); if ( global_op_flavor != OP_NOP ) global_ops.insert(global_op_flavor); } } 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) { auto v = ce->ValuePtr(); t = ce->GetType(); c = ZVal(v, t); if ( ZAM_error ) reporter->InternalError("bad value compiling code"); } } // namespace zeek::detail