[ovs-dev] [PATCH v2 11/21] ovn: Use a common symbol table for ovn-controller and test-ovn.

Ben Pfaff blp at ovn.org
Mon Aug 8 16:14:22 UTC 2016


Most of the differences were superficial, so it's better to reduce code
duplication.

This also adds a test to ensure that the OVN logical registers are built
properly out of subfields.

Signed-off-by: Ben Pfaff <blp at ovn.org>
---
 include/ovn/expr.h       |   4 +
 ovn/controller/lflow.c   | 163 +---------------------------------------
 ovn/lib/automake.mk      |   1 +
 ovn/lib/expr.c           |  32 ++++++++
 ovn/lib/logical-fields.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++
 ovn/lib/logical-fields.h |   4 +
 tests/ovn.at             |  49 +++++++++---
 tests/test-ovn.c         | 122 +++++++-----------------------
 8 files changed, 296 insertions(+), 269 deletions(-)
 create mode 100644 ovn/lib/logical-fields.c

diff --git a/include/ovn/expr.h b/include/ovn/expr.h
index 011685d..df22895 100644
--- a/include/ovn/expr.h
+++ b/include/ovn/expr.h
@@ -253,6 +253,8 @@ struct expr_symbol {
     bool rw;
 };
 
+void expr_symbol_format(const struct expr_symbol *, struct ds *);
+
 /* A reference to a symbol or a subfield of a symbol.
  *
  * For string fields, ofs and n_bits are 0. */
@@ -262,6 +264,8 @@ struct expr_field {
     int n_bits;                       /* Number of bits. */
 };
 
+void expr_field_format(const struct expr_field *, struct ds *);
+
 struct expr_symbol *expr_symtab_add_field(struct shash *symtab,
                                           const char *name, enum mf_field_id,
                                           const char *prereqs,
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 71d42a7..ece464b 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -52,172 +52,11 @@ lflow_reset_processing(void)
     physical_reset_processing();
 }
 
