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);
}