diff --git a/tools/gen-zam/src/Gen-ZAM.cc b/tools/gen-zam/src/Gen-ZAM.cc index f030d93df2..8ba48830c2 100644 --- a/tools/gen-zam/src/Gen-ZAM.cc +++ b/tools/gen-zam/src/Gen-ZAM.cc @@ -26,39 +26,52 @@ static char under_to_dash(char c) struct TypeInfo { string tag; - ZAM_ExprType et; + ZAM_Type zt; string suffix; string accessor; // doesn't include "As" prefix or "()" suffix bool is_managed; }; static vector ZAM_type_info = { - {"TYPE_ADDR", ZAM_EXPR_TYPE_ADDR, "A", "Addr", true}, - {"TYPE_ANY", ZAM_EXPR_TYPE_ANY, "a", "Any", true}, - {"TYPE_COUNT", ZAM_EXPR_TYPE_UINT, "U", "Count", false}, - {"TYPE_DOUBLE", ZAM_EXPR_TYPE_DOUBLE, "D", "Double", false}, - {"TYPE_FILE", ZAM_EXPR_TYPE_FILE, "f", "File", true}, - {"TYPE_FUNC", ZAM_EXPR_TYPE_FUNC, "F", "Func", true}, - {"TYPE_INT", ZAM_EXPR_TYPE_INT, "I", "Int", false}, - {"TYPE_LIST", ZAM_EXPR_TYPE_LIST, "L", "List", true}, - {"TYPE_OPAQUE", ZAM_EXPR_TYPE_OPAQUE, "O", "Opaque", true}, - {"TYPE_PATTERN", ZAM_EXPR_TYPE_PATTERN, "P", "Pattern", true}, - {"TYPE_RECORD", ZAM_EXPR_TYPE_RECORD, "R", "Record", true}, - {"TYPE_STRING", ZAM_EXPR_TYPE_STRING, "S", "String", true}, - {"TYPE_SUBNET", ZAM_EXPR_TYPE_SUBNET, "N", "SubNet", true}, - {"TYPE_TABLE", ZAM_EXPR_TYPE_TABLE, "T", "Table", true}, - {"TYPE_TYPE", ZAM_EXPR_TYPE_TYPE, "t", "Type", true}, - {"TYPE_VECTOR", ZAM_EXPR_TYPE_VECTOR, "V", "Vector", true}, + {"TYPE_ADDR", ZAM_TYPE_ADDR, "A", "Addr", true}, + {"TYPE_ANY", ZAM_TYPE_ANY, "a", "Any", true}, + {"TYPE_COUNT", ZAM_TYPE_UINT, "U", "Count", false}, + {"TYPE_DOUBLE", ZAM_TYPE_DOUBLE, "D", "Double", false}, + {"TYPE_FILE", ZAM_TYPE_FILE, "f", "File", true}, + {"TYPE_FUNC", ZAM_TYPE_FUNC, "F", "Func", true}, + {"TYPE_INT", ZAM_TYPE_INT, "I", "Int", false}, + {"TYPE_LIST", ZAM_TYPE_LIST, "L", "List", true}, + {"TYPE_OPAQUE", ZAM_TYPE_OPAQUE, "O", "Opaque", true}, + {"TYPE_PATTERN", ZAM_TYPE_PATTERN, "P", "Pattern", true}, + {"TYPE_RECORD", ZAM_TYPE_RECORD, "R", "Record", true}, + {"TYPE_STRING", ZAM_TYPE_STRING, "S", "String", true}, + {"TYPE_SUBNET", ZAM_TYPE_SUBNET, "N", "SubNet", true}, + {"TYPE_TABLE", ZAM_TYPE_TABLE, "T", "Table", true}, + {"TYPE_TYPE", ZAM_TYPE_TYPE, "t", "Type", true}, + {"TYPE_VECTOR", ZAM_TYPE_VECTOR, "V", "Vector", true}, }; -// Given a ZAM_ExprType, returns the corresponding TypeInfo. -const TypeInfo& find_type_info(ZAM_ExprType et) - { - assert(et != ZAM_EXPR_TYPE_NONE); +// Maps op-type mnemonics to the corresponding internal value used by Gen-ZAM. +static unordered_map type_names = { + {'*', ZAM_TYPE_DEFAULT}, {'A', ZAM_TYPE_ADDR}, {'a', ZAM_TYPE_ANY}, + {'D', ZAM_TYPE_DOUBLE}, {'f', ZAM_TYPE_FILE}, {'F', ZAM_TYPE_FUNC}, + {'I', ZAM_TYPE_INT}, {'L', ZAM_TYPE_LIST}, {'X', ZAM_TYPE_NONE}, + {'O', ZAM_TYPE_OPAQUE}, {'P', ZAM_TYPE_PATTERN}, {'R', ZAM_TYPE_RECORD}, + {'S', ZAM_TYPE_STRING}, {'N', ZAM_TYPE_SUBNET}, {'T', ZAM_TYPE_TABLE}, + {'t', ZAM_TYPE_TYPE}, {'U', ZAM_TYPE_UINT}, {'V', ZAM_TYPE_VECTOR}, +}; - auto pred = [et](const TypeInfo& ti) -> bool +// Inverse of the above. +static unordered_map expr_name_types; + +// Given a ZAM_Type, returns the corresponding TypeInfo. +const TypeInfo& find_type_info(ZAM_Type zt) { - return ti.et == et; + assert(zt != ZAM_TYPE_NONE); + + auto pred = [zt](const TypeInfo& ti) -> bool + { + return ti.zt == zt; }; auto ti = std::find_if(ZAM_type_info.begin(), ZAM_type_info.end(), pred); @@ -66,73 +79,75 @@ const TypeInfo& find_type_info(ZAM_ExprType et) return *ti; } -// Given a ZAM_ExprType, return its ZVal accessor. Takes into account +// Given a ZAM_Type, return its ZVal accessor. Takes into account // some naming inconsistencies between ZVal's and Val's. -string find_type_accessor(ZAM_ExprType et) +string find_type_accessor(ZAM_Type zt, bool is_lhs) { - switch ( et ) - { - case ZAM_EXPR_TYPE_NONE: - return ""; + if ( zt == ZAM_TYPE_NONE ) + return ""; - case ZAM_EXPR_TYPE_UINT: - return "uint_val"; + string acc = string("As") + find_type_info(zt).accessor; + if ( is_lhs ) + acc += "Ref"; - case ZAM_EXPR_TYPE_PATTERN: - return "re_val"; - - default: - { - string acc = find_type_info(et).accessor; - transform(acc.begin(), acc.end(), acc.begin(), ::tolower); - return acc + "_val"; - } - } + return acc + "()"; } // Maps ZAM operand types to pairs of (1) the C++ name used to declare // the operand in a method declaration, and (2) the variable name to // use for the operand. -unordered_map> ArgsManager::ot_to_args = { - {ZAM_OT_AUX, {"OpaqueVals*", "v"}}, - {ZAM_OT_CONSTANT, {"const ConstExpr*", "c"}}, - {ZAM_OT_EVENT_HANDLER, {"EventHandler*", "h"}}, - {ZAM_OT_INT, {"int", "i"}}, - {ZAM_OT_LIST, {"const ListExpr*", "l"}}, - {ZAM_OT_RECORD_FIELD, {"const NameExpr*", "n"}}, - {ZAM_OT_VAR, {"const NameExpr*", "n"}}, +unordered_map> ArgsManager::oc_to_args = { + {ZAM_OC_AUX, {"OpaqueVals*", "v"}}, + {ZAM_OC_CONSTANT, {"const ConstExpr*", "c"}}, + {ZAM_OC_EVENT_HANDLER, {"EventHandler*", "h"}}, + {ZAM_OC_INT, {"int", "i"}}, + {ZAM_OC_BRANCH, {"int", "i"}}, + {ZAM_OC_GLOBAL, {"int", "i"}}, + {ZAM_OC_STEP_ITER, {"int", "i"}}, + {ZAM_OC_TBL_ITER, {"int", "i"}}, + {ZAM_OC_LIST, {"const ListExpr*", "l"}}, + {ZAM_OC_RECORD_FIELD, {"const NameExpr*", "n"}}, + {ZAM_OC_VAR, {"const NameExpr*", "n"}}, // The following gets special treatment. - {ZAM_OT_ASSIGN_FIELD, {"const NameExpr*", "n"}}, + {ZAM_OC_ASSIGN_FIELD, {"const NameExpr*", "n"}}, }; -ArgsManager::ArgsManager(const vector& ot, ZAM_InstClass zc) +// The different operand classes that are represented as "raw" integers +// (meaning the slot value is used directly, rather than indexing the frame). +static const set raw_int_oc( + { ZAM_OC_BRANCH, ZAM_OC_GLOBAL, ZAM_OC_INT, ZAM_OC_STEP_ITER, ZAM_OC_TBL_ITER } +); + +ArgsManager::ArgsManager(const OCVec& oc_orig, ZAM_InstClass zc) { + auto oc = oc_orig; + if ( zc == ZIC_COND ) + // Remove the final entry corresponding to the branch, as + // we'll automatically generate it subsequently. + oc.pop_back(); + int n = 0; bool add_field = false; - for ( const auto& ot_i : ot ) + for ( const auto& ot_i : oc ) { - if ( ot_i == ZAM_OT_NONE ) + if ( ot_i == ZAM_OC_NONE ) { // it had better be the only operand type - assert(ot.size() == 1); + assert(oc.size() == 1); break; } - if ( n++ == 0 && zc == ZIC_COND ) - // Skip the conditional's nominal assignment slot. - continue; + ++n; // Start off the argument info using the usual case // of (1) same method parameter name as GenInst argument, // and (2) not requiring a record field. - auto& arg_i = ot_to_args[ot_i]; - Arg arg = {arg_i.second, arg_i.first, arg_i.second, false}; + auto& arg_i = oc_to_args[ot_i]; + Arg arg = {arg_i.second, arg_i.first, arg_i.second}; - if ( ot_i == ZAM_OT_ASSIGN_FIELD ) + if ( ot_i == ZAM_OC_ASSIGN_FIELD ) { - arg.is_field = true; - if ( n == 1 ) { // special-case the parameter arg.decl_name = "flhs"; @@ -185,9 +200,6 @@ void ArgsManager::Differentiate() // Finally, build the full versions of the declaration and parameters. - // Tracks how many record fields we're dealing with. - int num_fields = 0; - for ( auto& arg : args ) { if ( ! full_decl.empty() ) @@ -200,20 +212,6 @@ void ArgsManager::Differentiate() full_params += arg.param_name; params.push_back(arg.param_name); - - if ( arg.is_field ) - ++num_fields; - } - - assert(num_fields <= 2); - - // Add in additional arguments/parameters for record fields. - if ( num_fields == 1 ) - full_params += ", field"; - else if ( num_fields == 2 ) - { - full_decl += ", int field2"; - full_params += ", field1, field2"; } } @@ -242,32 +240,127 @@ void ZAM_OpTemplate::Build() Parse(words[0], line, words); } + + if ( ! op_classes.empty() && ! op_classes_vec.empty() ) + Gripe("\"class\" and \"classes\" are mutually exclusive"); + + if ( ! op_classes.empty() || ! op_classes_vec.empty() ) + { + auto nclasses = op_classes.empty() ? + op_classes_vec[0].size() : op_classes.size(); + + for ( auto& oc : op_classes_vec ) + if ( oc.size() != nclasses ) + Gripe("size mismatch in \"classes\" specifications"); + + if ( ! op_types.empty() && op_types.size() != nclasses ) + Gripe("number of \"op-types\" elements must match \"class\"/\"classes\""); + } + + else if ( ! op_types.empty() ) + Gripe("\"op-types\" can only be used with \"class\"/\"classes\""); } void ZAM_OpTemplate::Instantiate() { - InstantiateOp(OperandTypes(), IncludesVectorOp()); + if ( IsPredicate() ) + InstantiatePredicate(); + + else if ( op_classes_vec.empty() ) + InstantiateOp(OperandClasses(), IncludesVectorOp()); + + else + for ( auto& ocs : op_classes_vec ) + InstantiateOp(ocs, IncludesVectorOp()); + } + +void ZAM_OpTemplate::InstantiatePredicate() + { + if ( ! op_classes_vec.empty() ) + Gripe("\"predicate\" cannot include \"classes\""); + + if ( op_classes.empty() ) + Gripe("\"predicate\" requires a \"class\""); + + if ( IncludesVectorOp() ) + Gripe("\"predicate\" cannot include \"vector\""); + + // Build 3 forms: an assignment to an int-value'd $$, a conditional + // if the evaluation is true, and one if it is not. + + auto orig_eval = eval; + // Remove trailing '\n' from eval. + orig_eval.pop_back(); + + auto orig_op_classes = op_classes; + bool no_classes = orig_op_classes[0] == ZAM_OC_NONE; + + // Assignment form. + op_classes.clear(); + op_classes.push_back(ZAM_OC_VAR); + if ( ! no_classes ) + op_classes.insert(op_classes.end(), orig_op_classes.begin(), orig_op_classes.end()); + + string target_accessor; + + if ( ! op_types.empty() ) + op_types.insert(op_types.begin(), ZAM_TYPE_INT); + else + target_accessor = ".AsIntRef()"; + + eval = "$$" + target_accessor + " = " + orig_eval + ";"; + + InstantiateOp(op_classes, false); + + // Conditional form - branch if not true. + + if ( ! op_types.empty() ) + { + // Remove 'V' at the beginning from the assignment form, + // and add a 'i' at the end for the branch. + op_types.erase(op_types.begin()); + op_types.push_back(ZAM_TYPE_INT); + } + + cname += "_COND"; + op1_flavor = "OP1_READ"; + if ( no_classes ) + op_classes.clear(); + else + op_classes = orig_op_classes; + + op_classes.push_back(ZAM_OC_BRANCH); + + auto branch_pos = to_string(op_classes.size()); + auto suffix = " )\n\t\t$" + branch_pos; + eval = "if ( ! (" + orig_eval + ")" + suffix; + InstantiateOp(op_classes, false); + + // Now the form that branches if true. + cname = "NOT_" + cname; + eval = "if ( (" + orig_eval + ")" + suffix; + InstantiateOp(op_classes, false); } void ZAM_OpTemplate::UnaryInstantiate() { // First operand is always the frame slot to which this operation // assigns the result of the applying unary operator. - vector ots = {ZAM_OT_VAR}; - ots.resize(2); + OCVec ocs = {ZAM_OC_VAR}; + ocs.resize(2); // Now build versions for a constant operand (maybe not actually // needed due to constant folding, but sometimes that gets deferred // to run-time) ... if ( ! NoConst() ) { - ots[1] = ZAM_OT_CONSTANT; - InstantiateOp(ots, IncludesVectorOp()); + ocs[1] = ZAM_OC_CONSTANT; + InstantiateOp(ocs, IncludesVectorOp()); } // ... and for a variable (frame-slot) operand. - ots[1] = ZAM_OT_VAR; - InstantiateOp(ots, IncludesVectorOp()); + ocs[1] = ZAM_OC_VAR; + InstantiateOp(ocs, IncludesVectorOp()); } void ZAM_OpTemplate::Parse(const string& attr, const string& line, const Words& words) @@ -275,56 +368,42 @@ void ZAM_OpTemplate::Parse(const string& attr, const string& line, const Words& int num_args = -1; // -1 = don't enforce int nwords = static_cast(words.size()); - if ( attr == "type" ) + if ( attr == "class" ) { if ( nwords <= 1 ) g->Gripe("missing argument", line); num_args = 1; + op_classes = ParseClass(words[1]); + } - const char* types = words[1].c_str(); - while ( *types ) + else if ( attr == "classes" ) + { + if ( nwords <= 1 ) + g->Gripe("missing argument", line); + + num_args = -1; + + for ( int i = 1; i < nwords; ++i ) + op_classes_vec.push_back(ParseClass(words[i])); + } + + else if ( attr == "op-types" ) + { + if ( words.size() == 1 ) + g->Gripe("op-types needs arguments", line); + + for ( auto i = 1U; i < words.size(); ++i ) { - ZAM_OperandType ot = ZAM_OT_NONE; - switch ( *types ) - { - case 'C': - ot = ZAM_OT_CONSTANT; - break; - case 'F': - ot = ZAM_OT_ASSIGN_FIELD; - break; - case 'H': - ot = ZAM_OT_EVENT_HANDLER; - break; - case 'L': - ot = ZAM_OT_LIST; - break; - case 'O': - ot = ZAM_OT_AUX; - break; - case 'R': - ot = ZAM_OT_RECORD_FIELD; - break; - case 'V': - ot = ZAM_OT_VAR; - break; - case 'i': - ot = ZAM_OT_INT; - break; + auto& w_i = words[i]; + if ( w_i.size() != 1 ) + g->Gripe("bad op-types argument", w_i); - case 'X': - ot = ZAM_OT_NONE; - break; + auto et_c = w_i.c_str()[0]; + if ( type_names.count(et_c) == 0 ) + g->Gripe("bad op-types argument", w_i); - default: - g->Gripe("bad operand type", words[1]); - break; - } - - AddOpType(ot); - - ++types; + op_types.push_back(type_names[et_c]); } } @@ -415,6 +494,71 @@ void ZAM_OpTemplate::Parse(const string& attr, const string& line, const Words& g->Gripe("extraneous or missing arguments", line); } +OCVec ZAM_OpTemplate::ParseClass(const string& spec) const + { + OCVec ocs; + + const char* types = spec.c_str(); + while ( *types ) + { + ZAM_OperandClass oc = ZAM_OC_NONE; + + switch ( *types ) + { + case 'C': + oc = ZAM_OC_CONSTANT; + break; + case 'F': + oc = ZAM_OC_ASSIGN_FIELD; + break; + case 'H': + oc = ZAM_OC_EVENT_HANDLER; + break; + case 'L': + oc = ZAM_OC_LIST; + break; + case 'O': + oc = ZAM_OC_AUX; + break; + case 'R': + oc = ZAM_OC_RECORD_FIELD; + break; + case 'V': + oc = ZAM_OC_VAR; + break; + case 'i': + oc = ZAM_OC_INT; + break; + case 'b': + oc = ZAM_OC_BRANCH; + break; + case 'f': // 'f' = "for" loop + oc = ZAM_OC_TBL_ITER; + break; + case 'g': + oc = ZAM_OC_GLOBAL; + break; + case 's': + oc = ZAM_OC_STEP_ITER; + break; + + case 'X': + oc = ZAM_OC_NONE; + break; + + default: + g->Gripe("bad operand type", spec); + break; + } + + ocs.push_back(oc); + + ++types; + } + + return ocs; + } + string ZAM_OpTemplate::GatherEval() { string res; @@ -436,7 +580,7 @@ string ZAM_OpTemplate::GatherEval() int ZAM_OpTemplate::ExtractTypeParam(const string& arg) { if ( arg == "$$" ) - return 1; + return 0; if ( arg[0] != '$' ) g->Gripe("bad set-type parameter, should be $n", arg); @@ -446,59 +590,93 @@ int ZAM_OpTemplate::ExtractTypeParam(const string& arg) if ( param <= 0 || param > 2 ) g->Gripe("bad set-type parameter, should be $1 or $2", arg); - // Convert operand to underlying instruction element, i.e., add - // one to account for the $$ assignment slot. - return param + 1; + return param; } // Maps an operand type to a character mnemonic used to distinguish // it from others. -unordered_map ZAM_OpTemplate::ot_to_char = { - {ZAM_OT_AUX, 'O'}, {ZAM_OT_CONSTANT, 'C'}, {ZAM_OT_EVENT_HANDLER, 'H'}, - {ZAM_OT_ASSIGN_FIELD, 'F'}, {ZAM_OT_INT, 'i'}, {ZAM_OT_LIST, 'L'}, - {ZAM_OT_NONE, 'X'}, {ZAM_OT_RECORD_FIELD, 'R'}, {ZAM_OT_VAR, 'V'}, +unordered_map ZAM_OpTemplate::oc_to_char = { + {ZAM_OC_AUX, 'O'}, + {ZAM_OC_CONSTANT, 'C'}, + {ZAM_OC_EVENT_HANDLER, 'H'}, + {ZAM_OC_ASSIGN_FIELD, 'F'}, + {ZAM_OC_INT, 'i'}, + {ZAM_OC_LIST, 'L'}, + {ZAM_OC_NONE, 'X'}, + {ZAM_OC_RECORD_FIELD, 'R'}, + {ZAM_OC_VAR, 'V'}, + {ZAM_OC_BRANCH, 'b'}, + {ZAM_OC_GLOBAL, 'g'}, + {ZAM_OC_STEP_ITER, 's'}, + {ZAM_OC_TBL_ITER, 'f'}, }; -void ZAM_OpTemplate::InstantiateOp(const vector& ot, bool do_vec) +void ZAM_OpTemplate::InstantiateOp(const OCVec& oc, bool do_vec) { - auto method = MethodName(ot); + auto method = MethodName(oc); - InstantiateOp(method, ot, ZIC_REGULAR); + InstantiateOp(method, oc, ZIC_REGULAR); if ( IncludesFieldOp() ) - InstantiateOp(method, ot, ZIC_FIELD); + InstantiateOp(method, oc, ZIC_FIELD); if ( do_vec ) - InstantiateOp(method, ot, ZIC_VEC); + InstantiateOp(method, oc, ZIC_VEC); if ( IsConditionalOp() ) - InstantiateOp(method, ot, ZIC_COND); + InstantiateOp(method, oc, ZIC_COND); } -void ZAM_OpTemplate::InstantiateOp(const string& method, const vector& ot, +void ZAM_OpTemplate::InstantiateOp(const string& orig_method, const OCVec& oc_orig, ZAM_InstClass zc) { + auto oc = oc_orig; string suffix = ""; if ( zc == ZIC_FIELD ) - suffix = "_field"; - else if ( zc == ZIC_VEC ) - suffix = "_vec"; + { + // Make room for the offset. + oc.push_back(ZAM_OC_INT); + suffix = NoEval() ? "" : "_field"; + } + else if ( zc == ZIC_COND ) + { + // Remove the assignment and add in the branch. + oc.erase(oc.begin()); + oc.push_back(ZAM_OC_BRANCH); suffix = "_cond"; + } + + else if ( zc == ZIC_VEC ) + { + // Don't generate versions of these for constant operands + // as those don't exist. + if ( int(oc.size()) != Arity() + 1 ) + Gripe("vector class/arity mismatch"); + + if ( oc[1] == ZAM_OC_CONSTANT ) + return; + if ( Arity() > 1 && oc[2] == ZAM_OC_CONSTANT ) + return; + + suffix = "_vec"; + } + + auto method = MethodName(oc); if ( ! IsInternalOp() ) - InstantiateMethod(method, suffix, ot, zc); + InstantiateMethod(method, suffix, oc, zc); if ( IsAssignOp() ) - InstantiateAssignOp(ot, suffix); + InstantiateAssignOp(oc, suffix); else { - InstantiateEval(ot, suffix, zc); + InstantiateEval(oc, suffix, zc); if ( HasAssignmentLess() ) { - auto op_string = "_" + OpSuffix(ot); + auto op_string = "_" + OpSuffix(oc); auto op = g->GenOpCode(this, op_string); GenAssignmentlessVersion(op); } @@ -509,21 +687,16 @@ void ZAM_OpTemplate::GenAssignmentlessVersion(const string& op) { EmitTo(AssignFlavor); Emit("assignmentless_op[" + op + "] = " + AssignmentLessOp() + ";"); - Emit("assignmentless_op_type[" + op + "] = " + AssignmentLessOpType() + ";"); + Emit("assignmentless_op_class[" + op + "] = " + AssignmentLessOpClass() + ";"); } void ZAM_OpTemplate::InstantiateMethod(const string& m, const string& suffix, - const vector& ot_orig, ZAM_InstClass zc) + const OCVec& oc, ZAM_InstClass zc) { if ( IsInternalOp() ) return; - auto ot = ot_orig; - if ( zc == ZIC_FIELD ) - // Need to make room for the field offset. - ot.emplace_back(ZAM_OT_INT); - - auto decls = MethodDeclare(ot, zc); + auto decls = MethodDeclare(oc, zc); EmitTo(MethodDecl); Emit("const ZAMStmt " + m + suffix + "(" + decls + ");"); @@ -532,7 +705,7 @@ void ZAM_OpTemplate::InstantiateMethod(const string& m, const string& suffix, Emit("const ZAMStmt ZAMCompiler::" + m + suffix + "(" + decls + ")"); BeginBlock(); - InstantiateMethodCore(ot, suffix, zc); + InstantiateMethodCore(oc, suffix, zc); if ( HasPostMethod() ) Emit(GetPostMethod()); @@ -544,7 +717,7 @@ void ZAM_OpTemplate::InstantiateMethod(const string& m, const string& suffix, NL(); } -void ZAM_OpTemplate::InstantiateMethodCore(const vector& ot, const string& suffix, +void ZAM_OpTemplate::InstantiateMethodCore(const OCVec& oc, const string& suffix, ZAM_InstClass zc) { if ( HasCustomMethod() ) @@ -553,75 +726,241 @@ void ZAM_OpTemplate::InstantiateMethodCore(const vector& ot, co return; } - assert(! ot.empty()); + assert(! oc.empty()); - string full_suffix = "_" + OpSuffix(ot) + suffix; + string full_suffix = "_" + OpSuffix(oc) + suffix; Emit("ZInstI z;"); - if ( ot[0] == ZAM_OT_AUX ) + if ( oc[0] == ZAM_OC_AUX ) { auto op = g->GenOpCode(this, full_suffix, zc); Emit("z = ZInstI(" + op + ");"); return; } - if ( ot[0] == ZAM_OT_NONE ) + if ( oc[0] == ZAM_OC_NONE ) { auto op = g->GenOpCode(this, full_suffix, zc); Emit("z = GenInst(" + op + ");"); return; } - if ( ot.size() > 1 && ot[1] == ZAM_OT_AUX ) + if ( oc.size() > 1 && oc[1] == ZAM_OC_AUX ) { auto op = g->GenOpCode(this, full_suffix, zc); Emit("z = ZInstI(" + op + ", Frame1Slot(n, " + op + "));"); return; } - ArgsManager args(ot, zc); - BuildInstruction(ot, args.Params(), full_suffix, zc); + ArgsManager args(oc, zc); + BuildInstruction(oc, args.Params(), full_suffix, zc); - auto tp = GetTypeParam(); - if ( tp > 0 ) - Emit("z.SetType(" + args.NthParam(tp - 1) + "->GetType());"); + auto& tp = GetTypeParam(); + if ( tp ) + Emit("z.SetType(" + args.NthParam(*tp) + "->GetType());"); - auto tp2 = GetType2Param(); - if ( tp2 > 0 ) - Emit("z.t2 = " + args.NthParam(tp2 - 1) + "->GetType();"); + auto& tp2 = GetType2Param(); + if ( tp2 ) + Emit("z.SetType2(" + args.NthParam(*tp2) + "->GetType());"); } -void ZAM_OpTemplate::BuildInstruction(const vector& ot, const string& params, +void ZAM_OpTemplate::BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) { auto op = g->GenOpCode(this, suffix, zc); Emit("z = GenInst(" + op + ", " + params + ");"); } -void ZAM_OpTemplate::InstantiateEval(const vector& ot, const string& suffix, - ZAM_InstClass zc) +static bool skippable_ot(ZAM_OperandClass oc) { - auto eval = GetEval(); - - if ( ot.size() > 1 ) - { // Check for use of "$1" to indicate the operand - string op1; - if ( ot[1] == ZAM_OT_CONSTANT ) - op1 = "z.c"; - else if ( ot[1] == ZAM_OT_VAR ) - op1 = "frame[z.v2]"; - - eval = regex_replace(eval, regex("\\$1"), op1); - } - - InstantiateEval(Eval, OpSuffix(ot) + suffix, eval, zc); + return oc == ZAM_OC_EVENT_HANDLER || oc == ZAM_OC_AUX || oc == ZAM_OC_LIST; } -void ZAM_OpTemplate::InstantiateEval(EmitTarget et, const string& op_suffix, const string& eval, +string ZAM_OpTemplate::ExpandParams(const OCVec& oc, string eval, const vector& accessors) const + { + auto have_target = eval.find("$$") != string::npos; + + auto fl = GetOp1Flavor(); + auto need_target = fl == "OP1_WRITE"; + + auto oc_size = oc.size(); + if ( oc_size > 0 ) + { + auto oc0 = oc[0]; + + if ( oc0 == ZAM_OC_NONE || oc0 == ZAM_OC_AUX ) + { + --oc_size; + need_target = false; + } + + else if ( raw_int_oc.count(oc0) > 0 ) + need_target = false; + } + + while ( oc_size > 0 && skippable_ot(oc[oc_size - 1]) ) + --oc_size; + + auto max_param = oc_size; + + if ( need_target && ! have_target ) + Gripe("eval missing $$:", eval); + + if ( have_target ) + { + assert(max_param > 0); + --max_param; + } + + bool has_d1 = eval.find("$1") != string::npos; + bool has_d2 = eval.find("$2") != string::npos; + bool has_d3 = eval.find("$3") != string::npos; + bool has_d4 = eval.find("$4") != string::npos; + + switch ( max_param ) { + case 4: if ( ! has_d4 ) Gripe("eval missing $4", eval); + case 3: if ( ! has_d3 ) Gripe("eval missing $3", eval); + case 2: if ( ! has_d2 ) Gripe("eval missing $2", eval); + case 1: if ( ! has_d1 ) Gripe("eval missing $1", eval); + + case 0: + break; + + default: + Gripe("unexpected param size", to_string(max_param) + " - " + eval); + break; + } + + switch ( max_param ) { + case 0: if ( has_d1 ) Gripe("extraneous $1 in eval", eval); + case 1: if ( has_d2 ) Gripe("extraneous $2 in eval", eval); + case 2: if ( has_d3 ) Gripe("extraneous $3 in eval", eval); + case 3: if ( has_d4 ) Gripe("extraneous $4 in eval", eval); + + case 4: + break; + + default: + Gripe("unexpected param size", to_string(max_param) + " - " + eval); + break; + } + + int frame_slot = 0; + bool const_seen = false; + bool int_seen = false; + + for ( size_t i = 0; i < oc_size; ++i ) + { + string op; + bool needs_accessor = true; + + switch ( oc[i] ) { + case ZAM_OC_VAR: + if ( int_seen ) + Gripe("'V' type specifier after 'i' specifier", eval); + op = "frame[z.v" + to_string(++frame_slot) + "]"; + break; + + case ZAM_OC_RECORD_FIELD: + op = "frame[z.v" + to_string(++frame_slot) + "]"; + break; + + case ZAM_OC_INT: + case ZAM_OC_BRANCH: + case ZAM_OC_GLOBAL: + case ZAM_OC_STEP_ITER: + case ZAM_OC_TBL_ITER: + op = "z.v" + to_string(++frame_slot); + int_seen = true; + needs_accessor = false; + + if ( oc[i] == ZAM_OC_BRANCH ) + op = "Branch(" + op + ")"; + else if ( oc[i] == ZAM_OC_STEP_ITER ) + op = "StepIter(" + op + ")"; + else if ( oc[i] == ZAM_OC_TBL_ITER ) + op = "TableIter(" + op + ")"; + break; + + case ZAM_OC_CONSTANT: + if ( const_seen ) + g->Gripe("double constant", eval.c_str()); + const_seen = true; + op = "z.c"; + break; + + default: + Gripe("unexpected oc type", eval); + break; + } + + if ( needs_accessor ) + { + if ( ! accessors.empty() && ! accessors[i].empty() ) + op += "." + accessors[i]; + else if ( ! op_types.empty() && op_types[i] != ZAM_TYPE_NONE ) + op += "." + find_type_accessor(op_types[i], have_target && i == 0); + } + + else if ( ! op_types.empty() && oc[i] == ZAM_OC_INT ) + { + if ( op_types[i] == ZAM_TYPE_UINT ) + op = "zeek_uint_t(" + op + ")"; + } + + string pat; + if ( i == 0 && have_target ) + pat = "\\$\\$"; + else + pat = "\\$" + to_string(have_target ? i : i + 1); + + auto orig_eval = eval; + eval = regex_replace(eval, regex(pat), op); + if ( orig_eval == eval ) + Gripe("no eval sub", pat + " - " + eval); + } + + return eval; + } + +void ZAM_OpTemplate::InstantiateEval(const OCVec& oc, const string& suffix, ZAM_InstClass zc) { - auto op_code = g->GenOpCode(this, "_" + op_suffix, zc); + if ( NoEval() ) + return; + + auto eval = ExpandParams(oc, GetEval(), accessors); + + GenEval(Eval, OpSuffix(oc), suffix, eval, zc); + } + +void ZAM_OpTemplate::GenEval(EmitTarget et, const string& oc_str, const string& op_suffix, const string& eval, ZAM_InstClass zc) + { + auto op_code = g->GenOpCode(this, "_" + oc_str + op_suffix, zc); + + if ( et == Eval ) + { + auto oc_str_copy = oc_str; + if ( zc == ZIC_COND ) + { + auto n = oc_str_copy.size(); + + if ( oc_str_copy[n-1] == 'V' ) + oc_str_copy[n-1] = 'i'; + + else if ( oc_str_copy[n-1] == 'C' ) + { + if ( oc_str_copy[n-2] != 'V' ) + Gripe("bad operator class"); + + oc_str_copy[n-2] = 'C'; + oc_str_copy[n-1] = 'i'; + } + } + + GenDesc(op_code, oc_str_copy, eval); + } EmitTo(et); Emit("case " + op_code + ":"); @@ -632,11 +971,52 @@ void ZAM_OpTemplate::InstantiateEval(EmitTarget et, const string& op_suffix, con NL(); } -void ZAM_OpTemplate::InstantiateAssignOp(const vector& ot, const string& suffix) +void ZAM_OpTemplate::GenDesc(const string& op_code, const string& oc_str, const string& eval) + { + StartDesc(op_code, oc_str); + Emit(eval); + EndDesc(); + } + +void ZAM_OpTemplate::StartDesc(const string& op_code, const string& oc_str) + { + EmitTo(Desc); + Emit("{ " + op_code + ","); + BeginBlock(); + Emit("\"" + oc_str + "\","); + + if ( op_types.empty() ) + Emit("\"\","); + else + { + string ots; + for ( auto ot : op_types ) + { + if ( ot == ZAM_TYPE_DEFAULT ) + ots += "X"; + else + ots += expr_name_types[ot]; + } + + Emit("\"" + ots + "\", "); + } + + StartString(); + } + +void ZAM_OpTemplate::EndDesc() + { + EndString(); + EndBlock(); + Emit("},"); + } + +void ZAM_OpTemplate::InstantiateAssignOp(const OCVec& oc, const string& suffix) { // First, create a generic version of the operand, which the // ZAM compiler uses to find specific-flavored versions. - auto op_string = "_" + OpSuffix(ot); + auto oc_str = OpSuffix(oc); + auto op_string = "_" + oc_str; auto generic_op = g->GenOpCode(this, op_string); auto flavor_ind = "assignment_flavor[" + generic_op + "]"; @@ -659,10 +1039,16 @@ void ZAM_OpTemplate::InstantiateAssignOp(const vector& ot, cons GenAssignmentlessVersion(op); } + StartDesc(op, oc_str); + GenAssignOpCore(oc, eval, ti.accessor, ti.is_managed); + if ( ! post_eval.empty() ) + Emit(post_eval); + EndDesc(); + EmitTo(Eval); Emit("case " + op + ":"); BeginBlock(); - GenAssignOpCore(ot, eval, ti.accessor, ti.is_managed); + GenAssignOpCore(oc, eval, ti.accessor, ti.is_managed); if ( ! post_eval.empty() ) Emit(post_eval); Emit("break;"); @@ -672,21 +1058,21 @@ void ZAM_OpTemplate::InstantiateAssignOp(const vector& ot, cons post_eval.clear(); } -void ZAM_OpTemplate::GenAssignOpCore(const vector& ot, const string& eval, +void ZAM_OpTemplate::GenAssignOpCore(const OCVec& oc, const string& eval, const string& accessor, bool is_managed) { if ( HasAssignVal() ) { - GenAssignOpValCore(eval, accessor, is_managed); + GenAssignOpValCore(oc, eval, accessor, is_managed); return; } if ( ! eval.empty() ) g->Gripe("assign-op should not have an \"eval\"", eval); - auto lhs_field = (ot[0] == ZAM_OT_ASSIGN_FIELD); - auto rhs_field = lhs_field && ot.size() > 2 && (ot[2] == ZAM_OT_INT); - auto constant_op = (ot[1] == ZAM_OT_CONSTANT); + auto lhs_field = (oc[0] == ZAM_OC_ASSIGN_FIELD); + auto rhs_field = lhs_field && oc.size() > 3 && (oc[3] == ZAM_OC_INT); + auto constant_op = (oc[1] == ZAM_OC_CONSTANT); string rhs = constant_op ? "z.c" : "frame[z.v2]"; @@ -700,12 +1086,12 @@ void ZAM_OpTemplate::GenAssignOpCore(const vector& ot, const st // through a CoerceToAny operation, we allow expressing // these directly. They don't fit with the usual assignment // paradigm since the RHS differs in type from the LHS. - Emit("auto v = z.c.ToVal(z.t);"); + Emit("auto v = z.c.ToVal(Z_TYPE);"); if ( lhs_field ) { Emit("auto r = frame[z.v1].AsRecord();"); - Emit("auto& f = r->RawField(z.v2);"); + Emit("auto& f = DirectField(r, z.v2);"); } else Emit("auto& f = frame[z.v1];"); @@ -724,18 +1110,19 @@ void ZAM_OpTemplate::GenAssignOpCore(const vector& ot, const st auto lhs_offset = constant_op ? 3 : 4; auto rhs_offset = lhs_offset - 1; - Emit("auto v = " + rhs + ".AsRecord()->RawOptField(z.v" + to_string(rhs_offset) + - "); // note, RHS field before LHS field"); + Emit("auto v = DirectOptField(" + rhs + ".AsRecord(), z.v" + to_string(rhs_offset) + + "); // note, RHS field before LHS field\n"); Emit("if ( ! v )"); BeginBlock(); - Emit("ZAM_run_time_error(z.loc, \"field value missing\");"); - Emit("break;"); + Emit("ZAM_run_time_error(Z_LOC, \"field value missing\");"); EndBlock(); + Emit("else"); + BeginBlock(); auto slot = "z.v" + to_string(lhs_offset); Emit("auto r = frame[z.v1].AsRecord();"); - Emit("auto& f = r->RawField(" + slot + "); // note, LHS field after RHS field"); + Emit("auto& f = DirectField(r, " + slot + "); // note, LHS field after RHS field\n"); if ( is_managed ) { @@ -744,6 +1131,11 @@ void ZAM_OpTemplate::GenAssignOpCore(const vector& ot, const st } Emit("f = *v;"); + + if ( lhs_field ) + Emit("r->Modified();"); + + EndBlock(); } else @@ -756,12 +1148,13 @@ void ZAM_OpTemplate::GenAssignOpCore(const vector& ot, const st auto lhs_offset = constant_op ? 2 : 3; auto slot = "z.v" + to_string(lhs_offset); Emit("auto r = frame[z.v1].AsRecord();"); - Emit("auto& f = r->RawField(" + slot + ");"); + Emit("auto& f = DirectField(r, " + slot + ");"); if ( is_managed ) Emit("zeek::Unref(f.ManagedVal());"); Emit("f = " + rhs + ";"); + Emit("r->Modified();"); } else @@ -772,17 +1165,12 @@ void ZAM_OpTemplate::GenAssignOpCore(const vector& ot, const st Emit("frame[z.v1] = ZVal(" + rhs + acc + ");"); } } - - if ( lhs_field ) - Emit("r->Modified();"); } -void ZAM_OpTemplate::GenAssignOpValCore(const string& eval, const string& accessor, bool is_managed) +void ZAM_OpTemplate::GenAssignOpValCore(const OCVec& oc, const string& orig_eval, const string& accessor, bool is_managed) { auto v = GetAssignVal(); - Emit(eval); - // Maps Zeek types to how to get the underlying value from a ValPtr. static unordered_map val_accessors = { {"Addr", "->AsAddrVal()"}, {"Any", ".get()"}, @@ -803,33 +1191,37 @@ void ZAM_OpTemplate::GenAssignOpValCore(const string& eval, const string& access else rhs = v + ".As" + accessor + "()"; + auto eval = orig_eval; + if ( is_managed ) { - Emit("auto rhs = " + rhs + ";"); - Emit("zeek::Ref(rhs);"); - Emit("Unref(frame[z.v1].ManagedVal());"); - Emit("frame[z.v1] = ZVal(rhs);"); + eval += string("auto rhs = ") + rhs + ";\n"; + eval += "zeek::Ref(rhs);\n"; + eval += "Unref($$.ManagedVal());\n"; + eval += "$$ = ZVal(rhs);\n"; } else - Emit("frame[z.v1] = ZVal(" + rhs + ");"); + eval += "$$ = ZVal(" + rhs + ");\n"; + + Emit(ExpandParams(oc, eval)); } -string ZAM_OpTemplate::MethodName(const vector& ot) const +string ZAM_OpTemplate::MethodName(const OCVec& oc) const { - return base_name + OpSuffix(ot); + return base_name + OpSuffix(oc); } -string ZAM_OpTemplate::MethodDeclare(const vector& ot, ZAM_InstClass zc) +string ZAM_OpTemplate::MethodDeclare(const OCVec& oc, ZAM_InstClass zc) { - ArgsManager args(ot, zc); + ArgsManager args(oc, zc); return args.Decls(); } -string ZAM_OpTemplate::OpSuffix(const vector& ot) const +string ZAM_OpTemplate::OpSuffix(const OCVec& oc) const { string os; - for ( auto& o : ot ) - os += ot_to_char[o]; + for ( auto& o : oc ) + os += oc_to_char[o]; return os; } @@ -864,6 +1256,27 @@ void ZAM_OpTemplate::IndentDown() g->IndentDown(); } +void ZAM_OpTemplate::StartString() + { + g->StartString(); + } + +void ZAM_OpTemplate::EndString() + { + g->EndString(); + } + +void ZAM_OpTemplate::Gripe(const char* msg) const + { + g->Gripe(msg, op_loc); + } + +void ZAM_OpTemplate::Gripe(string msg, string addl) const + { + auto full_msg = msg + ": " + addl; + Gripe(full_msg.c_str()); + } + void ZAM_UnaryOpTemplate::Instantiate() { UnaryInstantiate(); @@ -875,19 +1288,6 @@ void ZAM_DirectUnaryOpTemplate::Instantiate() Emit("case EXPR_" + cname + ":\treturn " + direct + "(lhs, rhs);"); } -// Maps op-type mnemonics to the corresponding internal value used by Gen-ZAM. -static unordered_map expr_type_names = { - {'*', ZAM_EXPR_TYPE_DEFAULT}, {'A', ZAM_EXPR_TYPE_ADDR}, {'a', ZAM_EXPR_TYPE_ANY}, - {'D', ZAM_EXPR_TYPE_DOUBLE}, {'f', ZAM_EXPR_TYPE_FILE}, {'F', ZAM_EXPR_TYPE_FUNC}, - {'I', ZAM_EXPR_TYPE_INT}, {'L', ZAM_EXPR_TYPE_LIST}, {'X', ZAM_EXPR_TYPE_NONE}, - {'O', ZAM_EXPR_TYPE_OPAQUE}, {'P', ZAM_EXPR_TYPE_PATTERN}, {'R', ZAM_EXPR_TYPE_RECORD}, - {'S', ZAM_EXPR_TYPE_STRING}, {'N', ZAM_EXPR_TYPE_SUBNET}, {'T', ZAM_EXPR_TYPE_TABLE}, - {'t', ZAM_EXPR_TYPE_TYPE}, {'U', ZAM_EXPR_TYPE_UINT}, {'V', ZAM_EXPR_TYPE_VECTOR}, -}; - -// Inverse of the above. -static unordered_map expr_name_types; - ZAM_ExprOpTemplate::ZAM_ExprOpTemplate(ZAMGen* _g, string _base_name) : ZAM_OpTemplate(_g, std::move(_base_name)) { @@ -895,7 +1295,7 @@ ZAM_ExprOpTemplate::ZAM_ExprOpTemplate(ZAMGen* _g, string _base_name) if ( ! did_map_init ) { // Create the inverse mapping. - for ( auto& tn : expr_type_names ) + for ( auto& tn : type_names ) expr_name_types[tn.second] = tn.first; did_map_init = true; @@ -916,10 +1316,10 @@ void ZAM_ExprOpTemplate::Parse(const string& attr, const string& line, const Wor g->Gripe("bad op-type argument", w_i); auto et_c = w_i.c_str()[0]; - if ( expr_type_names.count(et_c) == 0 ) + if ( type_names.count(et_c) == 0 ) g->Gripe("bad op-type argument", w_i); - AddExprType(expr_type_names[et_c]); + AddExprType(type_names[et_c]); } } @@ -941,17 +1341,17 @@ void ZAM_ExprOpTemplate::Parse(const string& attr, const string& line, const Wor g->Gripe("bad eval-type type", type); auto type_c = type.c_str()[0]; - if ( expr_type_names.count(type_c) == 0 ) + if ( type_names.count(type_c) == 0 ) g->Gripe("bad eval-type type", type); - auto et = expr_type_names[type_c]; + auto zt = type_names[type_c]; - if ( expr_types.count(et) == 0 ) + if ( expr_types.count(zt) == 0 ) g->Gripe("eval-type type not present in eval-type", type); auto eval = g->SkipWords(line, 2); eval += GatherEval(); - AddEvalSet(et, eval); + AddEvalSet(zt, eval); } else if ( attr == "eval-mixed" ) @@ -966,11 +1366,11 @@ void ZAM_ExprOpTemplate::Parse(const string& attr, const string& line, const Wor auto type_c1 = type1.c_str()[0]; auto type_c2 = type2.c_str()[0]; - if ( expr_type_names.count(type_c1) == 0 || expr_type_names.count(type_c2) == 0 ) + if ( type_names.count(type_c1) == 0 || type_names.count(type_c2) == 0 ) g->Gripe("bad eval-mixed types", line); - auto et1 = expr_type_names[type_c1]; - auto et2 = expr_type_names[type_c2]; + auto et1 = type_names[type_c1]; + auto et2 = type_names[type_c2]; if ( eval_set.count(et1) > 0 ) g->Gripe("eval-mixed uses type also included in op-type", line); @@ -980,15 +1380,35 @@ void ZAM_ExprOpTemplate::Parse(const string& attr, const string& line, const Wor AddEvalSet(et1, et2, eval); } - else if ( attr == "eval-pre" ) + else if ( attr == "precheck" ) { if ( words.size() < 2 ) - g->Gripe("eval-pre needs evaluation", line); + g->Gripe("precheck needs evaluation", line); auto eval = g->SkipWords(line, 1); eval += GatherEval(); + eval.pop_back(); - SetPreEval(eval); + SetPreCheck(eval); + } + + else if ( attr == "precheck-action" ) + { + if ( words.size() < 2 ) + g->Gripe("precheck-action needs evaluation", line); + + auto eval = g->SkipWords(line, 1); + eval += GatherEval(); + eval.pop_back(); + + SetPreCheckAction(eval); + } + + else if ( attr == "explicit-result-type" ) + { + if ( words.size() != 1 ) + g->Gripe("extraneous argument to explicit-result-type", line); + SetHasExplicitResultType(); } else @@ -998,70 +1418,70 @@ void ZAM_ExprOpTemplate::Parse(const string& attr, const string& line, const Wor void ZAM_ExprOpTemplate::Instantiate() { - InstantiateOp(OperandTypes(), IncludesVectorOp()); + if ( ! op_classes_vec.empty() ) + Gripe("expressions cannot use \"classes\""); - if ( op_types.size() > 1 && op_types[1] == ZAM_OT_CONSTANT ) - InstantiateC1(op_types, op_types.size() - 1); - if ( op_types.size() > 2 && op_types[2] == ZAM_OT_CONSTANT ) - InstantiateC2(op_types, op_types.size() - 1); - if ( op_types.size() > 3 && op_types[3] == ZAM_OT_CONSTANT ) - InstantiateC3(op_types); + InstantiateOp(OperandClasses(), IncludesVectorOp()); + + if ( op_classes.size() > 1 && op_classes[1] == ZAM_OC_CONSTANT ) + InstantiateC1(op_classes, op_classes.size() - 1); + if ( op_classes.size() > 2 && op_classes[2] == ZAM_OC_CONSTANT ) + InstantiateC2(op_classes, op_classes.size() - 1); + if ( op_classes.size() > 3 && op_classes[3] == ZAM_OC_CONSTANT ) + InstantiateC3(op_classes); bool all_var = true; - for ( auto i = 1U; i < op_types.size(); ++i ) - if ( op_types[i] != ZAM_OT_VAR ) + for ( auto i = 1U; i < op_classes.size(); ++i ) + if ( op_classes[i] != ZAM_OC_VAR ) all_var = false; if ( all_var ) - InstantiateV(op_types); + InstantiateV(op_classes); - if ( op_types.size() == 3 && op_types[1] == ZAM_OT_RECORD_FIELD && op_types[2] == ZAM_OT_INT ) - InstantiateV(op_types); + if ( op_classes.size() == 3 && op_classes[1] == ZAM_OC_RECORD_FIELD && op_classes[2] == ZAM_OC_INT ) + InstantiateV(op_classes); } -void ZAM_ExprOpTemplate::InstantiateC1(const vector& ots, size_t arity, bool do_vec) +void ZAM_ExprOpTemplate::InstantiateC1(const OCVec& ocs, size_t arity) { string args = "lhs, r1->AsConstExpr()"; - if ( arity == 1 && ots[0] == ZAM_OT_RECORD_FIELD ) + if ( arity == 1 && ocs[0] == ZAM_OC_RECORD_FIELD ) args += ", rhs->AsFieldExpr()->Field()"; else if ( arity > 1 ) { args += ", "; - if ( ots[2] == ZAM_OT_RECORD_FIELD ) + if ( ocs[2] == ZAM_OC_RECORD_FIELD ) args += "rhs->AsFieldExpr()->Field()"; else args += "r2->AsNameExpr()"; } - auto m = MethodName(ots); + auto m = MethodName(ocs); EmitTo(C1Def); EmitNoNL("case EXPR_" + cname + ":"); - if ( do_vec ) - DoVectorCase(m, args); - else - EmitUp("return " + m + "(" + args + ");"); + EmitUp("return " + m + "(" + args + ");"); if ( IncludesFieldOp() ) { EmitTo(C1FieldDef); - Emit("case EXPR_" + cname + ":\treturn " + m + "_field(" + args + ", field);"); + Emit("case EXPR_" + cname + ":\treturn " + m + "i_field(" + args + ", field);"); } } -void ZAM_ExprOpTemplate::InstantiateC2(const vector& ots, size_t arity) +void ZAM_ExprOpTemplate::InstantiateC2(const OCVec& ocs, size_t arity) { string args = "lhs, r1->AsNameExpr(), r2->AsConstExpr()"; if ( arity == 3 ) args += ", r3->AsNameExpr()"; - auto method = MethodName(ots); + auto method = MethodName(ocs); auto m = method.c_str(); EmitTo(C2Def); @@ -1070,26 +1490,26 @@ void ZAM_ExprOpTemplate::InstantiateC2(const vector& ots, size_ if ( IncludesFieldOp() ) { EmitTo(C2FieldDef); - Emit("case EXPR_" + cname + ":\treturn " + m + "_field(" + args + ", field);"); + Emit("case EXPR_" + cname + ":\treturn " + m + "i_field(" + args + ", field);"); } } -void ZAM_ExprOpTemplate::InstantiateC3(const vector& ots) +void ZAM_ExprOpTemplate::InstantiateC3(const OCVec& ocs) { EmitTo(C3Def); - Emit("case EXPR_" + cname + ":\treturn " + MethodName(ots) + + Emit("case EXPR_" + cname + ":\treturn " + MethodName(ocs) + "(lhs, r1->AsNameExpr(), r2->AsNameExpr(), r3->AsConstExpr());"); } -void ZAM_ExprOpTemplate::InstantiateV(const vector& ots) +void ZAM_ExprOpTemplate::InstantiateV(const OCVec& ocs) { - auto m = MethodName(ots); + auto m = MethodName(ocs); string args = "lhs, r1->AsNameExpr()"; - if ( ots.size() >= 3 ) + if ( ocs.size() >= 3 ) { - if ( ots[2] == ZAM_OT_INT ) + if ( ocs[2] == ZAM_OC_INT ) { string acc_flav = IncludesFieldOp() ? "Has" : ""; args += ", rhs->As" + acc_flav + "FieldExpr()->Field()"; @@ -1097,7 +1517,7 @@ void ZAM_ExprOpTemplate::InstantiateV(const vector& ots) else args += ", r2->AsNameExpr()"; - if ( ots.size() == 4 ) + if ( ocs.size() == 4 ) args += ", r3->AsNameExpr()"; } @@ -1111,8 +1531,9 @@ void ZAM_ExprOpTemplate::InstantiateV(const vector& ots) if ( IncludesFieldOp() ) { + string suffix = NoEval() ? "" : "_field"; EmitTo(VFieldDef); - Emit("case EXPR_" + cname + ":\treturn " + m + "_field(" + args + ", field);"); + Emit("case EXPR_" + cname + ":\treturn " + m + "i" + suffix + "(" + args + ", field);"); } } @@ -1135,18 +1556,21 @@ void ZAM_ExprOpTemplate::BuildInstructionCore(const string& params, const string int ncases = 0; - for ( auto& [et1, et2_map] : eval_mixed_set ) - for ( auto& [et2, eval] : et2_map ) - GenMethodTest(et1, et2, params, suffix, ++ncases > 1, zc); + if ( zc != ZIC_VEC ) + for ( auto& [et1, et2_map] : eval_mixed_set ) + for ( auto& [et2, eval] : et2_map ) + GenMethodTest(et1, et2, params, suffix, ++ncases > 1, zc); bool do_default = false; - for ( auto et : ExprTypes() ) + for ( auto zt : ExprTypes() ) { - if ( et == ZAM_EXPR_TYPE_DEFAULT ) + if ( zt == ZAM_TYPE_DEFAULT ) do_default = true; + else if ( zt == ZAM_TYPE_NONE ) + continue; else - GenMethodTest(et, et, params, suffix, ++ncases > 1, zc); + GenMethodTest(zt, zt, params, suffix, ++ncases > 1, zc); } Emit("else"); @@ -1161,33 +1585,33 @@ void ZAM_ExprOpTemplate::BuildInstructionCore(const string& params, const string EmitUp("reporter->InternalError(\"bad tag when generating method core\");"); } -void ZAM_ExprOpTemplate::GenMethodTest(ZAM_ExprType et1, ZAM_ExprType et2, const string& params, +void ZAM_ExprOpTemplate::GenMethodTest(ZAM_Type et1, ZAM_Type et2, const string& params, const string& suffix, bool do_else, ZAM_InstClass zc) { - // Maps ZAM_ExprType's to the information needed (variable name, + // Maps ZAM_Type's to the information needed (variable name, // constant to compare it against) to identify using an "if" test // that a given AST Expr node employs the given type of operand. - static map> if_tests = { - {ZAM_EXPR_TYPE_ADDR, {"i_t", "TYPE_INTERNAL_ADDR"}}, - {ZAM_EXPR_TYPE_ANY, {"tag", "TYPE_ANY"}}, - {ZAM_EXPR_TYPE_DOUBLE, {"i_t", "TYPE_INTERNAL_DOUBLE"}}, - {ZAM_EXPR_TYPE_FILE, {"tag", "TYPE_FILE"}}, - {ZAM_EXPR_TYPE_FUNC, {"tag", "TYPE_FUNC"}}, - {ZAM_EXPR_TYPE_INT, {"i_t", "TYPE_INTERNAL_INT"}}, - {ZAM_EXPR_TYPE_LIST, {"tag", "TYPE_LIST"}}, - {ZAM_EXPR_TYPE_OPAQUE, {"tag", "TYPE_OPAQUE"}}, - {ZAM_EXPR_TYPE_PATTERN, {"tag", "TYPE_PATTERN"}}, - {ZAM_EXPR_TYPE_RECORD, {"tag", "TYPE_RECORD"}}, - {ZAM_EXPR_TYPE_STRING, {"i_t", "TYPE_INTERNAL_STRING"}}, - {ZAM_EXPR_TYPE_SUBNET, {"i_t", "TYPE_INTERNAL_SUBNET"}}, - {ZAM_EXPR_TYPE_TABLE, {"tag", "TYPE_TABLE"}}, - {ZAM_EXPR_TYPE_TYPE, {"tag", "TYPE_TYPE"}}, - {ZAM_EXPR_TYPE_UINT, {"i_t", "TYPE_INTERNAL_UNSIGNED"}}, - {ZAM_EXPR_TYPE_VECTOR, {"tag", "TYPE_VECTOR"}}, + static map> if_tests = { + {ZAM_TYPE_ADDR, {"i_t", "TYPE_INTERNAL_ADDR"}}, + {ZAM_TYPE_ANY, {"tag", "TYPE_ANY"}}, + {ZAM_TYPE_DOUBLE, {"i_t", "TYPE_INTERNAL_DOUBLE"}}, + {ZAM_TYPE_FILE, {"tag", "TYPE_FILE"}}, + {ZAM_TYPE_FUNC, {"tag", "TYPE_FUNC"}}, + {ZAM_TYPE_INT, {"i_t", "TYPE_INTERNAL_INT"}}, + {ZAM_TYPE_LIST, {"tag", "TYPE_LIST"}}, + {ZAM_TYPE_OPAQUE, {"tag", "TYPE_OPAQUE"}}, + {ZAM_TYPE_PATTERN, {"tag", "TYPE_PATTERN"}}, + {ZAM_TYPE_RECORD, {"tag", "TYPE_RECORD"}}, + {ZAM_TYPE_STRING, {"i_t", "TYPE_INTERNAL_STRING"}}, + {ZAM_TYPE_SUBNET, {"i_t", "TYPE_INTERNAL_SUBNET"}}, + {ZAM_TYPE_TABLE, {"tag", "TYPE_TABLE"}}, + {ZAM_TYPE_TYPE, {"tag", "TYPE_TYPE"}}, + {ZAM_TYPE_UINT, {"i_t", "TYPE_INTERNAL_UNSIGNED"}}, + {ZAM_TYPE_VECTOR, {"tag", "TYPE_VECTOR"}}, }; if ( if_tests.count(et1) == 0 ) - g->Gripe("bad op-type", op_loc); + Gripe("bad op-type"); auto if_test = if_tests[et1]; auto if_var = if_test.first; @@ -1207,7 +1631,7 @@ void ZAM_ExprOpTemplate::GenMethodTest(ZAM_ExprType et1, ZAM_ExprType et2, const EmitUp("z = GenInst(" + op + ", " + params + ");"); } -EvalInstance::EvalInstance(ZAM_ExprType _lhs_et, ZAM_ExprType _op1_et, ZAM_ExprType _op2_et, +EvalInstance::EvalInstance(ZAM_Type _lhs_et, ZAM_Type _op1_et, ZAM_Type _op2_et, string _eval, bool _is_def) { lhs_et = _lhs_et; @@ -1219,27 +1643,27 @@ EvalInstance::EvalInstance(ZAM_ExprType _lhs_et, ZAM_ExprType _op1_et, ZAM_ExprT string EvalInstance::LHSAccessor(bool is_ptr) const { - if ( lhs_et == ZAM_EXPR_TYPE_NONE || lhs_et == ZAM_EXPR_TYPE_DEFAULT ) + if ( lhs_et == ZAM_TYPE_NONE || lhs_et == ZAM_TYPE_DEFAULT ) return ""; string deref = is_ptr ? "->" : "."; - string acc = find_type_accessor(lhs_et); + string acc = find_type_accessor(lhs_et, true); return deref + acc; } -string EvalInstance::Accessor(ZAM_ExprType et, bool is_ptr) const +string EvalInstance::Accessor(ZAM_Type zt, bool is_ptr) const { - if ( et == ZAM_EXPR_TYPE_NONE || et == ZAM_EXPR_TYPE_DEFAULT ) + if ( zt == ZAM_TYPE_NONE || zt == ZAM_TYPE_DEFAULT ) return ""; string deref = is_ptr ? "->" : "."; - return deref + "As" + find_type_info(et).accessor + "()"; + return deref + "As" + find_type_info(zt).accessor + "()"; } string EvalInstance::OpMarker() const { - if ( op1_et == ZAM_EXPR_TYPE_DEFAULT || op1_et == ZAM_EXPR_TYPE_NONE ) + if ( op1_et == ZAM_TYPE_DEFAULT || op1_et == ZAM_TYPE_NONE ) return ""; if ( op1_et == op2_et ) @@ -1248,21 +1672,24 @@ string EvalInstance::OpMarker() const return "_" + find_type_info(op1_et).suffix + find_type_info(op2_et).suffix; } -void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, +void ZAM_ExprOpTemplate::InstantiateEval(const OCVec& oc_orig, const string& suffix, ZAM_InstClass zc) { + if ( (HasPreCheck() || HasPreCheckAction()) && + (! HasPreCheck() || ! HasPreCheckAction()) ) + Gripe("precheck and precheck-action must be used together"); + + auto oc = oc_orig; + if ( expr_types.empty() ) - { // No operand types to expand over. - ZAM_OpTemplate::InstantiateEval(ot_orig, suffix, zc); + { + // No operand types to expand over. This happens for + // some "non-uniform" operations. + ZAM_OpTemplate::InstantiateEval(oc, suffix, zc); return; } - auto ot = ot_orig; - if ( zc == ZIC_FIELD ) - // Make room for the offset. - ot.emplace_back(ZAM_OT_INT); - - auto ot_str = OpSuffix(ot); + auto oc_str = OpSuffix(oc); // Some of these might not wind up being used, but no harm in // initializing them in case they are. @@ -1284,31 +1711,40 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, { lhs = "frame[z.v1]"; - auto op1_offset = zc == ZIC_COND ? 1 : 2; - auto op2_offset = op1_offset + 1; - bool ot1_const = ot[1] == ZAM_OT_CONSTANT; - bool ot2_const = Arity() >= 2 && ot[2] == ZAM_OT_CONSTANT; + // First compute the offsets into oc for the operands. + auto op1_offset = zc == ZIC_COND ? 0 : 1; + bool oc1_const = oc[op1_offset] == ZAM_OC_CONSTANT; + bool oc2_const = + Arity() > 1 && oc[op1_offset + 1] == ZAM_OC_CONSTANT; - if ( ot1_const ) + // Now the frame slots. + auto op1_slot = op1_offset + 1; + auto op2_slot = op1_slot + 1; + + if ( oc1_const ) { op1 = "z.c"; - --op2_offset; - branch_target += "2"; + --op2_slot; + if ( zc == ZIC_COND ) + branch_target += "2"; } else { - op1 = "frame[z.v" + to_string(op1_offset) + "]"; + op1 = "frame[z.v" + to_string(op1_slot) + "]"; - if ( Arity() > 1 && ot[2] == ZAM_OT_VAR ) - branch_target += "3"; - else - branch_target += "2"; + if ( zc == ZIC_COND ) + { + if ( Arity() > 1 && ! oc2_const ) + branch_target += "3"; + else + branch_target += "2"; + } } - if ( ot2_const ) + if ( oc2_const ) op2 = "z.c"; else - op2 = "frame[z.v" + to_string(op2_offset) + "]"; + op2 = "frame[z.v" + to_string(op2_slot) + "]"; if ( zc == ZIC_FIELD ) { @@ -1316,30 +1752,35 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, auto f = // The first slots are taken up by the - // assignment slot and the operands ... + // assignment slot and the operands ... Arity() + 1 + // ... and slots are numbered starting at 1. +1; - if ( ot1_const || ot2_const ) + if ( oc1_const || oc2_const ) // One of the operand slots won't be needed // due to the presence of a constant. // (It's never the case that both operands // are constants - those instead get folded.) --f; - lhs += ".AsRecord()->RawField(z.v" + to_string(f) + ")"; + lhs = "DirectField(" + lhs + ".AsRecord(), z.v" + to_string(f) + ")"; } } vector eval_instances; - for ( auto et : expr_types ) + for ( auto zt : expr_types ) { - auto is_def = eval_set.count(et) == 0; - string eval = is_def ? GetEval() : eval_set[et]; - auto lhs_et = IsConditionalOp() ? ZAM_EXPR_TYPE_INT : et; - eval_instances.emplace_back(lhs_et, et, et, eval, is_def); + // Support for "op-type X" meaning "allow empty evaluation", + // as well as "evaluation is generic". + if ( zt == ZAM_TYPE_NONE && GetEval().empty() ) + continue; + + auto is_def = eval_set.count(zt) == 0; + string eval = is_def ? GetEval() : eval_set[zt]; + auto lhs_et = IsConditionalOp() ? ZAM_TYPE_INT : zt; + eval_instances.emplace_back(lhs_et, zt, zt, eval, is_def); } if ( zc != ZIC_VEC ) @@ -1353,21 +1794,52 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, // For the LHS, either its expression type is // ignored, or if it's a conditional, so just // note it for the latter. - auto lhs_et = ZAM_EXPR_TYPE_INT; + auto lhs_et = ZAM_TYPE_INT; eval_instances.emplace_back(lhs_et, et1, et2, em2.second, false); } } for ( auto& ei : eval_instances ) { + op_types.clear(); + auto lhs_accessor = ei.LHSAccessor(); if ( HasExplicitResultType() ) + { + op_types.push_back(ZAM_TYPE_NONE); lhs_accessor = ""; + } + else if ( zc == ZIC_FIELD ) + op_types.push_back(ZAM_TYPE_RECORD); + else if ( zc != ZIC_COND ) + op_types.push_back(ei.LHS_ET()); string lhs_ei = lhs; if ( zc != ZIC_VEC ) lhs_ei += lhs_accessor; + op_types.push_back(ei.Op1_ET()); + if ( Arity() > 1 ) + op_types.push_back(ei.Op2_ET()); + + if ( zc == ZIC_FIELD ) + op_types.push_back(ZAM_TYPE_INT); + + else if ( zc == ZIC_COND ) + op_types.push_back(ZAM_TYPE_INT); + + else if ( zc == ZIC_VEC ) + { + // Above isn't applicable, since we use helper + // functions. + op_types.clear(); + op_types.push_back(ZAM_TYPE_VECTOR); + op_types.push_back(ZAM_TYPE_VECTOR); + + if ( Arity() > 1 ) + op_types.push_back(ZAM_TYPE_VECTOR); + } + auto op1_ei = op1 + ei.Op1Accessor(zc == ZIC_VEC); auto op2_ei = op2 + ei.Op2Accessor(zc == ZIC_VEC); @@ -1388,8 +1860,8 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, eval = regex_replace(eval, regex(rhs), replacement, std::regex_constants::match_not_null); } - auto is_none = ei.LHS_ET() == ZAM_EXPR_TYPE_NONE; - auto is_default = ei.LHS_ET() == ZAM_EXPR_TYPE_DEFAULT; + auto is_none = ei.LHS_ET() == ZAM_TYPE_NONE; + auto is_default = ei.LHS_ET() == ZAM_TYPE_DEFAULT; if ( ! is_none && ! is_default && find_type_info(ei.LHS_ET()).is_managed && ! HasExplicitResultType() ) @@ -1418,7 +1890,15 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, eval = regex_replace(eval, regex("\\$1"), op1_ei); eval = regex_replace(eval, regex("\\$2"), op2_ei); - string pre = GetPreEval(); + string pre, post; + + if ( HasPreCheck() ) + { + pre = "if ( " + GetPreCheck() + ")\n\t{\n\t" + + GetPreCheckAction() + "\n\t}\n\telse\n\t{\n\t"; + post = "\n\t}"; + } + pre = regex_replace(pre, regex("\\$1"), op1_ei); pre = regex_replace(pre, regex("\\$2"), op2_ei); @@ -1426,9 +1906,12 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, eval = regex_replace(eval, regex("\\$\\$"), lhs_ei); else if ( zc == ZIC_COND ) - { // Aesthetics: get rid of trailing newlines. + { + // Aesthetics: get rid of trailing newlines. eval = regex_replace(eval, regex("\n"), ""); - eval = "if ( ! (" + eval + ") ) " + "{ pc = " + branch_target + "; continue; }"; + + eval = "if ( ! (" + eval + ") ) " + + "Branch(" + branch_target + ")"; } else if ( ! is_none && (ei.IsDefault() || IsConditionalOp()) ) @@ -1439,11 +1922,11 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, eval = regex_replace(eval, regex(";*\n"), ";\n"); } - eval = pre + eval; + eval = pre + eval + post; - auto full_suffix = ot_str + suffix + ei.OpMarker(); + auto full_suffix = suffix + ei.OpMarker(); - ZAM_OpTemplate::InstantiateEval(emit_target, full_suffix, eval, zc); + GenEval(emit_target, oc_str, full_suffix, eval, zc); if ( zc == ZIC_VEC ) { @@ -1452,10 +1935,10 @@ void ZAM_ExprOpTemplate::InstantiateEval(const vector& ot_orig, if ( Arity() == 2 ) dispatch_params += ", frame[z.v3].AsVector()"; - auto op_code = g->GenOpCode(this, "_" + full_suffix); - auto dispatch = "vec_exec(" + op_code + ", z.t, " + dispatch_params + ", z);"; + auto op_code = g->GenOpCode(this, "_" + oc_str + full_suffix); + auto dispatch = "vec_exec(" + op_code + ", Z_TYPE, " + dispatch_params + ", z);"; - ZAM_OpTemplate::InstantiateEval(Eval, full_suffix, dispatch, zc); + GenEval(Eval, oc_str, full_suffix, dispatch, zc); } } } @@ -1470,13 +1953,6 @@ void ZAM_UnaryExprOpTemplate::Parse(const string& attr, const string& line, cons SetNoConst(); } - else if ( attr == "explicit-result-type" ) - { - if ( words.size() != 1 ) - g->Gripe("extraneous argument to explicit-result-type", line); - SetHasExplicitResultType(); - } - else ZAM_ExprOpTemplate::Parse(attr, line, words); } @@ -1485,36 +1961,35 @@ void ZAM_UnaryExprOpTemplate::Instantiate() { UnaryInstantiate(); - vector ots = {ZAM_OT_VAR, ZAM_OT_CONSTANT}; + OCVec ocs = {ZAM_OC_VAR, ZAM_OC_CONSTANT}; if ( ! NoConst() ) - InstantiateC1(ots, 1, IncludesVectorOp()); + InstantiateC1(ocs, 1); - ots[1] = ZAM_OT_VAR; - InstantiateV(ots); + ocs[1] = ZAM_OC_VAR; + InstantiateV(ocs); } -void ZAM_UnaryExprOpTemplate::BuildInstruction(const vector& ot, +void ZAM_UnaryExprOpTemplate::BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) { const auto& ets = ExprTypes(); - if ( ets.size() == 1 && ets.count(ZAM_EXPR_TYPE_NONE) == 1 ) + if ( ets.size() == 1 && ets.count(ZAM_TYPE_NONE) == 1 ) { - ZAM_ExprOpTemplate::BuildInstruction(ot, params, suffix, zc); + ZAM_ExprOpTemplate::BuildInstruction(oc, params, suffix, zc); return; } - auto constant_op = ot[1] == ZAM_OT_CONSTANT; + auto constant_op = oc[1] == ZAM_OC_CONSTANT; string type_src = constant_op ? "c" : "n2"; - if ( ot[0] == ZAM_OT_ASSIGN_FIELD ) + if ( oc[0] == ZAM_OC_ASSIGN_FIELD ) { type_src = constant_op ? "n" : "n1"; Emit("auto " + type_src + " = flhs->GetOp1()->AsNameExpr();"); Emit("auto t = flhs->GetType();"); - Emit("int field = flhs->Field();"); } else @@ -1531,14 +2006,14 @@ void ZAM_UnaryExprOpTemplate::BuildInstruction(const vector& ot if ( IsAssignOp() && IsFieldOp() ) // These can't take the type from the LHS variable, since // that's the enclosing record and not the field within it. - Emit("z.t = t;"); + Emit("z.SetType(t);"); else if ( zc == ZIC_VEC ) { if ( constant_op ) - Emit("z.t = n->GetType();"); + Emit("z.SetType(n->GetType());"); else - Emit("z.t = n1->GetType();"); + Emit("z.SetType(n1->GetType());"); } } @@ -1546,11 +2021,11 @@ ZAM_AssignOpTemplate::ZAM_AssignOpTemplate(ZAMGen* _g, string _base_name) : ZAM_UnaryExprOpTemplate(_g, std::move(_base_name)) { // Assignments apply to every valid form of ExprType. - for ( auto& etn : expr_type_names ) + for ( auto& etn : type_names ) { - auto et = etn.second; - if ( et != ZAM_EXPR_TYPE_NONE && et != ZAM_EXPR_TYPE_DEFAULT ) - AddExprType(et); + auto zt = etn.second; + if ( zt != ZAM_TYPE_NONE && zt != ZAM_TYPE_DEFAULT ) + AddExprType(zt); } } @@ -1570,44 +2045,46 @@ void ZAM_AssignOpTemplate::Parse(const string& attr, const string& line, const W void ZAM_AssignOpTemplate::Instantiate() { - if ( op_types.size() != 1 ) - g->Gripe("operation needs precisely one \"type\"", op_loc); + if ( op_classes.size() != 1 ) + Gripe("operation needs precisely one \"type\""); + if ( ! op_classes_vec.empty() ) + Gripe("operation cannot use \"classes\""); - vector ots; - ots.push_back(op_types[0]); + OCVec ocs; + ocs.push_back(op_classes[0]); // Build constant/variable versions ... - ots.push_back(ZAM_OT_CONSTANT); + ocs.push_back(ZAM_OC_CONSTANT); - if ( ots[0] == ZAM_OT_RECORD_FIELD ) - ots.push_back(ZAM_OT_INT); + if ( ocs[0] == ZAM_OC_RECORD_FIELD || ocs[0] == ZAM_OC_ASSIGN_FIELD ) + ocs.push_back(ZAM_OC_INT); - InstantiateOp(ots, false); + InstantiateOp(ocs, false); if ( IsFieldOp() ) - InstantiateC1(ots, 1); + InstantiateC1(ocs, 1); - ots[1] = ZAM_OT_VAR; - InstantiateOp(ots, false); + ocs[1] = ZAM_OC_VAR; + InstantiateOp(ocs, false); // ... and for assignments to fields, additional field versions. - if ( ots[0] == ZAM_OT_ASSIGN_FIELD ) + if ( ocs[0] == ZAM_OC_ASSIGN_FIELD ) { - ots.push_back(ZAM_OT_INT); - InstantiateOp(ots, false); + ocs.push_back(ZAM_OC_INT); + InstantiateOp(ocs, false); - ots[1] = ZAM_OT_CONSTANT; - InstantiateOp(ots, false); + ocs[1] = ZAM_OC_CONSTANT; + InstantiateOp(ocs, false); } else if ( IsFieldOp() ) - InstantiateV(ots); + InstantiateV(ocs); } void ZAM_BinaryExprOpTemplate::Instantiate() { // As usual, the first slot receives the operator's result. - vector ots = {ZAM_OT_VAR}; - ots.resize(3); + OCVec ocs = {ZAM_OC_VAR}; + ocs.resize(3); // Build each combination for constant/variable operand, // except skip constant/constant as that is always folded. @@ -1615,39 +2092,39 @@ void ZAM_BinaryExprOpTemplate::Instantiate() // We only include vector operations when both operands // are non-constants. - ots[1] = ZAM_OT_CONSTANT; - ots[2] = ZAM_OT_VAR; - InstantiateOp(ots, false); + ocs[1] = ZAM_OC_CONSTANT; + ocs[2] = ZAM_OC_VAR; + InstantiateOp(ocs, false); if ( ! IsInternalOp() ) - InstantiateC1(ots, 2, false); + InstantiateC1(ocs, 2); - ots[1] = ZAM_OT_VAR; - ots[2] = ZAM_OT_CONSTANT; - InstantiateOp(ots, false); + ocs[1] = ZAM_OC_VAR; + ocs[2] = ZAM_OC_CONSTANT; + InstantiateOp(ocs, false); if ( ! IsInternalOp() ) - InstantiateC2(ots, 2); + InstantiateC2(ocs, 2); - ots[2] = ZAM_OT_VAR; - InstantiateOp(ots, IncludesVectorOp()); + ocs[2] = ZAM_OC_VAR; + InstantiateOp(ocs, IncludesVectorOp()); if ( ! IsInternalOp() ) - InstantiateV(ots); + InstantiateV(ocs); } -void ZAM_BinaryExprOpTemplate::BuildInstruction(const vector& ot, +void ZAM_BinaryExprOpTemplate::BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) { - auto constant_op = ot[1] == ZAM_OT_CONSTANT; + auto constant_op = oc[1] == ZAM_OC_CONSTANT; string type_src = constant_op ? "c" : "n2"; auto type_suffix = zc == ZIC_VEC ? "->Yield();" : ";"; Emit("auto t = " + type_src + "->GetType()" + type_suffix); BuildInstructionCore(params, suffix, zc); if ( zc == ZIC_VEC ) - Emit("z.t = n1->GetType();"); + Emit("z.SetType(n1->GetType());"); } void ZAM_RelationalExprOpTemplate::Instantiate() @@ -1659,16 +2136,16 @@ void ZAM_RelationalExprOpTemplate::Instantiate() Emit("case EXPR_" + cname + ":"); IndentUp(); Emit("if ( n1 && n2 )"); - EmitUp("return " + cname + "VVV_cond(n1, n2);"); + EmitUp("return " + cname + "VVb_cond(n1, n2);"); Emit("else if ( n1 )"); - EmitUp("return " + cname + "VVC_cond(n1, c);"); + EmitUp("return " + cname + "VCb_cond(n1, c);"); Emit("else"); - EmitUp("return " + cname + "VCV_cond(c, n2);"); + EmitUp("return " + cname + "CVb_cond(c, n2);"); IndentDown(); NL(); } -void ZAM_RelationalExprOpTemplate::BuildInstruction(const vector& ot, +void ZAM_RelationalExprOpTemplate::BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) { @@ -1676,16 +2153,16 @@ void ZAM_RelationalExprOpTemplate::BuildInstruction(const vectorGetType();"); - } - -void ZAM_InternalBinaryOpTemplate::Parse(const string& attr, const string& line, const Words& words) - { - if ( attr == "op-accessor" ) - { - if ( words.size() != 2 ) - g->Gripe("op-accessor takes one argument", line); - - SetOpAccessor(words[1]); - } - - else if ( attr == "op1-accessor" ) - { - if ( words.size() != 2 ) - g->Gripe("op-accessor1 takes one argument", line); - - SetOp1Accessor(words[1]); - } - - else if ( attr == "op2-accessor" ) - { - if ( words.size() != 2 ) - g->Gripe("op-accessor2 takes one argument", line); - - SetOp2Accessor(words[1]); - } - - else - ZAM_BinaryExprOpTemplate::Parse(attr, line, words); - } - -void ZAM_InternalBinaryOpTemplate::InstantiateEval(const vector& ot, - const string& suffix, ZAM_InstClass zc) - { - assert(ot.size() == 3); - - auto op1_const = ot[1] == ZAM_OT_CONSTANT; - auto op2_const = ot[2] == ZAM_OT_CONSTANT; - - string op1 = op1_const ? "z.c" : "frame[z.v2]"; - string op2 = op2_const ? "z.c" : (op1_const ? "frame[z.v2]" : "frame[z.v3]"); - - string prelude = "auto op1 = " + op1 + "." + op1_accessor + ";\n"; - prelude += "auto op2 = " + op2 + "." + op2_accessor + ";\n"; - - auto eval = prelude + GetEval(); - - auto& ets = ExprTypes(); - if ( ! ets.empty() ) - { - if ( ets.size() != 1 ) - g->Gripe("internal-binary-op's can have at most one op-type", op_loc); - - for ( auto& et : ets ) - { - auto acc = find_type_accessor(et); - auto lhs = "frame[z.v1]." + acc; - eval = regex_replace(eval, regex("\\$\\$"), lhs); - } - } - - ZAM_OpTemplate::InstantiateEval(Eval, OpSuffix(ot) + suffix, eval, zc); + Emit("z.SetType(n1->GetType());"); } void ZAM_InternalOpTemplate::Parse(const string& attr, const string& line, const Words& words) { - if ( attr != "num-call-args" ) + if ( attr == "num-call-args" ) + ParseCall(line, words); + + else if ( attr == "indirect-call" || attr == "indirect-local-call" ) { - if ( attr == "indirect-call" || attr == "indirect-local-call" ) - { - if ( words.size() != 1 ) - g->Gripe("indirect-call takes one argument", line); - // Note, currently only works with a *subsequent* - // num-call-args, whose setting needs to be 'n'. - is_indirect_call = true; + if ( words.size() != 1 ) + g->Gripe("indirect-call takes one argument", line); - if ( attr == "indirect-local-call" ) - is_local_indirect_call = true; - } - else - ZAM_OpTemplate::Parse(attr, line, words); + // Note, currently only works with a *subsequent* num-call-args, + // whose setting needs to be 'n'. + is_indirect_call = true; - return; + if ( attr == "indirect-local-call" ) + is_local_indirect_call = true; } + else + ZAM_OpTemplate::Parse(attr, line, words); + } + +void ZAM_InternalOpTemplate::ParseCall(const string& line, const Words& words) + { if ( words.size() != 2 ) g->Gripe("num-call-args takes one argument", line); @@ -1794,22 +2211,14 @@ void ZAM_InternalOpTemplate::Parse(const string& attr, const string& line, const auto arg_offset = HasAssignVal() ? 1 : 0; auto arg_slot = arg_offset + 1; - string func = "z.aux->func"; + string func = "Z_AUX->func"; if ( n == 1 ) - { - eval += "args.push_back("; - if ( op_types[arg_offset] == ZAM_OT_CONSTANT ) - eval += "z.c"; - else - eval += "frame[z.v" + to_string(arg_slot) + "]"; - - eval += ".ToVal(z.t));\n"; - } + eval += "args.push_back($1.ToVal(Z_TYPE));\n"; else if ( n != 0 ) { - eval += "auto aux = z.aux;\n"; + eval += "auto aux = Z_AUX;\n"; if ( n < 0 ) { @@ -1818,11 +2227,7 @@ void ZAM_InternalOpTemplate::Parse(const string& attr, const string& line, const func = "func"; if ( is_local_indirect_call ) - { - eval += "auto sel = z.v" + - to_string(arg_slot) + ";\n"; - eval += "auto func = frame[sel].AsFunc();\n"; - } + eval += "auto func = $1.AsFunc();\n"; else { eval += "auto func_v = aux->id_val->GetVal();\n"; @@ -1831,7 +2236,7 @@ void ZAM_InternalOpTemplate::Parse(const string& attr, const string& line, const eval += "if ( ! func )\n"; eval += "\t{\n"; - eval += "\tZAM_run_time_error(z.loc, \"value used but not set\");\n"; + eval += "\tZAM_run_time_error(Z_LOC, \"value used but not set\");\n"; eval += "\tbreak;\n"; eval += "\t}\n"; } @@ -1850,7 +2255,7 @@ void ZAM_InternalOpTemplate::Parse(const string& attr, const string& line, const } } - eval += "f->SetOnlyCall(z.aux->call_expr.get());\n"; + eval += "f->SetOnlyCall(Z_AUX->call_expr.get());\n"; eval += "ZAM_PROFILE_PRE_CALL\n"; if ( HasAssignVal() ) @@ -2012,6 +2417,9 @@ void ZAMGen::ReadMacro(const string& line) break; } + if ( regex_search(s, regex("\\$[$123]")) ) + Gripe("macro has $-param", s); + mac.push_back(s); } @@ -2092,12 +2500,30 @@ void ZAMGen::Emit(EmitTarget et, const string& s) FILE* f = gen_files[et]; for ( auto i = indent_level; i > 0; --i ) - fputs("\t", f); + fputc('\t', f); - fputs(s.c_str(), f); + if ( string_lit ) + { + fputc('"', f); + for ( auto sp = s.c_str(); *sp; ++sp ) + { + if ( *sp == '\\' ) + fputs("\\\\", f); + else if ( *sp == '"' ) + fputs("\\\"", f); + else if ( *sp == '\n' ) + fputs("\\n", f); + else + fputc(*sp, f); + } + fputc('"', f); + } + + else + fputs(s.c_str(), f); if ( ! no_NL && (s.empty() || s.back() != '\n') ) - fputs("\n", f); + fputc('\n', f); } void ZAMGen::InitEmitTargets() @@ -2112,6 +2538,7 @@ void ZAMGen::InitEmitTargets() {C2FieldDef, "ZAM-GenFieldsDefsC2.h"}, {C3Def, "ZAM-GenExprsDefsC3.h"}, {Cond, "ZAM-Conds.h"}, + {Desc, "ZAM-Desc.h"}, {DirectDef, "ZAM-DirectDefs.h"}, {Eval, "ZAM-EvalDefs.h"}, {EvalMacros, "ZAM-EvalMacros.h"}, @@ -2245,10 +2672,13 @@ bool ZAMGen::ParseTemplate() t = make_unique(this, op_name); else if ( op == "rel-expr-op" ) t = make_unique(this, op_name); - else if ( op == "internal-binary-op" ) - t = make_unique(this, op_name); else if ( op == "internal-op" ) t = make_unique(this, op_name); + else if ( op == "predicate-op" ) + { + t = make_unique(this, op_name); + t->SetIsPredicate(); + } else if ( op == "internal-assignment-op" ) t = make_unique(this, op_name); diff --git a/tools/gen-zam/src/Gen-ZAM.h b/tools/gen-zam/src/Gen-ZAM.h index 0a3af45d0b..b05db91d90 100644 --- a/tools/gen-zam/src/Gen-ZAM.h +++ b/tools/gen-zam/src/Gen-ZAM.h @@ -10,7 +10,9 @@ #pragma once #include +#include #include +#include #include #include #include @@ -29,57 +31,66 @@ enum ZAM_InstClass ZIC_FIELD, // a record field assignment }; -// For a given instruction operand, its general type. -enum ZAM_OperandType +// For a given instruction operand, its general class. +enum ZAM_OperandClass { - ZAM_OT_CONSTANT, // uses the instruction's associated constant - ZAM_OT_EVENT_HANDLER, // uses the associated event handler - ZAM_OT_INT, // directly specified integer - ZAM_OT_VAR, // frame slot associated with a variable + ZAM_OC_CONSTANT, // uses the instruction's associated constant + ZAM_OC_EVENT_HANDLER, // uses the associated event handler + ZAM_OC_INT, // directly specified integer + ZAM_OC_VAR, // frame slot associated with a variable - ZAM_OT_ASSIGN_FIELD, // record field offset to assign to - ZAM_OT_RECORD_FIELD, // record field offset to access + ZAM_OC_ASSIGN_FIELD, // record field offset to assign to + ZAM_OC_RECORD_FIELD, // record field offset to access // The following wind up the same in the ultimate instruction, // but they differ in the calling sequences used to generate // the instruction. - ZAM_OT_AUX, // uses the instruction's "aux" field - ZAM_OT_LIST, // a list, managed via the "aux" field + ZAM_OC_AUX, // uses the instruction's "aux" field + ZAM_OC_LIST, // a list, managed via the "aux" field - ZAM_OT_NONE, // instruction has no direct operands + // Internal types: branches, tracking globals, step-wise iterations + // (vectors and strings), table iterations. + ZAM_OC_BRANCH, + ZAM_OC_GLOBAL, + ZAM_OC_STEP_ITER, + ZAM_OC_TBL_ITER, + + ZAM_OC_NONE, // instruction has no direct operands }; +using OCVec = vector; + // For instructions corresponding to evaluating expressions, the type // of a given operand. The generator uses these to transform the operand's // low-level ZVal into a higher-level type expected by the associated // evaluation code. -enum ZAM_ExprType +enum ZAM_Type { - ZAM_EXPR_TYPE_ADDR, - ZAM_EXPR_TYPE_ANY, - ZAM_EXPR_TYPE_DOUBLE, - ZAM_EXPR_TYPE_FUNC, - ZAM_EXPR_TYPE_INT, - ZAM_EXPR_TYPE_PATTERN, - ZAM_EXPR_TYPE_RECORD, - ZAM_EXPR_TYPE_STRING, - ZAM_EXPR_TYPE_SUBNET, - ZAM_EXPR_TYPE_TABLE, - ZAM_EXPR_TYPE_UINT, - ZAM_EXPR_TYPE_VECTOR, - ZAM_EXPR_TYPE_FILE, - ZAM_EXPR_TYPE_OPAQUE, - ZAM_EXPR_TYPE_LIST, - ZAM_EXPR_TYPE_TYPE, + ZAM_TYPE_ADDR, + ZAM_TYPE_ANY, + ZAM_TYPE_DOUBLE, + ZAM_TYPE_FUNC, + ZAM_TYPE_INT, + ZAM_TYPE_PATTERN, + ZAM_TYPE_RECORD, + ZAM_TYPE_STRING, + ZAM_TYPE_SUBNET, + ZAM_TYPE_TABLE, + ZAM_TYPE_UINT, + ZAM_TYPE_VECTOR, + ZAM_TYPE_FILE, + ZAM_TYPE_OPAQUE, + ZAM_TYPE_LIST, + ZAM_TYPE_TYPE, // Used to specify "apart from the explicitly specified operand // types, do this action for any other types". - ZAM_EXPR_TYPE_DEFAULT, + ZAM_TYPE_DEFAULT, // Used for expressions where the evaluation code for the // expression deals directly with the operand's ZVal, rather // than the generator providing a higher-level version. - ZAM_EXPR_TYPE_NONE, + ZAM_TYPE_NONE, }; // We only use the following in the context where the vector's elements @@ -132,6 +143,9 @@ enum EmitTarget // conditionals. Cond, + // Descriptions of final ZAM operations, used for validation. + Desc, + // Switch cases that provide the C++ code for executing specific // individual ZAM instructions. Eval, @@ -176,15 +190,15 @@ enum EmitTarget OpName, }; -// A helper class for managing the (ordered) collection of ZAM_OperandType's +// A helper class for managing the (ordered) collection of ZAM_OperandClass's // associated with an instruction in order to generate C++ calling sequences // (both parameters for declarations, and arguments for invocations). class ArgsManager { public: - // Constructed by providing the various ZAM_OperandType's along + // Constructed by providing the various ZAM_OperandClass's along // with the instruction's class. - ArgsManager(const vector& ot, ZAM_InstClass ic); + ArgsManager(const OCVec& oc, ZAM_InstClass ic); // Returns a string defining the parameters for a declaration; // these have full C++ type information along with the parameter @@ -205,25 +219,23 @@ private: // "x1", "x2", etc. void Differentiate(); - // Maps ZAM_OperandType's to their associated C++ type and + // Maps ZAM_OperandClass's to their associated C++ type and // canonical parameter name. - static std::unordered_map> ot_to_args; + static std::unordered_map> oc_to_args; // For a single argument/parameter, tracks its declaration name, // C++ type, and the name to use when providing it as a parameter. - // These last two names are potentially distinct when we're - // assigning to record field (which is tracked by the is_field - // member variable), hence the need to track both. + // We have two names because in some contexts record fields have + // different names in declarations vs. in parameter lists. struct Arg { string decl_name; string decl_type; string param_name; - bool is_field; }; // All of the argument/parameters associated with the collection - // of ZAM_OperandType's. + // of ZAM_OperandClass's. vector args; // Each of the individual parameters. @@ -234,10 +246,9 @@ private: string full_params; }; -// There are two mutually interacting classes: ZAMGen is the overall -// driver for the ZAM generator, while ZAM_OpTemplate represents a -// single operation template, with subclasses for specific types of -// operations. +// There are two mutually interacting classes: ZAMGen is the overall driver +// for the ZAM generator, while ZAM_OpTemplate represents a single operation +// template, with subclasses for specific types of operations. class ZAMGen; class ZAM_OpTemplate @@ -270,15 +281,25 @@ public: // with this operation. const string& GetOp1Flavor() const { return op1_flavor; } - // True if this is an operation with side effects (see OpSideEffects - // above). + // True if this operation has side effects (see OpSideEffects above). bool HasSideEffects() const { return has_side_effects; } + // True if this operation has a predicate form (i.e., yields a + // boolean value that can be used in conditionals). + void SetIsPredicate() { is_predicate = true; } + bool IsPredicate() const { return is_predicate; } + + // The number of operands the operation takes (not including its + // assignment target). A value of 0 is used for operations that + // require special handling. + virtual int Arity() const { return 0; } + protected: - // Append to the list of operand types associated with this operation. - void AddOpType(ZAM_OperandType ot) { op_types.push_back(ot); } - // Retrieve the list of operand types associated with this operation. - const vector& OperandTypes() const { return op_types; } + // Do instantiation for predicate operations. + void InstantiatePredicate(); + + // Retrieve the list of operand classes associated with this operation. + const OCVec& OperandClasses() const { return op_classes; } // Specify the ZAMOp1Flavor associated with this operation. See // GetOp1Flavor() above for the corresponding accessor. @@ -287,12 +308,12 @@ protected: // Specify/fetch the parameter (operand) from which to take the // primary type of this operation. void SetTypeParam(int param) { type_param = param; } - int GetTypeParam() const { return type_param; } + const auto& GetTypeParam() const { return type_param; } // Specify/fetch the parameter (operand) from which to take the // secondary type of this operation. void SetType2Param(int param) { type2_param = param; } - int GetType2Param() const { return type2_param; } + const auto& GetType2Param() const { return type2_param; } // Tracking of assignment values (C++ variables that hold the // value that should be assigned to usual frame slot). @@ -353,13 +374,13 @@ protected: // is used in a dead assignment, should be converted to a different // operation that explictly omits any assignment. bool HasAssignmentLess() const { return ! assignment_less_op.empty(); } - void SetAssignmentLess(string op, string op_type) + void SetAssignmentLess(string op, string op_class) { assignment_less_op = op; - assignment_less_op_type = op_type; + assignment_less_op_class = op_class; } const string& AssignmentLessOp() const { return assignment_less_op; } - const string& AssignmentLessOpType() const { return assignment_less_op_type; } + const string& AssignmentLessOpClass() const { return assignment_less_op_class; } // Builds the instructions associated with this operation, assuming // a single operand. @@ -373,6 +394,9 @@ protected: // words. virtual void Parse(const string& attr, const string& line, const Words& words); + // Helper function that parses "class" specifications. + OCVec ParseClass(const string& spec) const; + // Scans in a C++ evaluation block, which continues until encountering // a line that does not start with whitespace, or that's empty. string GatherEval(); @@ -382,68 +406,89 @@ protected: int ExtractTypeParam(const string& arg); // Generates instructions for each of the different flavors of the - // given operation. "ot" specifies the types of operands for the + // given operation. "oc" specifies the classes of operands for the // instruction, and "do_vec" whether to generate a vector version. - void InstantiateOp(const vector& ot, bool do_vec); + void InstantiateOp(const OCVec& oc, bool do_vec); // Generates one specific flavor ("zc") of the given operation, - // using a method named 'm', the given operand types, and the class. - void InstantiateOp(const string& m, const vector& ot, ZAM_InstClass zc); + // using a method named 'm', the given operand classes, and the + // instruction class. + void InstantiateOp(const string& m, const OCVec& oc, ZAM_InstClass zc); // Generates the "assignment-less" version of the given op-code. void GenAssignmentlessVersion(const string& op); // Generates the method 'm' for an operation, where "suffix" is // a (potentially empty) string differentiating the method from - // others for that operation, and "ot" and "zc" are the same + // others for that operation, and "oc" and "zc" are the same // as above. - void InstantiateMethod(const string& m, const string& suffix, const vector& ot, + void InstantiateMethod(const string& m, const string& suffix, const OCVec& oc, ZAM_InstClass zc); // Generates the main logic of an operation's method, given the - // specific operand types, an associated suffix for differentiating + // specific operand classes, an associated suffix for differentiating // ZAM instructions, and the instruction class. - void InstantiateMethodCore(const vector& ot, const string& suffix, ZAM_InstClass zc); + void InstantiateMethodCore(const OCVec& oc, const string& suffix, ZAM_InstClass zc); // Generates the specific code to create a ZInst for the given // operation, operands, parameters to "GenInst", and suffix and // class per the above. - virtual void BuildInstruction(const vector& ot, const string& params, + virtual void BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc); + // Expands $-parameters into their direct representations given the + // operand classes and associated accessors. + string ExpandParams(const OCVec& oc, string eval, const vector& accessors) const; + string ExpandParams(const OCVec& oc, string eval) const + { + vector no_accessors; + return ExpandParams(oc, std::move(eval), no_accessors); + } + // Top-level driver for generating the C++ evaluation code for // a given flavor of operation. - virtual void InstantiateEval(const vector& ot, const string& suffix, + virtual void InstantiateEval(const OCVec& oc, const string& suffix, ZAM_InstClass zc); // Generates the C++ case statement for evaluating the given flavor // of operation. - void InstantiateEval(EmitTarget et, const string& op_suffix, const string& eval, - ZAM_InstClass zc); + void GenEval(EmitTarget et, const string& ot_str, const string& op_suffix, const string& eval, + ZAM_InstClass zc); + + // Generates a description of the ZAM operation suitable for + // reflection. + void GenDesc(const string& op_code, const string& ot_str, const string& eval); + + // Generates the first part of a description, up to (but not including) + // the evaluation. + void StartDesc(const string& op_code, const string& ot_str); + + // Finishes a description, once the evaluation is done. + void EndDesc(); // Generates a set of assignment C++ evaluations, one per each // possible Zeek scripting type of operand. - void InstantiateAssignOp(const vector& ot, const string& suffix); + void InstantiateAssignOp(const OCVec& oc, const string& suffix); // Generates a C++ evaluation for an assignment of the type // corresponding to "accessor". If "is_managed" is true then // generates the associated memory management, too. - void GenAssignOpCore(const vector& ot, const string& eval, + void GenAssignOpCore(const OCVec& oc, const string& eval, const string& accessor, bool is_managed); // The same, but for when there's an explicit assignment value. - void GenAssignOpValCore(const string& eval, const string& accessor, bool is_managed); + void GenAssignOpValCore(const OCVec& oc, const string& eval, const string& accessor, bool is_managed); // Returns the name of the method associated with the particular - // list of operand types. - string MethodName(const vector& ot) const; + // list of operand classes. + string MethodName(const OCVec& oc) const; // Returns the parameter declarations to use in declaring a method. - string MethodDeclare(const vector& ot, ZAM_InstClass zc); + string MethodDeclare(const OCVec& oc, ZAM_InstClass zc); // Returns a suffix that differentiates an operation name for - // a specific list of operand types. - string OpSuffix(const vector& ot) const; + // a specific list of operand classes. + string OpSuffix(const OCVec& oc) const; // Returns a copy of the given string with leading whitespace // removed. @@ -485,9 +530,18 @@ protected: IndentDown(); } - // Maps an operand type to a character mnemonic used to distinguish + // Start/finish emiting a (likely multi-line) string literal - + // see corresponding ZAMGen methods. + void StartString(); + void EndString(); + + // Exit with an error, mainly for consistency-checking. + void Gripe(const char* msg) const; + void Gripe(string msg, string addl) const; + + // Maps an operand class to a character mnemonic used to distinguish // it from others. - static std::unordered_map ot_to_char; + static std::unordered_map oc_to_char; // The associated driver object. ZAMGen* g; @@ -503,18 +557,31 @@ protected: // The current emission target. EmitTarget curr_et = None; - // The operand types for operations that have a single fixed list. + // The operand classes for operations that have a single fixed list. // Some operations (like those evaluating expressions) instead have - // dynamically generated range of possible operand types. - vector op_types; + // dynamically generated range of possible operand classes. + OCVec op_classes; + + // For operations that have several fixed operand sets to work through. + vector op_classes_vec; + + // If non-empty, the ZAM types associated with each operand, + // left-to-right mirroring the order of the op_classes. + vector op_types; + + // The following is usually empty, but can be instantiated when + // iterating across "types" that in some instances include ZAM_OC_INT, + // in which case those will have ".int_val" accessors associated + // with those slots. + vector accessors; // See the description of Op1Flavor above. string op1_flavor = "OP1_WRITE"; // Tracks the result of ExtractTypeParam() used for "type" and // "type2" attributes. - int type_param = 0; // 0 = not set - int type2_param = 0; + std::optional type_param; + std::optional type2_param; // If non-empty, the value to assign to the target in an assignment // operation. @@ -547,11 +614,17 @@ protected: // If true, then this operation has side effects. bool has_side_effects = false; + // Whether to instantiate this operation as a predicate, which + // results in three versions: (1) assignment of the evaluation to + // a (integer-typed) target, (2) branch if the evaluation *is not* + // the case, (3) branch if the evaluation *is* the case. + bool is_predicate = false; + // If non-empty, then specifies the associated operation that // is a version of this operation but without assigning the result; - // and the operand type (like "OP_V") of that associated operation. + // and the operand class (like "OP_V") of that associated operation. string assignment_less_op; - string assignment_less_op_type; + string assignment_less_op_class; }; // A subclass used for "unary-op" templates. @@ -595,7 +668,7 @@ public: // "is_def" is true if this instance is for the default catch-all // where the operand types don't match any of the explicitly // specified evaluations; - EvalInstance(ZAM_ExprType lhs_et, ZAM_ExprType op1_et, ZAM_ExprType op2_et, string eval, + EvalInstance(ZAM_Type lhs_et, ZAM_Type op1_et, ZAM_Type op2_et, string eval, bool is_def); // Returns the accessor to use for assigning to the LHS. "is_ptr" @@ -608,20 +681,23 @@ public: string Op2Accessor(bool is_ptr = false) const { return Accessor(op2_et, is_ptr); } // Provides an accessor for an operand of the given type. - string Accessor(ZAM_ExprType et, bool is_ptr = false) const; + string Accessor(ZAM_Type zt, bool is_ptr = false) const; // Returns the "marker" use to make unique the opcode for this // flavor of expression-evaluation instruction. string OpMarker() const; const string& Eval() const { return eval; } - ZAM_ExprType LHS_ET() const { return lhs_et; } bool IsDefault() const { return is_def; } + ZAM_Type LHS_ET() const { return lhs_et; } + ZAM_Type Op1_ET() const { return op1_et; } + ZAM_Type Op2_ET() const { return op2_et; } + private: - ZAM_ExprType lhs_et; - ZAM_ExprType op1_et; - ZAM_ExprType op2_et; + ZAM_Type lhs_et; + ZAM_Type op1_et; + ZAM_Type op2_et; string eval; bool is_def; }; @@ -632,19 +708,14 @@ class ZAM_ExprOpTemplate : public ZAM_OpTemplate public: ZAM_ExprOpTemplate(ZAMGen* _g, string _base_name); - // The number of operands the operation takes (not including its - // assignment target). A value of 0 is used for expressions that - // require special handling. - virtual int Arity() const { return 0; } - int HasExplicitResultType() const { return explicit_res_type; } void SetHasExplicitResultType() { explicit_res_type = true; } - void AddExprType(ZAM_ExprType et) { expr_types.insert(et); } - const std::unordered_set& ExprTypes() const { return expr_types; } + void AddExprType(ZAM_Type zt) { expr_types.insert(zt); } + const std::unordered_set& ExprTypes() const { return expr_types; } - void AddEvalSet(ZAM_ExprType et, string ev) { eval_set[et] += ev; } - void AddEvalSet(ZAM_ExprType et1, ZAM_ExprType et2, string ev) + void AddEvalSet(ZAM_Type zt, string ev) { eval_set[zt] += ev; } + void AddEvalSet(ZAM_Type et1, ZAM_Type et2, string ev) { eval_mixed_set[et1][et2] += ev; } @@ -652,9 +723,13 @@ public: bool IncludesFieldOp() const override { return includes_field_op; } void SetIncludesFieldOp() { includes_field_op = true; } - bool HasPreEval() const { return ! pre_eval.empty(); } - void SetPreEval(string pe) { pre_eval = SkipWS(pe); } - const string& GetPreEval() const { return pre_eval; } + bool HasPreCheck() const { return ! pre_check.empty(); } + void SetPreCheck(string pe) { pre_check = SkipWS(pe); } + const string& GetPreCheck() const { return pre_check; } + + bool HasPreCheckAction() const { return ! pre_check_action.empty(); } + void SetPreCheckAction(string pe) { pre_check_action = SkipWS(pe); } + const string& GetPreCheckAction() const { return pre_check_action; } protected: // Returns a regular expression used to access the value of the @@ -671,12 +746,12 @@ protected: // Instantiates versions of the operation that have a constant // as the first, second, or third operand ... - void InstantiateC1(const vector& ots, size_t arity, bool do_vec = false); - void InstantiateC2(const vector& ots, size_t arity); - void InstantiateC3(const vector& ots); + void InstantiateC1(const OCVec& ocs, size_t arity); + void InstantiateC2(const OCVec& ocs, size_t arity); + void InstantiateC3(const OCVec& ocs); // ... or if all of the operands are non-constant. - void InstantiateV(const vector& ots); + void InstantiateV(const OCVec& ocs); // Generates code that instantiates either the vectorized version // of an operation, or the non-vector one, depending on whether @@ -689,30 +764,34 @@ protected: // Generates an if-else cascade element that matches one of the // specific Zeek types associated with the instruction. - void GenMethodTest(ZAM_ExprType et1, ZAM_ExprType et2, const string& params, + void GenMethodTest(ZAM_Type et1, ZAM_Type et2, const string& params, const string& suffix, bool do_else, ZAM_InstClass zc); - void InstantiateEval(const vector& ot, const string& suffix, + void InstantiateEval(const OCVec& oc, const string& suffix, ZAM_InstClass zc) override; private: // The Zeek types that can appear as operands for the expression. - std::unordered_set expr_types; + std::unordered_set expr_types; // The C++ evaluation template for a given operand type. - std::unordered_map eval_set; + std::unordered_map eval_set; // Some expressions take two operands of different types. This // holds their C++ evaluation template. - std::unordered_map> eval_mixed_set; + std::unordered_map> eval_mixed_set; // Whether this expression's operand is a field access (and thus // needs both the record as an operand and an additional constant // offset into the record to get to the field). bool includes_field_op = false; - // If non-zero, code to generate prior to evaluating the expression. - string pre_eval; + // If non-empty, a check to conduct before evaluating the expression ... + string pre_check; + + // ... and the action to take if the check is true, *instead* of + // evaluating the expression. + string pre_check_action; // If true, then the evaluations will take care of ensuring // proper result types when assigning to $$. @@ -725,7 +804,7 @@ class ZAM_UnaryExprOpTemplate : public ZAM_ExprOpTemplate public: ZAM_UnaryExprOpTemplate(ZAMGen* _g, string _base_name) : ZAM_ExprOpTemplate(_g, _base_name) { } - bool IncludesFieldOp() const override { return ExprTypes().count(ZAM_EXPR_TYPE_NONE) == 0; } + bool IncludesFieldOp() const override { return ExprTypes().count(ZAM_TYPE_NONE) == 0; } int Arity() const override { return 1; } @@ -733,7 +812,7 @@ protected: void Parse(const string& attr, const string& line, const Words& words) override; void Instantiate() override; - void BuildInstruction(const vector& ot, const string& params, + void BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) override; }; @@ -770,7 +849,7 @@ public: protected: void Instantiate() override; - void BuildInstruction(const vector& ot, const string& params, + void BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) override; }; @@ -797,43 +876,10 @@ protected: void Instantiate() override; - void BuildInstruction(const vector& ot, const string& params, + void BuildInstruction(const OCVec& oc, const string& params, const string& suffix, ZAM_InstClass zc) override; }; -// A version of ZAM_BinaryExprOpTemplate for binary operations generated -// by custom methods rather than directly from the AST. -class ZAM_InternalBinaryOpTemplate : public ZAM_BinaryExprOpTemplate - { -public: - ZAM_InternalBinaryOpTemplate(ZAMGen* _g, string _base_name) - : ZAM_BinaryExprOpTemplate(_g, _base_name) - { - } - - bool IsInternalOp() const override { return true; } - - // The accessors used to get to the underlying Zeek script value - // of the first and second operand. - void SetOp1Accessor(string accessor) { op1_accessor = accessor; } - void SetOp2Accessor(string accessor) { op2_accessor = accessor; } - void SetOpAccessor(string accessor) - { - SetOp1Accessor(accessor); - SetOp2Accessor(accessor); - } - -protected: - void Parse(const string& attr, const string& line, const Words& words) override; - - void InstantiateEval(const vector& ot, const string& suffix, - ZAM_InstClass zc) override; - -private: - string op1_accessor; - string op2_accessor; - }; - // A version of ZAM_OpTemplate for operations used internally (and not // corresponding to AST elements). class ZAM_InternalOpTemplate : public ZAM_OpTemplate @@ -847,6 +893,8 @@ protected: void Parse(const string& attr, const string& line, const Words& words) override; private: + void ParseCall(const string& line, const Words& words); + // True if the internal operation corresponds to an indirect call, // i.e., one through a variable rather than one directly specified. bool is_indirect_call = false; @@ -944,6 +992,8 @@ public: void IndentUp() { ++indent_level; } void IndentDown() { --indent_level; } + void StartString() { string_lit = true; } + void EndString() { string_lit = false; } void SetNoNL(bool _no_NL) { no_NL = _no_NL; } [[noreturn]] void Gripe(const char* msg, const string& input) const { ti->Gripe(msg, input); } @@ -987,6 +1037,9 @@ private: // consistently. int indent_level = 0; + // If true, we're generating a string literal. + bool string_lit = false; + // If true, refrain from appending a newline to any emitted lines. bool no_NL = false; };