-static void
-add_subregister(const char *name,
-                const char *parent_name, int parent_idx,
-                int width, int idx,
-                struct shash *symtab)
-{
-    int lsb = width * idx;
-    int msb = lsb + (width - 1);
-    char *expansion = xasprintf("%s%d[%d..%d]",
-                                parent_name, parent_idx, lsb, msb);
-    expr_symtab_add_subfield(symtab, name, NULL, expansion);
-    free(expansion);
-}
-
 void
 lflow_init(void)
 {
-    shash_init(&symtab);
+    ovn_init_symtab(&symtab);
     shash_init(&expr_address_sets);
-
-    /* Reserve a pair of registers for the logical inport and outport.  A full
-     * 32-bit register each is bigger than we need, but the expression code
-     * doesn't yet support string fields that occupy less than a full OXM. */
-    expr_symtab_add_string(&symtab, "inport", MFF_LOG_INPORT, NULL);
-    expr_symtab_add_string(&symtab, "outport", MFF_LOG_OUTPORT, NULL);
-
-    /* Logical registers:
-     *     128-bit xxregs
-     *     64-bit xregs
-     *     32-bit regs
-     *
-     * The expression language doesn't handle overlapping fields properly
-     * unless they're formally defined as subfields.  It's a little awkward. */
-    for (int xxi = 0; xxi < MFF_N_LOG_REGS / 4; xxi++) {
-        char *xxname = xasprintf("xxreg%d", xxi);
-        expr_symtab_add_field(&symtab, xxname, MFF_XXREG0 + xxi, NULL, false);
-        free(xxname);
-    }
-    for (int xi = 0; xi < MFF_N_LOG_REGS / 2; xi++) {
-        char *xname = xasprintf("xreg%d", xi);
-        int xxi = xi / 2;
-        if (xxi < MFF_N_LOG_REGS / 4) {
-            add_subregister(xname, "xxreg", xxi, 64, 1 - xi % 2, &symtab);
-        } else {
-            expr_symtab_add_field(&symtab, xname, MFF_XREG0 + xi, NULL, false);
-        }
-        free(xname);
-    }
-    for (int i = 0; i < MFF_N_LOG_REGS; i++) {
-        char *name = xasprintf("reg%d", i);
-        int xxi = i / 4;
-        int xi = i / 2;
-        if (xxi < MFF_N_LOG_REGS / 4) {
-            add_subregister(name, "xxreg", xxi, 32, 3 - i % 4, &symtab);
-        } else if (xi < MFF_N_LOG_REGS / 2) {
-            add_subregister(name, "xreg", xi, 32, 1 - i % 2, &symtab);
-        } else {
-            expr_symtab_add_field(&symtab, name, MFF_REG0 + i, NULL, false);
-        }
-        free(name);
-    }
-
-    /* Flags used in logical to physical transformation. */
-    expr_symtab_add_field(&symtab, "flags", MFF_LOG_FLAGS, NULL, false);
-    char flags_str[16];
-    snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_ALLOW_LOOPBACK_BIT);
-    expr_symtab_add_subfield(&symtab, "flags.loopback", NULL, flags_str);
-
-    /* Connection tracking state. */
-    expr_symtab_add_field(&symtab, "ct_mark", MFF_CT_MARK, NULL, false);
-    expr_symtab_add_field(&symtab, "ct_label", MFF_CT_LABEL, NULL, false);
-    expr_symtab_add_field(&symtab, "ct_state", MFF_CT_STATE, NULL, false);
-    char ct_state_str[16];
-    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_TRACKED_BIT);
-    expr_symtab_add_predicate(&symtab, "ct.trk", ct_state_str);
-    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_NEW_BIT);
-    expr_symtab_add_subfield(&symtab, "ct.new", "ct.trk", ct_state_str);
-    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_ESTABLISHED_BIT);
-    expr_symtab_add_subfield(&symtab, "ct.est", "ct.trk", ct_state_str);
-    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_RELATED_BIT);
-    expr_symtab_add_subfield(&symtab, "ct.rel", "ct.trk", ct_state_str);
-    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_REPLY_DIR_BIT);
-    expr_symtab_add_subfield(&symtab, "ct.rpl", "ct.trk", ct_state_str);
-    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_INVALID_BIT);
-    expr_symtab_add_subfield(&symtab, "ct.inv", "ct.trk", ct_state_str);
-
-    /* Data fields. */
-    expr_symtab_add_field(&symtab, "eth.src", MFF_ETH_SRC, NULL, false);
-    expr_symtab_add_field(&symtab, "eth.dst", MFF_ETH_DST, NULL, false);
-    expr_symtab_add_field(&symtab, "eth.type", MFF_ETH_TYPE, NULL, true);
-    expr_symtab_add_predicate(&symtab, "eth.bcast",
-                              "eth.dst == ff:ff:ff:ff:ff:ff");
-    expr_symtab_add_subfield(&symtab, "eth.mcast", NULL, "eth.dst[40]");
-
-    expr_symtab_add_field(&symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false);
-    expr_symtab_add_predicate(&symtab, "vlan.present", "vlan.tci[12]");
-    expr_symtab_add_subfield(&symtab, "vlan.pcp", "vlan.present",
-                             "vlan.tci[13..15]");
-    expr_symtab_add_subfield(&symtab, "vlan.vid", "vlan.present",
-                             "vlan.tci[0..11]");
-
-    expr_symtab_add_predicate(&symtab, "ip4", "eth.type == 0x800");
-    expr_symtab_add_predicate(&symtab, "ip6", "eth.type == 0x86dd");
-    expr_symtab_add_predicate(&symtab, "ip", "ip4 || ip6");
-    expr_symtab_add_field(&symtab, "ip.proto", MFF_IP_PROTO, "ip", true);
-    expr_symtab_add_field(&symtab, "ip.dscp", MFF_IP_DSCP, "ip", false);
-    expr_symtab_add_field(&symtab, "ip.ecn", MFF_IP_ECN, "ip", false);
-    expr_symtab_add_field(&symtab, "ip.ttl", MFF_IP_TTL, "ip", false);
-
-    expr_symtab_add_field(&symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false);
-    expr_symtab_add_field(&symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false);
-    expr_symtab_add_predicate(&symtab, "ip4.mcast", "ip4.dst[28..31] == 0xe");
-
-    expr_symtab_add_predicate(&symtab, "icmp4", "ip4 && ip.proto == 1");
-    expr_symtab_add_field(&symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4",
-              false);
-    expr_symtab_add_field(&symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
-              false);
-
-    expr_symtab_add_field(&symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
-    expr_symtab_add_field(&symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
-    expr_symtab_add_field(&symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false);
-
-    expr_symtab_add_predicate(&symtab, "icmp6", "ip6 && ip.proto == 58");
-    expr_symtab_add_field(&symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6",
-                          true);
-    expr_symtab_add_field(&symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6",
-                          true);
-
-    expr_symtab_add_predicate(&symtab, "icmp", "icmp4 || icmp6");
-
-    expr_symtab_add_field(&symtab, "ip.frag", MFF_IP_FRAG, "ip", false);
-    expr_symtab_add_predicate(&symtab, "ip.is_frag", "ip.frag[0]");
-    expr_symtab_add_predicate(&symtab, "ip.later_frag", "ip.frag[1]");
-    expr_symtab_add_predicate(&symtab, "ip.first_frag",
-                              "ip.is_frag && !ip.later_frag");
-
-    expr_symtab_add_predicate(&symtab, "arp", "eth.type == 0x806");
-    expr_symtab_add_field(&symtab, "arp.op", MFF_ARP_OP, "arp", false);
-    expr_symtab_add_field(&symtab, "arp.spa", MFF_ARP_SPA, "arp", false);
-    expr_symtab_add_field(&symtab, "arp.sha", MFF_ARP_SHA, "arp", false);
-    expr_symtab_add_field(&symtab, "arp.tpa", MFF_ARP_TPA, "arp", false);
-    expr_symtab_add_field(&symtab, "arp.tha", MFF_ARP_THA, "arp", false);
-
-    expr_symtab_add_predicate(&symtab, "nd",
-              "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(&symtab, "nd_ns",
-              "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(&symtab, "nd_na",
-              "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_field(&symtab, "nd.target", MFF_ND_TARGET, "nd", false);
-    expr_symtab_add_field(&symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false);
-    expr_symtab_add_field(&symtab, "nd.tll", MFF_ND_TLL, "nd_na", false);
-
-    expr_symtab_add_predicate(&symtab, "tcp", "ip.proto == 6");
-    expr_symtab_add_field(&symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
-    expr_symtab_add_field(&symtab, "tcp.dst", MFF_TCP_DST, "tcp", false);
-    expr_symtab_add_field(&symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false);
-
-    expr_symtab_add_predicate(&symtab, "udp", "ip.proto == 17");
-    expr_symtab_add_field(&symtab, "udp.src", MFF_UDP_SRC, "udp", false);
-    expr_symtab_add_field(&symtab, "udp.dst", MFF_UDP_DST, "udp", false);
-
-    expr_symtab_add_predicate(&symtab, "sctp", "ip.proto == 132");
-    expr_symtab_add_field(&symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false);
-    expr_symtab_add_field(&symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false);
 }
 
 /* Details of an address set currently in address_sets. We keep a cached
diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
index 4e9daf5..7f26de2 100644
--- a/ovn/lib/automake.mk
+++ b/ovn/lib/automake.mk
@@ -10,6 +10,7 @@ ovn_lib_libovn_la_SOURCES = \
 	ovn/lib/ovn-dhcp.h \
 	ovn/lib/ovn-util.c \
 	ovn/lib/ovn-util.h \
+	ovn/lib/logical-fields.c \
 	ovn/lib/logical-fields.h
 nodist_ovn_lib_libovn_la_SOURCES = \
 	ovn/lib/ovn-nb-idl.c \
diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
index 7ad990b..db00d7c 100644
--- a/ovn/lib/expr.c
+++ b/ovn/lib/expr.c
@@ -18,6 +18,7 @@
 #include "byte-order.h"
 #include "openvswitch/json.h"
 #include "logical-fields.h"
+#include "nx-match.h"
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/match.h"
 #include "openvswitch/ofp-actions.h"
@@ -1116,6 +1117,37 @@ expr_parse_string(const char *s, const struct shash *symtab,
     return expr;
 }
 
+/* Appends to 's' a re-parseable representation of 'field'. */
+void
+expr_field_format(const struct expr_field *field, struct ds *s)
+{
+    ds_put_cstr(s, field->symbol->name);
+    if (field->ofs || field->n_bits != field->symbol->width) {
+        if (field->n_bits != 1) {
+            ds_put_format(s, "[%d..%d]",
+                          field->ofs, field->ofs + field->n_bits - 1);
+        } else {
+            ds_put_format(s, "[%d]", field->ofs);
+        }
+    }
+}
+
+void
+expr_symbol_format(const struct expr_symbol *symbol, struct ds *s)
+{
+    ds_put_format(s, "%s = ", symbol->name);
+    if (symbol->parent) {
+        struct expr_field f = { symbol->parent,
+                                symbol->parent_ofs,
+                                symbol->width };
+        expr_field_format(&f, s);
+    } else if (symbol->predicate) {
+        ds_put_cstr(s, symbol->predicate);
+    } else {
+        nx_format_field_name(symbol->field->id, OFP13_VERSION, s);
+    }
+}
+
 static struct expr_symbol *
 add_symbol(struct shash *symtab, const char *name, int width,
            const char *prereqs, enum expr_level level,
diff --git a/ovn/lib/logical-fields.c b/ovn/lib/logical-fields.c
new file mode 100644
index 0000000..f79346d
--- /dev/null
+++ b/ovn/lib/logical-fields.c
@@ -0,0 +1,190 @@
+/* Copyright (c) 2016 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "logical-fields.h"
+
+#include "openvswitch/shash.h"
+#include "ovn/expr.h"
+#include "ovs-thread.h"
+#include "packets.h"
+
+static void
+add_subregister(const char *name,
+                const char *parent_name, int parent_idx,
+                int width, int idx,
+                struct shash *symtab)
+{
+    int lsb = width * idx;
+    int msb = lsb + (width - 1);
+    char *expansion = xasprintf("%s%d[%d..%d]",
+                                parent_name, parent_idx, lsb, msb);
+    expr_symtab_add_subfield(symtab, name, NULL, expansion);
+    free(expansion);
+}
+
+void
+ovn_init_symtab(struct shash *symtab)
+{
+    shash_init(symtab);
+
+    /* Reserve a pair of registers for the logical inport and outport.  A full
+     * 32-bit register each is bigger than we need, but the expression code
+     * doesn't yet support string fields that occupy less than a full OXM. */
+    expr_symtab_add_string(symtab, "inport", MFF_LOG_INPORT, NULL);
+    expr_symtab_add_string(symtab, "outport", MFF_LOG_OUTPORT, NULL);
+
+    /* Logical registers:
+     *     128-bit xxregs
+     *     64-bit xregs
+     *     32-bit regs
+     *
+     * The expression language doesn't handle overlapping fields properly
+     * unless they're formally defined as subfields.  It's a little awkward. */
+    for (int xxi = 0; xxi < MFF_N_LOG_REGS / 4; xxi++) {
+        char *xxname = xasprintf("xxreg%d", xxi);
+        expr_symtab_add_field(symtab, xxname, MFF_XXREG0 + xxi, NULL, false);
+        free(xxname);
+    }
+    for (int xi = 0; xi < MFF_N_LOG_REGS / 2; xi++) {
+        char *xname = xasprintf("xreg%d", xi);
+        int xxi = xi / 2;
+        if (xxi < MFF_N_LOG_REGS / 4) {
+            add_subregister(xname, "xxreg", xxi, 64, 1 - xi % 2, symtab);
+        } else {
+            expr_symtab_add_field(symtab, xname, MFF_XREG0 + xi, NULL, false);
+        }
+        free(xname);
+    }
+    for (int i = 0; i < MFF_N_LOG_REGS; i++) {
+        char *name = xasprintf("reg%d", i);
+        int xxi = i / 4;
+        int xi = i / 2;
+        if (xxi < MFF_N_LOG_REGS / 4) {
+            add_subregister(name, "xxreg", xxi, 32, 3 - i % 4, symtab);
+        } else if (xi < MFF_N_LOG_REGS / 2) {
+            add_subregister(name, "xreg", xi, 32, 1 - i % 2, symtab);
+        } else {
+            expr_symtab_add_field(symtab, name, MFF_REG0 + i, NULL, false);
+        }
+        free(name);
+    }
+
+    /* Flags used in logical to physical transformation. */
+    expr_symtab_add_field(symtab, "flags", MFF_LOG_FLAGS, NULL, false);
+    char flags_str[16];
+    snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_ALLOW_LOOPBACK_BIT);
+    expr_symtab_add_subfield(symtab, "flags.loopback", NULL, flags_str);
+
+    /* Connection tracking state. */
+    expr_symtab_add_field(symtab, "ct_mark", MFF_CT_MARK, NULL, false);
+    expr_symtab_add_field(symtab, "ct_label", MFF_CT_LABEL, NULL, false);
+    expr_symtab_add_field(symtab, "ct_state", MFF_CT_STATE, NULL, false);
+    char ct_state_str[16];
+    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_TRACKED_BIT);
+    expr_symtab_add_predicate(symtab, "ct.trk", ct_state_str);
+    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_NEW_BIT);
+    expr_symtab_add_subfield(symtab, "ct.new", "ct.trk", ct_state_str);
+    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_ESTABLISHED_BIT);
+    expr_symtab_add_subfield(symtab, "ct.est", "ct.trk", ct_state_str);
+    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_RELATED_BIT);
+    expr_symtab_add_subfield(symtab, "ct.rel", "ct.trk", ct_state_str);
+    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_REPLY_DIR_BIT);
+    expr_symtab_add_subfield(symtab, "ct.rpl", "ct.trk", ct_state_str);
+    snprintf(ct_state_str, sizeof ct_state_str, "ct_state[%d]", CS_INVALID_BIT);
+    expr_symtab_add_subfield(symtab, "ct.inv", "ct.trk", ct_state_str);
+
+    /* Data fields. */
+    expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false);
+    expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false);
+    expr_symtab_add_field(symtab, "eth.type", MFF_ETH_TYPE, NULL, true);
+    expr_symtab_add_predicate(symtab, "eth.bcast",
+                              "eth.dst == ff:ff:ff:ff:ff:ff");
+    expr_symtab_add_subfield(symtab, "eth.mcast", NULL, "eth.dst[40]");
+
+    expr_symtab_add_field(symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false);
+    expr_symtab_add_predicate(symtab, "vlan.present", "vlan.tci[12]");
+    expr_symtab_add_subfield(symtab, "vlan.pcp", "vlan.present",
+                             "vlan.tci[13..15]");
+    expr_symtab_add_subfield(symtab, "vlan.vid", "vlan.present",
+                             "vlan.tci[0..11]");
+
+    expr_symtab_add_predicate(symtab, "ip4", "eth.type == 0x800");
+    expr_symtab_add_predicate(symtab, "ip6", "eth.type == 0x86dd");
+    expr_symtab_add_predicate(symtab, "ip", "ip4 || ip6");
+    expr_symtab_add_field(symtab, "ip.proto", MFF_IP_PROTO, "ip", true);
+    expr_symtab_add_field(symtab, "ip.dscp", MFF_IP_DSCP, "ip", false);
+    expr_symtab_add_field(symtab, "ip.ecn", MFF_IP_ECN, "ip", false);
+    expr_symtab_add_field(symtab, "ip.ttl", MFF_IP_TTL, "ip", false);
+
+    expr_symtab_add_field(symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false);
+    expr_symtab_add_field(symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false);
+    expr_symtab_add_predicate(symtab, "ip4.mcast", "ip4.dst[28..31] == 0xe");
+
+    expr_symtab_add_predicate(symtab, "icmp4", "ip4 && ip.proto == 1");
+    expr_symtab_add_field(symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4",
+              false);
+    expr_symtab_add_field(symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
+              false);
+
+    expr_symtab_add_field(symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
+    expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
+    expr_symtab_add_field(symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false);
+
+    expr_symtab_add_predicate(symtab, "icmp6", "ip6 && ip.proto == 58");
+    expr_symtab_add_field(symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6",
+                          true);
+    expr_symtab_add_field(symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6",
+                          true);
+
+    expr_symtab_add_predicate(symtab, "icmp", "icmp4 || icmp6");
+
+    expr_symtab_add_field(symtab, "ip.frag", MFF_IP_FRAG, "ip", false);
+    expr_symtab_add_predicate(symtab, "ip.is_frag", "ip.frag[0]");
+    expr_symtab_add_predicate(symtab, "ip.later_frag", "ip.frag[1]");
+    expr_symtab_add_predicate(symtab, "ip.first_frag",
+                              "ip.is_frag && !ip.later_frag");
+
+    expr_symtab_add_predicate(symtab, "arp", "eth.type == 0x806");
+    expr_symtab_add_field(symtab, "arp.op", MFF_ARP_OP, "arp", false);
+    expr_symtab_add_field(symtab, "arp.spa", MFF_ARP_SPA, "arp", false);
+    expr_symtab_add_field(symtab, "arp.sha", MFF_ARP_SHA, "arp", false);
+    expr_symtab_add_field(symtab, "arp.tpa", MFF_ARP_TPA, "arp", false);
+    expr_symtab_add_field(symtab, "arp.tha", MFF_ARP_THA, "arp", false);
+
+    expr_symtab_add_predicate(symtab, "nd",
+              "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
+    expr_symtab_add_predicate(symtab, "nd_ns",
+              "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
+    expr_symtab_add_predicate(symtab, "nd_na",
+              "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
+    expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false);
+    expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false);
+    expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false);
+
+    expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6");
+    expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
+    expr_symtab_add_field(symtab, "tcp.dst", MFF_TCP_DST, "tcp", false);
+    expr_symtab_add_field(symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false);
+
+    expr_symtab_add_predicate(symtab, "udp", "ip.proto == 17");
+    expr_symtab_add_field(symtab, "udp.src", MFF_UDP_SRC, "udp", false);
+    expr_symtab_add_field(symtab, "udp.dst", MFF_UDP_DST, "udp", false);
+
+    expr_symtab_add_predicate(symtab, "sctp", "ip.proto == 132");
+    expr_symtab_add_field(symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false);
+    expr_symtab_add_field(symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false);
+}
diff --git a/ovn/lib/logical-fields.h b/ovn/lib/logical-fields.h
index c8e3f07..1ea2e0f 100644
--- a/ovn/lib/logical-fields.h
+++ b/ovn/lib/logical-fields.h
@@ -18,6 +18,8 @@
 
 #include "openvswitch/meta-flow.h"
 
