diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5a623745f6..fbcfa6b672 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -446,6 +446,7 @@ set(MAIN_SRCS script_opt/ZAM/Profile.cc script_opt/ZAM/Stmt.cc script_opt/ZAM/Support.cc + script_opt/ZAM/Validate.cc script_opt/ZAM/Vars.cc script_opt/ZAM/ZBody.cc script_opt/ZAM/ZInst.cc diff --git a/src/Options.cc b/src/Options.cc index 0c15fbe074..c0c6ff3d4e 100644 --- a/src/Options.cc +++ b/src/Options.cc @@ -194,6 +194,7 @@ static void print_analysis_help() { fprintf(stderr, " optimize-AST optimize the (transformed) AST; implies xform\n"); fprintf(stderr, " profile-ZAM generate to zprof.out a ZAM execution profile; implies -O ZAM\n"); fprintf(stderr, " report-recursive report on recursive functions and exit\n"); + fprintf(stderr, " validate-ZAM perform internal assessment of synthesized ZAM instructions and exit\n"); fprintf(stderr, " xform transform scripts to \"reduced\" form\n"); fprintf(stderr, "\n--optimize options when generating C++:\n"); @@ -220,14 +221,14 @@ static void set_analysis_option(const char* opt, Options& opts) { exit(0); } - if ( util::streq(opt, "dump-uds") ) + if ( util::streq(opt, "allow-cond") ) + a_o.allow_cond = true; + else if ( util::streq(opt, "dump-uds") ) a_o.activate = a_o.dump_uds = true; else if ( util::streq(opt, "dump-xform") ) a_o.activate = a_o.dump_xform = true; else if ( util::streq(opt, "dump-ZAM") ) a_o.activate = a_o.dump_ZAM = true; - else if ( util::streq(opt, "allow-cond") ) - a_o.allow_cond = true; else if ( util::streq(opt, "gen-C++") ) a_o.gen_CPP = true; else if ( util::streq(opt, "gen-standalone-C++") ) @@ -254,6 +255,8 @@ static void set_analysis_option(const char* opt, Options& opts) { a_o.report_uncompilable = true; else if ( util::streq(opt, "use-C++") ) a_o.use_CPP = true; + else if ( util::streq(opt, "validate-ZAM") ) + a_o.validate_ZAM = true; else if ( util::streq(opt, "xform") ) a_o.activate = true; diff --git a/src/script_opt/ScriptOpt.cc b/src/script_opt/ScriptOpt.cc index 4a789134e8..7013eecbb9 100644 --- a/src/script_opt/ScriptOpt.cc +++ b/src/script_opt/ScriptOpt.cc @@ -558,6 +558,11 @@ void clear_script_analysis() { void analyze_scripts(bool no_unused_warnings) { init_options(); + if ( analysis_options.validate_ZAM ) { + validate_ZAM_insts(); + return; + } + // Any standalone compiled scripts have already been instantiated // at this point, but may require post-loading-of-scripts finalization. for ( auto cb : standalone_finalizations ) diff --git a/src/script_opt/ScriptOpt.h b/src/script_opt/ScriptOpt.h index 4854936073..3ca64036e5 100644 --- a/src/script_opt/ScriptOpt.h +++ b/src/script_opt/ScriptOpt.h @@ -61,6 +61,10 @@ struct AnalyOpt { // recursive, and exit. Only germane if running the inliner. bool report_recursive = false; + // If true, assess the instructions generated from ZAM templates + // for validity, and exit. + bool validate_ZAM = false; + // If true, generate ZAM code for applicable function bodies, // activating all optimizations. bool gen_ZAM = false; @@ -244,6 +248,11 @@ extern bool should_analyze(const ScriptFuncPtr& f, const StmtPtr& body); // suppressed by the flag) and optimization. extern void analyze_scripts(bool no_unused_warnings); +// Conduct internal validation of ZAM instructions. Upon success, generates +// a terse report to stdout. Exits with an internal error if a problem is +// encountered. +extern void validate_ZAM_insts(); + // Called when all script processing is complete and we can discard // unused ASTs and associated state. extern void clear_script_analysis(); diff --git a/src/script_opt/ZAM/README.md b/src/script_opt/ZAM/README.md index dc9c62da68..90afbdbf09 100644 --- a/src/script_opt/ZAM/README.md +++ b/src/script_opt/ZAM/README.md @@ -100,6 +100,7 @@ issues: |`profile-ZAM` | Generate to "zprof.out" a ZAM execution profile. (Requires configuring with `--enable-ZAM-profiling` or `--enable-debug`.)| |`report-recursive` | Report on recursive functions and exit.| |`report-uncompilable` | Report on uncompilable functions and exit. For ZAM, all functions should be compilable.| +|`validate-ZAM` | Perform internal validation of ZAM instructions and exit.| |`xform` | Transform scripts to "reduced" form.| diff --git a/src/script_opt/ZAM/Validate.cc b/src/script_opt/ZAM/Validate.cc new file mode 100644 index 0000000000..9540964335 --- /dev/null +++ b/src/script_opt/ZAM/Validate.cc @@ -0,0 +1,110 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include + +#include "zeek/script_opt/ZAM/ZBody.h" +#include "zeek/script_opt/ZAM/ZOp.h" + +using std::string; + +namespace zeek::detail { + +std::unordered_map zam_inst_desc = { + +#include "ZAM-Desc.h" + +}; + +// While the following has commonalities that could be factored out, +// for now we keep this form because it provides flexibility for +// accommodating other forms of accessors. +static std::map type_pats = { + {'A', "Addr"}, {'a', "Any"}, {'D', "Double"}, {'F', "Func"}, {'I', "Int"}, {'L', "List"}, {'N', "SubNet"}, + {'P', "Pattern"}, {'R', "Record"}, {'S', "String"}, {'T', "Table"}, {'t', "Type"}, {'U', "Count"}, {'V', "Vector"}, +}; + +static int num_valid = 0; +static int num_tested = 0; +static int num_skipped = 0; + +void analyze_ZAM_inst(const char* op_name, const ZAMInstDesc& zid) { + auto& oc = zid.op_class; + auto& ot = zid.op_types; + auto& eval = zid.op_eval; + + bool have_ot = ! ot.empty(); + + if ( have_ot && oc.size() != ot.size() ) + reporter->InternalError("%s: instruction class/types mismatch (%s/%s)", op_name, oc.c_str(), ot.c_str()); + + int nslot = 0; + + for ( size_t i = 0; i < oc.size(); ++i ) { + auto oc_i = oc[i]; + + string op; + + switch ( oc_i ) { + case 'V': + case 'R': op = "frame\\[z\\.v" + std::to_string(++nslot) + "\\]"; break; + + case 'b': + case 'f': + case 'g': + case 's': + case 'i': op = "z\\.v" + std::to_string(++nslot); break; + + case 'C': op = "z\\.c"; break; + + default: + if ( have_ot && ot[i] != 'X' ) + reporter->InternalError("instruction types mismatch: %s (%c)", op_name, oc_i); + } + + auto match_pat = op; + if ( have_ot ) { + auto ot_i = ot[i]; + + bool bare_int = std::string("bfgis").find(oc_i) != std::string::npos; + + if ( ot_i == 'X' || bare_int ) { + if ( ot_i == 'X' && bare_int ) + reporter->InternalError("empty instruction type for '%c' class element: %s", oc_i, op_name); + + if ( ! std::regex_search(eval, std::regex(op)) ) + reporter->InternalError("%s: operand %s not found", op_name, op.c_str()); + + ++num_skipped; + continue; + } + + auto tp = type_pats.find(ot_i); + if ( tp == type_pats.end() ) + reporter->InternalError("%s: instruction type %c not found", op_name, ot_i); + match_pat += ".As" + tp->second + "(Ref)?\\(\\)"; + ++num_tested; + } + + if ( ! std::regex_search(eval, std::regex(match_pat)) ) + reporter->InternalError("%s: did not find /%s/ in %s", op_name, match_pat.c_str(), eval.c_str()); + } + ++num_valid; +} + +void validate_ZAM_insts() { + // The following primes a data structure we access. + (void)AssignmentFlavor(OP_NOP, TYPE_VOID, false); + + for ( int i = 0; i < int(OP_NOP); ++i ) { + auto zop = ZOp(i); + if ( zam_inst_desc.find(zop) == zam_inst_desc.end() && assignment_flavor.find(zop) == assignment_flavor.end() ) + reporter->InternalError("op %s missing from description", ZOP_name(zop)); + } + + for ( auto& zid : zam_inst_desc ) + analyze_ZAM_inst(ZOP_name(zid.first), zid.second); + + printf("%d valid, %d tested, %d skipped\n", num_valid, num_tested, num_skipped); +} + +} // namespace zeek::detail diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index 3f5549b78a..52f267449f 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -917,8 +917,8 @@ SetupResult setup(int argc, char** argv, Options* zopts) { analyze_scripts(options.no_unused_warnings); - if ( analysis_options.report_recursive ) { - // This option is report-and-exit. + if ( analysis_options.report_recursive || analysis_options.validate_ZAM ) { + // These options are report-and-exit. early_shutdown(); exit(0); }