+struct shash;
+
 enum {
     MLF_ALLOW_LOOPBACK_BIT = 0
 };
@@ -48,4 +50,6 @@ enum {
 #define MFF_LOG_REG0 MFF_REG0
 #define MFF_N_LOG_REGS 10
 
+void ovn_init_symtab(struct shash *symtab);
+
 #endif /* ovn/lib/logical-fields.h */
diff --git a/tests/ovn.at b/tests/ovn.at
index 3543cf5..e6d5af6 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -132,6 +132,33 @@ sed 's/.* => //' test-cases.txt > expout
 AT_CHECK([ovstest test-ovn lex < input.txt], [0], [expout])
 AT_CLEANUP
 
+dnl The OVN expression parser needs to know what fields overlap with one
+dnl another.  This test therefore verifies that all the smaller registers
+dnl are defined as terms of subfields of the larger ones.
+dnl
+dnl When we add or remove registers this test needs to be updated, of course.
+AT_SETUP([ovn -- registers])
+AT_CHECK([ovstest test-ovn dump-symtab | grep reg | sort], [0],
+[[reg0 = xxreg0[96..127]
+reg1 = xxreg0[64..95]
+reg2 = xxreg0[32..63]
+reg3 = xxreg0[0..31]
+reg4 = xxreg1[96..127]
+reg5 = xxreg1[64..95]
+reg6 = xxreg1[32..63]
+reg7 = xxreg1[0..31]
+reg8 = xreg4[32..63]
+reg9 = xreg4[0..31]
+xreg0 = xxreg0[64..127]
+xreg1 = xxreg0[0..63]
+xreg2 = xxreg1[64..127]
+xreg3 = xxreg1[0..63]
+xreg4 = OXM_OF_PKT_REG4
+xxreg0 = NXM_NX_XXREG0
+xxreg1 = NXM_NX_XXREG1
+]])
+AT_CLEANUP
+
 AT_SETUP([ovn -- expression parser])
 dnl For lines without =>, input and expected output are identical.
 dnl For lines with =>, input precedes => and expected output follows =>.
@@ -330,7 +357,7 @@ vlan.present => vlan.tci[12]
 
 !vlan.pcp => vlan.tci[13..15] == 0 && vlan.tci[12]
 vlan.pcp == 1 && vlan.vid == 2 => vlan.tci[13..15] == 0x1 && vlan.tci[12] && vlan.tci[0..11] == 0x2 && vlan.tci[12]
-!reg0 && !reg1 && !reg2 && !reg3 => xreg0[32..63] == 0 && xreg0[0..31] == 0 && xreg1[32..63] == 0 && xreg1[0..31] == 0
+!reg0 && !reg1 && !reg2 && !reg3 => xxreg0[96..127] == 0 && xxreg0[64..95] == 0 && xxreg0[32..63] == 0 && xxreg0[0..31] == 0
 
 ip.first_frag => ip.frag[0] && (eth.type == 0x800 || eth.type == 0x86dd) && (!ip.frag[1] || (eth.type != 0x800 && eth.type != 0x86dd))
 !ip.first_frag => !ip.frag[0] || (eth.type != 0x800 && eth.type != 0x86dd) || (ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd))
@@ -575,9 +602,9 @@ self_recurse = 123; => Error parsing expression `self_recurse != 0' encountered
 vlan.present = 0; => Predicate symbol vlan.present used where lvalue required.
 
 # Moving one field into another.
-reg0 = reg1; => actions=move:OXM_OF_PKT_REG0[0..31]->OXM_OF_PKT_REG0[32..63], prereqs=1
-vlan.pcp = reg0[0..2]; => actions=move:OXM_OF_PKT_REG0[32..34]->NXM_OF_VLAN_TCI[13..15], prereqs=vlan.tci[12]
-reg0[10] = vlan.pcp[1]; => actions=move:NXM_OF_VLAN_TCI[14]->OXM_OF_PKT_REG0[42], prereqs=vlan.tci[12]
+reg0 = reg1; => actions=move:NXM_NX_XXREG0[64..95]->NXM_NX_XXREG0[96..127], prereqs=1
+vlan.pcp = reg0[0..2]; => actions=move:NXM_NX_XXREG0[96..98]->NXM_OF_VLAN_TCI[13..15], prereqs=vlan.tci[12]
+reg0[10] = vlan.pcp[1]; => actions=move:NXM_OF_VLAN_TCI[14]->NXM_NX_XXREG0[106], prereqs=vlan.tci[12]
 outport = inport; => actions=move:NXM_NX_REG14[]->NXM_NX_REG15[], prereqs=1
 
 reg0[0] = vlan.present; => Predicate symbol vlan.present used where lvalue required.
@@ -587,9 +614,9 @@ inport = big_string; => String fields inport and big_string are incompatible for
 ip.proto = reg0[0..7]; => Field ip.proto is not modifiable.
 
 # Exchanging fields.
-reg0 <-> reg1; => actions=push:OXM_OF_PKT_REG0[0..31],push:OXM_OF_PKT_REG0[32..63],pop:OXM_OF_PKT_REG0[0..31],pop:OXM_OF_PKT_REG0[32..63], prereqs=1
-vlan.pcp <-> reg0[0..2]; => actions=push:OXM_OF_PKT_REG0[32..34],push:NXM_OF_VLAN_TCI[13..15],pop:OXM_OF_PKT_REG0[32..34],pop:NXM_OF_VLAN_TCI[13..15], prereqs=vlan.tci[12]
-reg0[10] <-> vlan.pcp[1]; => actions=push:NXM_OF_VLAN_TCI[14],push:OXM_OF_PKT_REG0[42],pop:NXM_OF_VLAN_TCI[14],pop:OXM_OF_PKT_REG0[42], prereqs=vlan.tci[12]
+reg0 <-> reg1; => actions=push:NXM_NX_XXREG0[64..95],push:NXM_NX_XXREG0[96..127],pop:NXM_NX_XXREG0[64..95],pop:NXM_NX_XXREG0[96..127], prereqs=1
+vlan.pcp <-> reg0[0..2]; => actions=push:NXM_NX_XXREG0[96..98],push:NXM_OF_VLAN_TCI[13..15],pop:NXM_NX_XXREG0[96..98],pop:NXM_OF_VLAN_TCI[13..15], prereqs=vlan.tci[12]
+reg0[10] <-> vlan.pcp[1]; => actions=push:NXM_OF_VLAN_TCI[14],push:NXM_NX_XXREG0[106],pop:NXM_OF_VLAN_TCI[14],pop:NXM_NX_XXREG0[106], prereqs=vlan.tci[12]
 outport <-> inport; => actions=push:NXM_NX_REG14[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_NX_REG15[], prereqs=1
 
 reg0[0] <-> vlan.present; => Predicate symbol vlan.present used where lvalue required.
@@ -649,7 +676,7 @@ arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; => actions=controller(userdata=00.
 
 # get_arp
 get_arp(outport, ip4.dst); => actions=push:NXM_NX_REG0[],push:NXM_OF_IP_DST[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[], prereqs=eth.type == 0x800
-get_arp(inport, reg0); => actions=push:NXM_NX_REG15[],push:NXM_NX_REG0[],push:OXM_OF_PKT_REG0[32..63],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[],pop:NXM_NX_REG15[], prereqs=1
+get_arp(inport, reg0); => actions=push:NXM_NX_REG15[],push:NXM_NX_REG0[],push:NXM_NX_XXREG0[96..127],push:NXM_NX_REG14[],pop:NXM_NX_REG15[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,65),pop:NXM_NX_REG0[],pop:NXM_NX_REG15[], prereqs=1
 get_arp; => Syntax error at `;' expecting `('.
 get_arp(); => Syntax error at `)' expecting field name.
 get_arp(inport); => Syntax error at `)' expecting `,'.
@@ -663,8 +690,8 @@ get_arp(reg0, ip4.dst); => Cannot use numeric field reg0 where string field is r
 put_arp(inport, arp.spa, arp.sha); => actions=push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.01.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[], prereqs=eth.type == 0x806 && eth.type == 0x806
 
 # put_dhcp_opts
-reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); => actions=controller(userdata=00.00.00.02.00.00.00.00.80.01.00.08.00.00.00.00.01.02.03.04.03.04.0a.00.00.01,pause), prereqs=1
-reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain="ovn.org"); => actions=controller(userdata=00.00.00.02.00.00.00.00.80.01.02.08.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67,pause), prereqs=1
+reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); => actions=controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause), prereqs=1
+reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain="ovn.org"); => actions=controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67,pause), prereqs=1
 # offerip=10.0.0.4             --> 0a.00.00.04
 # router=10.0.0.1              --> 03.04.0a.00.00.01
 # netmask=255.255.255.0        --> 01.04.ff.ff.ff.00
@@ -678,7 +705,7 @@ reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,m
 #  0.0.0.0/0,10.0.0.1}         --> 00.0a.00.00.01
 # ethernet_encap=1             --> 24.01.01
 # router_discovery=0           --> 1f.01.00
-reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0); => actions=controller(userdata=00.00.00.02.00.00.00.00.80.01.00.08.00.00.00.2f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00,pause), prereqs=1
+reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0); => actions=controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00,pause), prereqs=1
 reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); => Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
 reg1[0] = put_dhcp_opts(); => Syntax error at `)'.
 reg1[0] = put_dhcp_opts(x = 1.2.3.4, router = 10.0.0.1); => Syntax error at `x' expecting offerip option.
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index acb6a99..42941eb 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -29,6 +29,7 @@
 #include "ovn/actions.h"
 #include "ovn/expr.h"
 #include "ovn/lex.h"
+#include "ovn/lib/logical-fields.h"
 #include "ovn/lib/ovn-dhcp.h"
 #include "ovs-thread.h"
 #include "ovstest.h"
@@ -138,102 +139,7 @@ test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
 static void
 create_symtab(struct shash *symtab)
 {
-    shash_init(symtab);
-
-    /* Reserve a pair of registers for the logical inport and outport.  A full
-     * 32-bit register each is bigger than we need, but the expression code
-     * doesn't yet support string fields that occupy less than a full OXM. */
-    expr_symtab_add_string(symtab, "inport", MFF_REG14, NULL);
-    expr_symtab_add_string(symtab, "outport", MFF_REG15, NULL);
-
-    expr_symtab_add_field(symtab, "xxreg0", MFF_XXREG0, NULL, false);
-    expr_symtab_add_field(symtab, "xxreg1", MFF_XXREG1, NULL, false);
-
-    expr_symtab_add_field(symtab, "xreg0", MFF_XREG0, NULL, false);
-    expr_symtab_add_field(symtab, "xreg1", MFF_XREG1, NULL, false);
-    expr_symtab_add_field(symtab, "xreg2", MFF_XREG2, NULL, false);
-
-    expr_symtab_add_subfield(symtab, "reg0", NULL, "xreg0[32..63]");
-    expr_symtab_add_subfield(symtab, "reg1", NULL, "xreg0[0..31]");
-    expr_symtab_add_subfield(symtab, "reg2", NULL, "xreg1[32..63]");
-    expr_symtab_add_subfield(symtab, "reg3", NULL, "xreg1[0..31]");
-    expr_symtab_add_subfield(symtab, "reg4", NULL, "xreg2[32..63]");
-    expr_symtab_add_subfield(symtab, "reg5", NULL, "xreg2[0..31]");
-
-    expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false);
-    expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false);
-    expr_symtab_add_field(symtab, "eth.type", MFF_ETH_TYPE, NULL, true);
-
-    expr_symtab_add_field(symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false);
-    expr_symtab_add_predicate(symtab, "vlan.present", "vlan.tci[12]");
-    expr_symtab_add_subfield(symtab, "vlan.pcp", "vlan.present",
-                             "vlan.tci[13..15]");
-    expr_symtab_add_subfield(symtab, "vlan.vid", "vlan.present",
-                             "vlan.tci[0..11]");
-
-    expr_symtab_add_predicate(symtab, "ip4", "eth.type == 0x800");
-    expr_symtab_add_predicate(symtab, "ip6", "eth.type == 0x86dd");
-    expr_symtab_add_predicate(symtab, "ip", "ip4 || ip6");
-    expr_symtab_add_field(symtab, "ip.proto", MFF_IP_PROTO, "ip", true);
-    expr_symtab_add_field(symtab, "ip.dscp", MFF_IP_DSCP, "ip", false);
-    expr_symtab_add_field(symtab, "ip.ecn", MFF_IP_ECN, "ip", false);
-    expr_symtab_add_field(symtab, "ip.ttl", MFF_IP_TTL, "ip", false);
-
-    expr_symtab_add_field(symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false);
-    expr_symtab_add_field(symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false);
-
-    expr_symtab_add_predicate(symtab, "icmp4", "ip4 && ip.proto == 1");
-    expr_symtab_add_field(symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4",
-              false);
-    expr_symtab_add_field(symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
-              false);
-
-    expr_symtab_add_field(symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
-    expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
-    expr_symtab_add_field(symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false);
-
-    expr_symtab_add_predicate(symtab, "icmp6", "ip6 && ip.proto == 58");
-    expr_symtab_add_field(symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6",
-                          true);
-    expr_symtab_add_field(symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6",
-                          true);
-
-    expr_symtab_add_predicate(symtab, "icmp", "icmp4 || icmp6");
-
-    expr_symtab_add_field(symtab, "ip.frag", MFF_IP_FRAG, "ip", false);
-    expr_symtab_add_predicate(symtab, "ip.is_frag", "ip.frag[0]");
-    expr_symtab_add_predicate(symtab, "ip.later_frag", "ip.frag[1]");
-    expr_symtab_add_predicate(symtab, "ip.first_frag", "ip.is_frag && !ip.later_frag");
-
-    expr_symtab_add_predicate(symtab, "arp", "eth.type == 0x806");
-    expr_symtab_add_field(symtab, "arp.op", MFF_ARP_OP, "arp", false);
-    expr_symtab_add_field(symtab, "arp.spa", MFF_ARP_SPA, "arp", false);
-    expr_symtab_add_field(symtab, "arp.sha", MFF_ARP_SHA, "arp", false);
-    expr_symtab_add_field(symtab, "arp.tpa", MFF_ARP_TPA, "arp", false);
-    expr_symtab_add_field(symtab, "arp.tha", MFF_ARP_THA, "arp", false);
-
-    expr_symtab_add_predicate(symtab, "nd",
-              "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(symtab, "nd_ns",
-              "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_predicate(symtab, "nd_na",
-              "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
-    expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false);
-    expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL, "nd_ns", false);
-    expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL, "nd_na", false);
-
-    expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6");
-    expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
-    expr_symtab_add_field(symtab, "tcp.dst", MFF_TCP_DST, "tcp", false);
-    expr_symtab_add_field(symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false);
-
-    expr_symtab_add_predicate(symtab, "udp", "ip.proto == 17");
-    expr_symtab_add_field(symtab, "udp.src", MFF_UDP_SRC, "udp", false);
-    expr_symtab_add_field(symtab, "udp.dst", MFF_UDP_DST, "udp", false);
-
-    expr_symtab_add_predicate(symtab, "sctp", "ip.proto == 132");
-    expr_symtab_add_field(symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false);
-    expr_symtab_add_field(symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false);
+    ovn_init_symtab(symtab);
 
     /* For negative testing. */
     expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
@@ -398,6 +304,27 @@ test_expr_to_flows(struct ovs_cmdl_context *ctx OVS_UNUSED)
     test_parse_expr__(4);
 }
 
+/* Print the symbol table. */
+
+static void
+test_dump_symtab(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+    struct shash symtab;
+    create_symtab(&symtab);
+
+    const struct shash_node **nodes = shash_sort(&symtab);
+    for (size_t i = 0; i < shash_count(&symtab); i++) {
+        const struct expr_symbol *symbol = nodes[i]->data;
+        struct ds s = DS_EMPTY_INITIALIZER;
+        expr_symbol_format(symbol, &s);
+        puts(ds_cstr(&s));
+        ds_destroy(&s);
+    }
+
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+}
+
 /* Evaluate an expression. */
 
 static bool evaluate_expr(const struct expr *, unsigned int subst, int n_bits);
@@ -1534,6 +1461,9 @@ test_ovn_main(int argc, char *argv[])
         /* Lexer. */
         {"lex", NULL, 0, 0, test_lex},
 
+        /* Symbol table. */
+        {"dump-symtab", NULL, 0, 0, test_dump_symtab},
+
         /* Expressions. */
         {"parse-expr", NULL, 0, 0, test_parse_expr},
         {"annotate-expr", NULL, 0, 0, test_annotate_expr},
-- 
2.1.3




More information about the dev mailing list