Initial commit

llvm-jit based parser and loader
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..962b0fc
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 2.8.7)
+
+project(bpf-tools)
+
+find_package(BISON)
+find_package(FLEX)
+find_package(LLVM REQUIRED CONFIG)
+find_program(XXD xxd)
+if (${XXD} STREQUAL "XXD-NOTFOUND")
+  message(FATAL_ERROR "program xxd not found, install vim-common")
+endif()
+find_program(CLANG clang)
+if (${CLANG} STREQUAL "CLANG-NOTFOUND")
+  message(FATAL_ERROR "program clang not found, install clang with bpf support")
+endif()
+
+set(CMAKE_C_FLAGS "-Wall")
+set(CMAKE_CXX_FLAGS "-std=c++11 -Wall")
+
+add_subdirectory(jit)
diff --git a/jit/CMakeLists.txt b/jit/CMakeLists.txt
new file mode 100644
index 0000000..6831ea7
--- /dev/null
+++ b/jit/CMakeLists.txt
@@ -0,0 +1,7 @@
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -fno-rtti")
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/compat/include)
+
+add_subdirectory(src)
diff --git a/jit/compat/include/linux/bpf.h b/jit/compat/include/linux/bpf.h
new file mode 100644
index 0000000..23df3e7
--- /dev/null
+++ b/jit/compat/include/linux/bpf.h
@@ -0,0 +1,226 @@
+/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#ifndef _UAPI__LINUX_BPF_H__
+#define _UAPI__LINUX_BPF_H__
+
+#include <linux/types.h>
+#include <linux/bpf_common.h>
+
+/* Extended instruction set based on top of classic BPF */
+
+/* instruction classes */
+#define BPF_ALU64	0x07	/* alu mode in double word width */
+
+/* ld/ldx fields */
+#define BPF_DW		0x18	/* double word */
+#define BPF_XADD	0xc0	/* exclusive add */
+
+/* alu/jmp fields */
+#define BPF_MOV		0xb0	/* mov reg to reg */
+#define BPF_ARSH	0xc0	/* sign extending arithmetic shift right */
+
+/* change endianness of a register */
+#define BPF_END		0xd0	/* flags for endianness conversion: */
+#define BPF_TO_LE	0x00	/* convert to little-endian */
+#define BPF_TO_BE	0x08	/* convert to big-endian */
+#define BPF_FROM_LE	BPF_TO_LE
+#define BPF_FROM_BE	BPF_TO_BE
+
+#define BPF_JNE		0x50	/* jump != */
+#define BPF_JSGT	0x60	/* SGT is signed '>', GT in x86 */
+#define BPF_JSGE	0x70	/* SGE is signed '>=', GE in x86 */
+#define BPF_CALL	0x80	/* function call */
+#define BPF_EXIT	0x90	/* function return */
+
+/* Register numbers */
+enum {
+	BPF_REG_0 = 0,
+	BPF_REG_1,
+	BPF_REG_2,
+	BPF_REG_3,
+	BPF_REG_4,
+	BPF_REG_5,
+	BPF_REG_6,
+	BPF_REG_7,
+	BPF_REG_8,
+	BPF_REG_9,
+	BPF_REG_10,
+	__MAX_BPF_REG,
+};
+
+/* BPF has 10 general purpose 64-bit registers and stack frame. */
+#define MAX_BPF_REG	__MAX_BPF_REG
+
+struct bpf_insn {
+	__u8	code;		/* opcode */
+	__u8	dst_reg:4;	/* dest register */
+	__u8	src_reg:4;	/* source register */
+	__s16	off;		/* signed offset */
+	__s32	imm;		/* signed immediate constant */
+};
+
+/* BPF syscall commands */
+enum bpf_cmd {
+	/* create a map with given type and attributes
+	 * fd = bpf(BPF_MAP_CREATE, union bpf_attr *, u32 size)
+	 * returns fd or negative error
+	 * map is deleted when fd is closed
+	 */
+	BPF_MAP_CREATE,
+
+	/* lookup key in a given map
+	 * err = bpf(BPF_MAP_LOOKUP_ELEM, union bpf_attr *attr, u32 size)
+	 * Using attr->map_fd, attr->key, attr->value
+	 * returns zero and stores found elem into value
+	 * or negative error
+	 */
+	BPF_MAP_LOOKUP_ELEM,
+
+	/* create or update key/value pair in a given map
+	 * err = bpf(BPF_MAP_UPDATE_ELEM, union bpf_attr *attr, u32 size)
+	 * Using attr->map_fd, attr->key, attr->value, attr->flags
+	 * returns zero or negative error
+	 */
+	BPF_MAP_UPDATE_ELEM,
+
+	/* find and delete elem by key in a given map
+	 * err = bpf(BPF_MAP_DELETE_ELEM, union bpf_attr *attr, u32 size)
+	 * Using attr->map_fd, attr->key
+	 * returns zero or negative error
+	 */
+	BPF_MAP_DELETE_ELEM,
+
+	/* lookup key in a given map and return next key
+	 * err = bpf(BPF_MAP_GET_NEXT_KEY, union bpf_attr *attr, u32 size)
+	 * Using attr->map_fd, attr->key, attr->next_key
+	 * returns zero and stores next key or negative error
+	 */
+	BPF_MAP_GET_NEXT_KEY,
+
+	/* verify and load eBPF program
+	 * prog_fd = bpf(BPF_PROG_LOAD, union bpf_attr *attr, u32 size)
+	 * Using attr->prog_type, attr->insns, attr->license
+	 * returns fd or negative error
+	 */
+	BPF_PROG_LOAD,
+};
+
+enum bpf_map_type {
+	BPF_MAP_TYPE_UNSPEC,
+	BPF_MAP_TYPE_HASH,
+	BPF_MAP_TYPE_ARRAY,
+};
+
+enum bpf_prog_type {
+	BPF_PROG_TYPE_UNSPEC,
+	BPF_PROG_TYPE_SOCKET_FILTER,
+	BPF_PROG_TYPE_SCHED_CLS,
+	BPF_PROG_TYPE_SCHED_ACT,
+};
+
+#define BPF_PSEUDO_MAP_FD	1
+
+/* flags for BPF_MAP_UPDATE_ELEM command */
+#define BPF_ANY		0 /* create new element or update existing */
+#define BPF_NOEXIST	1 /* create new element if it didn't exist */
+#define BPF_EXIST	2 /* update existing element */
+
+union bpf_attr {
+	struct { /* anonymous struct used by BPF_MAP_CREATE command */
+		__u32	map_type;	/* one of enum bpf_map_type */
+		__u32	key_size;	/* size of key in bytes */
+		__u32	value_size;	/* size of value in bytes */
+		__u32	max_entries;	/* max number of entries in a map */
+	};
+
+	struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
+		__u32		map_fd;
+		__aligned_u64	key;
+		union {
+			__aligned_u64 value;
+			__aligned_u64 next_key;
+		};
+		__u64		flags;
+	};
+
+	struct { /* anonymous struct used by BPF_PROG_LOAD command */
+		__u32		prog_type;	/* one of enum bpf_prog_type */
+		__u32		insn_cnt;
+		__aligned_u64	insns;
+		__aligned_u64	license;
+		__u32		log_level;	/* verbosity level of verifier */
+		__u32		log_size;	/* size of user buffer */
+		__aligned_u64	log_buf;	/* user supplied buffer */
+	};
+} __attribute__((aligned(8)));
+
+/* integer value in 'imm' field of BPF_CALL instruction selects which helper
+ * function eBPF program intends to call
+ */
+enum bpf_func_id {
+	BPF_FUNC_unspec,
+	BPF_FUNC_map_lookup_elem, /* void *map_lookup_elem(&map, &key) */
+	BPF_FUNC_map_update_elem, /* int map_update_elem(&map, &key, &value, flags) */
+	BPF_FUNC_map_delete_elem, /* int map_delete_elem(&map, &key) */
+	BPF_FUNC_get_prandom_u32, /* u32 prandom_u32(void) */
+	BPF_FUNC_get_smp_processor_id, /* u32 raw_smp_processor_id(void) */
+
+	/**
+	 * skb_store_bytes(skb, offset, from, len, flags) - store bytes into packet
+	 * @skb: pointer to skb
+	 * @offset: offset within packet from skb->data
+	 * @from: pointer where to copy bytes from
+	 * @len: number of bytes to store into packet
+	 * @flags: bit 0 - if true, recompute skb->csum
+	 *         other bits - reserved
+	 * Return: 0 on success
+	 */
+	BPF_FUNC_skb_store_bytes,
+
+	/**
+	 * l3_csum_replace(skb, offset, from, to, flags) - recompute IP checksum
+	 * @skb: pointer to skb
+	 * @offset: offset within packet where IP checksum is located
+	 * @from: old value of header field
+	 * @to: new value of header field
+	 * @flags: bits 0-3 - size of header field
+	 *         other bits - reserved
+	 * Return: 0 on success
+	 */
+	BPF_FUNC_l3_csum_replace,
+
+	/**
+	 * l4_csum_replace(skb, offset, from, to, flags) - recompute TCP/UDP checksum
+	 * @skb: pointer to skb
+	 * @offset: offset within packet where TCP/UDP checksum is located
+	 * @from: old value of header field
+	 * @to: new value of header field
+	 * @flags: bits 0-3 - size of header field
+	 *         bit 4 - is pseudo header
+	 *         other bits - reserved
+	 * Return: 0 on success
+	 */
+	BPF_FUNC_l4_csum_replace,
+	__BPF_FUNC_MAX_ID,
+};
+
+/* user accessible mirror of in-kernel sk_buff.
+ * new fields can only be added to the end of this structure
+ */
+struct __sk_buff {
+	__u32 len;
+	__u32 pkt_type;
+	__u32 mark;
+	__u32 queue_mapping;
+	__u32 protocol;
+	__u32 vlan_present;
+	__u32 vlan_tci;
+	__u32 vlan_proto;
+	__u32 priority;
+};
+
+#endif /* _UAPI__LINUX_BPF_H__ */
diff --git a/jit/compat/include/linux/bpf_common.h b/jit/compat/include/linux/bpf_common.h
new file mode 100644
index 0000000..afe7433
--- /dev/null
+++ b/jit/compat/include/linux/bpf_common.h
@@ -0,0 +1,55 @@
+#ifndef __LINUX_BPF_COMMON_H__
+#define __LINUX_BPF_COMMON_H__
+
+/* Instruction classes */
+#define BPF_CLASS(code) ((code) & 0x07)
+#define		BPF_LD		0x00
+#define		BPF_LDX		0x01
+#define		BPF_ST		0x02
+#define		BPF_STX		0x03
+#define		BPF_ALU		0x04
+#define		BPF_JMP		0x05
+#define		BPF_RET		0x06
+#define		BPF_MISC        0x07
+
+/* ld/ldx fields */
+#define BPF_SIZE(code)  ((code) & 0x18)
+#define		BPF_W		0x00
+#define		BPF_H		0x08
+#define		BPF_B		0x10
+#define BPF_MODE(code)  ((code) & 0xe0)
+#define		BPF_IMM		0x00
+#define		BPF_ABS		0x20
+#define		BPF_IND		0x40
+#define		BPF_MEM		0x60
+#define		BPF_LEN		0x80
+#define		BPF_MSH		0xa0
+
+/* alu/jmp fields */
+#define BPF_OP(code)    ((code) & 0xf0)
+#define		BPF_ADD		0x00
+#define		BPF_SUB		0x10
+#define		BPF_MUL		0x20
+#define		BPF_DIV		0x30
+#define		BPF_OR		0x40
+#define		BPF_AND		0x50
+#define		BPF_LSH		0x60
+#define		BPF_RSH		0x70
+#define		BPF_NEG		0x80
+#define		BPF_MOD		0x90
+#define		BPF_XOR		0xa0
+
+#define		BPF_JA		0x00
+#define		BPF_JEQ		0x10
+#define		BPF_JGT		0x20
+#define		BPF_JGE		0x30
+#define		BPF_JSET        0x40
+#define BPF_SRC(code)   ((code) & 0x08)
+#define		BPF_K		0x00
+#define		BPF_X		0x08
+
+#ifndef BPF_MAXINSNS
+#define BPF_MAXINSNS 4096
+#endif
+
+#endif /* __LINUX_BPF_COMMON_H__ */
diff --git a/jit/src/CMakeLists.txt b/jit/src/CMakeLists.txt
new file mode 100644
index 0000000..1c18ad4
--- /dev/null
+++ b/jit/src/CMakeLists.txt
@@ -0,0 +1,4 @@
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+add_subdirectory(cc)
diff --git a/jit/src/bpf.py b/jit/src/bpf.py
new file mode 100644
index 0000000..0c06d88
--- /dev/null
+++ b/jit/src/bpf.py
@@ -0,0 +1,145 @@
+import ctypes as ct
+import os
+import pyroute2 as pr
+
+lib = ct.cdll.LoadLibrary("libbpfprog.so")
+
+lib.bpf_program_create.restype = ct.c_void_p
+lib.bpf_program_create.argtypes = [ct.c_char_p, ct.c_char_p, ct.c_uint]
+lib.bpf_program_destroy.restype = None
+lib.bpf_program_destroy.argtypes = [ct.c_void_p]
+lib.bpf_program_start.restype = ct.c_void_p
+lib.bpf_program_start.argtypes = [ct.c_void_p, ct.c_char_p]
+lib.bpf_program_size.restype = ct.c_size_t
+lib.bpf_program_size.argtypes = [ct.c_void_p, ct.c_char_p]
+lib.bpf_program_license.restype = ct.c_char_p
+lib.bpf_program_license.argtypes = [ct.c_void_p]
+lib.bpf_program_table_fd.restype = ct.c_int
+lib.bpf_program_table_fd.argtypes = [ct.c_void_p, ct.c_char_p]
+
+lib.bpf_get_next_key.restype = ct.c_int
+lib.bpf_get_next_key.argtypes = [ct.c_int, ct.c_void_p, ct.c_void_p]
+lib.bpf_lookup_elem.restype = ct.c_int
+lib.bpf_lookup_elem.argtypes = [ct.c_int, ct.c_void_p, ct.c_void_p]
+lib.bpf_update_elem.restype = ct.c_int
+lib.bpf_update_elem.argtypes = [ct.c_int, ct.c_void_p, ct.c_void_p,
+        ct.c_ulonglong]
+lib.bpf_delete_elem.restype = ct.c_int
+lib.bpf_delete_elem.argtypes = [ct.c_int, ct.c_void_p]
+lib.bpf_open_raw_sock.restype = ct.c_int
+lib.bpf_open_raw_sock.argtypes = [ct.c_char_p]
+lib.bpf_attach_socket.restype = ct.c_int
+lib.bpf_attach_socket.argtypes = [ct.c_int, ct.c_int]
+lib.bpf_attach_filter.restype = ct.c_int
+lib.bpf_attach_filter.argtypes = [ct.c_int, ct.c_char_p, ct.c_uint, ct.c_ubyte, ct.c_uint]
+lib.bpf_prog_load.restype = ct.c_int
+lib.bpf_prog_load.argtypes = [ct.c_int, ct.c_void_p, ct.c_size_t,
+        ct.c_char_p]
+
+class BPF(object):
+    BPF_PROG_TYPE_SOCKET_FILTER = 1
+    BPF_PROG_TYPE_SCHED_CLS = 2
+    BPF_PROG_TYPE_SCHED_ACT = 3
+    def __init__(self, name, dp_file, dph_file,
+            prog_type=BPF_PROG_TYPE_SOCKET_FILTER,
+            debug=0):
+        self.debug = debug
+        self.name = name
+        self.prog = lib.bpf_program_create(dp_file.encode("ascii"),
+                dph_file.encode("ascii"), self.debug)
+
+        if self.prog == ct.c_void_p(None):
+            raise Exception("Failed to compile BPF program %s" % dp_file)
+
+        if lib.bpf_program_start(self.prog,
+                self.name.encode("ascii")) == ct.c_void_p(None):
+            raise Exception("Unknown program %s" % self.name)
+
+        self.fd = lib.bpf_prog_load(prog_type,
+                lib.bpf_program_start(self.prog, self.name.encode("ascii")),
+                lib.bpf_program_size(self.prog, self.name.encode("ascii")),
+                lib.bpf_program_license(self.prog))
+
+        if self.fd < 0:
+            print((ct.c_char * 65536).in_dll(lib, "bpf_log_buf").value)
+            #print(ct.c_char_p.in_dll(lib, "bpf_log_buf").value)
+            raise Exception("Failed to load BPF program %s" % dp_file)
+
+    class Table(object):
+        def __init__(self, bpf, map_fd, keytype, leaftype):
+            self.bpf = bpf
+            self.map_fd = map_fd
+            self.keytype = keytype
+            self.leaftype = leaftype
+
+        def get(self, key):
+            key_p = ct.pointer(key)
+            leaf = self.leaftype()
+            leaf_p = ct.pointer(leaf)
+            res = lib.bpf_lookup_elem(self.map_fd,
+                    ct.cast(key_p, ct.c_void_p),
+                    ct.cast(leaf_p, ct.c_void_p))
+            if res < 0:
+                raise Exception("Could not lookup in table")
+            return leaf
+
+        def put(self, key, leaf, flags=0):
+            key_p = ct.pointer(key)
+            leaf_p = ct.pointer(leaf)
+            res = lib.bpf_update_elem(self.map_fd,
+                    ct.cast(key_p, ct.c_void_p),
+                    ct.cast(leaf_p, ct.c_void_p), flags)
+            if res < 0:
+                raise Exception("Could not update table")
+
+        class Iter(object):
+            def __init__(self, table, keytype):
+                self.keytype = keytype
+                self.table = table
+                self.key = keytype()
+            def __iter__(self):
+                return self
+            def __next__(self):
+                return self.next()
+            def next(self):
+                self.key = self.table.next(self.key)
+                return self.key
+
+        def iter(self):
+            return BPF.Table.Iter(self, self.keytype)
+
+        def next(self, key):
+            next_key = self.keytype()
+            next_key_p = ct.pointer(next_key)
+            key_p = ct.pointer(key)
+            res = lib.bpf_get_next_key(self.map_fd,
+                    ct.cast(key_p, ct.c_void_p),
+                    ct.cast(next_key_p, ct.c_void_p))
+            if res < 0:
+                raise StopIteration()
+            return next_key
+
+    def table(self, name, keytype, leaftype):
+        map_fd = lib.bpf_program_table_fd(self.prog,
+                ct.c_char_p(name.encode("ascii")))
+        if map_fd < 0:
+            raise Exception("Failed to find BPF Table %s" % name)
+        return BPF.Table(self, map_fd, keytype, leaftype)
+
+    def attach(self, dev):
+        self.sock = lib.bpf_open_raw_sock(dev.encode("ascii"))
+        if self.sock < 0:
+            errstr = os.strerror(ct.get_errno())
+            raise Exception("Failed to open raw device %s: %s" % (dev, errstr))
+        res = lib.bpf_attach_socket(self.sock, self.fd)
+        if res < 0:
+            errstr = os.strerror(ct.get_errno())
+            raise Exception("Failed to attach BPF to device %s: %s"
+                    % (dev, errstr))
+
+    def attach_filter(self, ifindex, prio, classid):
+        res = lib.bpf_attach_filter(self.fd, self.name.encode("ascii"), ifindex, prio, classid)
+        if res < 0:
+            raise Exception("Failed to filter with BPF")
+
+
diff --git a/jit/src/cc/CMakeLists.txt b/jit/src/cc/CMakeLists.txt
new file mode 100644
index 0000000..df88e42
--- /dev/null
+++ b/jit/src/cc/CMakeLists.txt
@@ -0,0 +1,37 @@
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+include_directories(${LLVM_INCLUDE_DIRS})
+add_definitions(${LLVM_DEFINITIONS})
+
+BISON_TARGET(Parser parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.yy.cc COMPILE_FLAGS "-o parser.yy.cc -v --debug")
+FLEX_TARGET(Lexer lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/lexer.ll.cc COMPILE_FLAGS "--c++ --o lexer.ll.cc")
+ADD_FLEX_BISON_DEPENDENCY(Lexer Parser)
+
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bitops.bc
+  COMMAND ${CLANG}
+  ARGS -O3 -emit-llvm -S -o bitops.bc -I${CMAKE_SOURCE_DIR}/jit/compat/include
+    -c ${CMAKE_CURRENT_SOURCE_DIR}/bitops.c
+  DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bitops.c
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  COMMENT "Generating bitops IR")
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bitops.h
+  COMMAND ${XXD} ARGS -i bitops.bc bitops.h
+  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bitops.bc
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  COMMENT "Generating bitops.h")
+add_custom_target(bitops DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bitops.h)
+
+set(CMAKE_SHARED_LINKER_FLAGS "-static-libstdc++ -Wl,--exclude-libs=ALL")
+
+add_library(bpfprog SHARED bpf_common.cc bpf_program.cc codegen_llvm.cc
+  node.cc parser.cc printer.cc type_check.cc libbpf.c
+  ${BISON_Parser_OUTPUTS} ${FLEX_Lexer_OUTPUTS} ${CMAKE_CURRENT_BINARY_DIR}/bitops.h)
+
+# BPF is still experimental otherwise it should be available
+#llvm_map_components_to_libnames(llvm_libs bpf mcjit irreader passes)
+llvm_map_components_to_libnames(llvm_libs mcjit irreader passes)
+
+# Link against LLVM libraries
+target_link_libraries(bpfprog ${llvm_libs} LLVMBPFCodeGen mnl)
diff --git a/jit/src/cc/bitops.c b/jit/src/cc/bitops.c
new file mode 100644
index 0000000..06e51ba
--- /dev/null
+++ b/jit/src/cc/bitops.c
@@ -0,0 +1,198 @@
+/*
+ * ====================================================================
+ * Copyright (c) 2012-2013, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * ====================================================================
+ */
+
+#include <stdint.h>
+#include "linux/bpf.h"
+#include "bpf_helpers.h"
+#define assert(v)
+
+static inline uint16_t bpf_ntohs(uint16_t val) {
+  /* will be recognized by gcc into rotate insn and eventually rolw 8 */
+  return (val << 8) | (val >> 8);
+}
+
+static inline uint32_t bpf_ntohl(uint32_t val) {
+  /* gcc will use bswapsi2 insn */
+  return __builtin_bswap32(val);
+}
+
+static inline uint64_t bpf_ntohll(uint64_t val) {
+  /* gcc will use bswapdi2 insn */
+  return __builtin_bswap64(val);
+}
+
+static inline unsigned __int128 bpf_ntoh128(unsigned __int128 val) {
+  return (((unsigned __int128)bpf_ntohll(val) << 64) | (uint64_t)bpf_ntohll(val >> 64));
+}
+
+static inline uint16_t bpf_htons(uint16_t val) {
+  return bpf_ntohs(val);
+}
+
+static inline uint32_t bpf_htonl(uint32_t val) {
+  return bpf_ntohl(val);
+}
+static inline uint64_t bpf_htonll(uint64_t val) {
+  return bpf_ntohll(val);
+}
+static inline unsigned __int128 bpf_hton128(unsigned __int128 val) {
+  return bpf_ntoh128(val);
+}
+
+static inline uint64_t load_dword(void *skb, uint64_t off) {
+  return ((uint64_t)load_word(skb, off) << 4) | load_word(skb, off + 4);
+}
+
+void bpf_store_byte(void *skb, uint64_t off, uint64_t val) asm("llvm.bpf.store.byte");
+void bpf_store_half(void *skb, uint64_t off, uint64_t val) asm("llvm.bpf.store.half");
+void bpf_store_word(void *skb, uint64_t off, uint64_t val) asm("llvm.bpf.store.word");
+static inline void bpf_store_dword(void *skb, uint64_t off, uint64_t val) {
+  bpf_store_word(skb, off, (uint32_t)val);
+  bpf_store_word(skb, off + 4, val >> 32);
+}
+
+#define MASK(_n) ((_n) < 64 ? (1ull << (_n)) - 1 : ((uint64_t)-1LL))
+#define MASK128(_n) ((_n) < 128 ? ((unsigned __int128)1 << (_n)) - 1 : ((unsigned __int128)-1))
+
+struct _skbuff;
+struct bpf_context;
+
+//static inline __attribute__((always_inline))
+SEC("helpers")
+uint64_t bpf_dext_pkt(void *pkt, uint64_t off, uint64_t bofs, uint64_t bsz) {
+  if (bofs == 0 && bsz == 8) {
+    return load_byte(pkt, off);
+  } else if (bofs + bsz <= 8) {
+    return load_byte(pkt, off) >> (8 - (bofs + bsz))  &  MASK(bsz);
+  } else if (bofs == 0 && bsz == 16) {
+    return load_half(pkt, off);
+  } else if (bofs + bsz <= 16) {
+    return load_half(pkt, off) >> (16 - (bofs + bsz))  &  MASK(bsz);
+  } else if (bofs == 0 && bsz == 32) {
+    return load_word(pkt, off);
+  } else if (bofs + bsz <= 32) {
+    return load_word(pkt, off) >> (32 - (bofs + bsz))  &  MASK(bsz);
+  } else if (bofs + bsz <= 64) {
+    return bpf_ntohll(load_dword(pkt, off)) >> (64 - (bofs + bsz))  &  MASK(bsz);
+  } else {
+    assert(0);
+  }
+  return 0;
+}
+
+//static inline __attribute__((always_inline))
+SEC("helpers")
+void bpf_dins_pkt(void *pkt, uint64_t off, uint64_t bofs, uint64_t bsz, uint64_t val) {
+  // The load_xxx function does a bswap before returning the short/word/dword,
+  // so the value in register will always be host endian. However, the bytes
+  // written back need to be in network order.
+  if (bofs == 0 && bsz == 8) {
+    bpf_skb_store_bytes(pkt, off, &val, 1, 0);
+  } else if (bofs + bsz <= 8) {
+    uint8_t v = load_byte(pkt, off);
+    v &= ~(MASK(bsz) << (8 - (bofs + bsz)));
+    v |= ((val & MASK(bsz)) << (8 - (bofs + bsz)));
+    bpf_skb_store_bytes(pkt, off, &v, 1, 0);
+  } else if (bofs == 0 && bsz == 16) {
+    uint16_t v = bpf_htons(val);
+    bpf_skb_store_bytes(pkt, off, &v, 2, 0);
+  } else if (bofs + bsz <= 16) {
+    uint16_t v = load_half(pkt, off);
+    v &= ~(MASK(bsz) << (16 - (bofs + bsz)));
+    v |= ((val & MASK(bsz)) << (16 - (bofs + bsz)));
+    v = bpf_htons(v);
+    bpf_skb_store_bytes(pkt, off, &v, 2, 0);
+  } else if (bofs == 0 && bsz == 32) {
+    uint32_t v = bpf_htonl(val);
+    bpf_skb_store_bytes(pkt, off, &v, 4, 0);
+  } else if (bofs + bsz <= 32) {
+    uint32_t v = load_word(pkt, off);
+    v &= ~(MASK(bsz) << (32 - (bofs + bsz)));
+    v |= ((val & MASK(bsz)) << (32 - (bofs + bsz)));
+    v = bpf_htonl(v);
+    bpf_skb_store_bytes(pkt, off, &v, 4, 0);
+  } else if (bofs == 0 && bsz == 64) {
+    uint64_t v = bpf_htonll(val);
+    bpf_skb_store_bytes(pkt, off, &v, 8, 0);
+  } else if (bofs + bsz <= 64) {
+    uint64_t v = load_dword(pkt, off);
+    v &= ~(MASK(bsz) << (64 - (bofs + bsz)));
+    v |= ((val & MASK(bsz)) << (64 - (bofs + bsz)));
+    v = bpf_htonll(v);
+    bpf_skb_store_bytes(pkt, off, &v, 8, 0);
+  } else if (bofs + bsz <= 128) {
+    assert(0);
+    //bpf_store_16bytes(pkt, off, bpf_hton128(~(MASK128(bsz) << (128 - (bofs + bsz)))),
+    //                 bpf_hton128((val & MASK128(bsz)) << (128 - (bofs + bsz))));
+  } else {
+    assert(0);
+  }
+}
+
+SEC("helpers")
+void * bpf_map_lookup_elem_(uintptr_t map, void *key) {
+  return bpf_map_lookup_elem((void *)map, key);
+}
+
+SEC("helpers")
+int bpf_map_update_elem_(uintptr_t map, void *key, void *value, uint64_t flags) {
+  return bpf_map_update_elem((void *)map, key, value, flags);
+}
+
+SEC("helpers")
+int bpf_map_delete_elem_(uintptr_t map, void *key) {
+  return bpf_map_delete_elem((void *)map, key);
+}
+
+SEC("helpers")
+int bpf_skb_store_bytes_(void *ctx, uint64_t off, void *from, uint64_t len, uint64_t flags) {
+  return bpf_skb_store_bytes(ctx, off, from, len, flags);
+}
+
+SEC("helpers")
+int bpf_l3_csum_replace_(void *ctx, uint64_t off, uint64_t from, uint64_t to, uint64_t flags) {
+  switch (flags & 0xf) {
+    case 2:
+      return bpf_l3_csum_replace(ctx, off, bpf_htons(from), bpf_htons(to), flags);
+    case 4:
+      return bpf_l3_csum_replace(ctx, off, bpf_htonl(from), bpf_htonl(to), flags);
+    case 8:
+      return bpf_l3_csum_replace(ctx, off, bpf_htonll(from), bpf_htonll(to), flags);
+    default:
+      {}
+  }
+  return bpf_l3_csum_replace(ctx, off, from, to, flags);
+}
+
+SEC("helpers")
+int bpf_l4_csum_replace_(void *ctx, uint64_t off, uint64_t from, uint64_t to, uint64_t flags) {
+  switch (flags & 0xf) {
+    case 2:
+      return bpf_l4_csum_replace(ctx, off, bpf_htons(from), bpf_htons(to), flags);
+    case 4:
+      return bpf_l4_csum_replace(ctx, off, bpf_htonl(from), bpf_htonl(to), flags);
+    case 8:
+      return bpf_l4_csum_replace(ctx, off, bpf_htonll(from), bpf_htonll(to), flags);
+    default:
+      {}
+  }
+  return bpf_l4_csum_replace(ctx, off, from, to, flags);
+}
+
+#undef assert
+
diff --git a/jit/src/cc/bpf_common.cc b/jit/src/cc/bpf_common.cc
new file mode 100644
index 0000000..9b4ee0e
--- /dev/null
+++ b/jit/src/cc/bpf_common.cc
@@ -0,0 +1,44 @@
+#include "cc/bpf_program.h"
+#include "cc/bpf_common.h"
+
+extern "C" {
+void * bpf_program_create(const char *filename, const char *proto_filename, unsigned flags) {
+  auto prog = new ebpf::BPFProgram(flags);
+  if (prog->load(filename, proto_filename) != 0) {
+    delete prog;
+    return nullptr;
+  }
+  return prog;
+}
+
+void bpf_program_destroy(void *program) {
+  auto prog = static_cast<ebpf::BPFProgram *>(program);
+  if (!prog) return;
+  delete prog;
+}
+
+void * bpf_program_start(void *program, const char *name) {
+  auto prog = static_cast<ebpf::BPFProgram *>(program);
+  if (!prog) return nullptr;
+  return prog->start(name);
+}
+
+size_t bpf_program_size(void *program, const char *name) {
+  auto prog = static_cast<ebpf::BPFProgram *>(program);
+  if (!prog) return 0;
+  return prog->size(name);
+}
+
+char * bpf_program_license(void *program) {
+  auto prog = static_cast<ebpf::BPFProgram *>(program);
+  if (!prog) return nullptr;
+  return prog->license();
+}
+
+int bpf_program_table_fd(void *program, const char *table_name) {
+  auto prog = static_cast<ebpf::BPFProgram *>(program);
+  if (!prog) return -1;
+  return prog->table_fd(table_name);
+}
+
+}
diff --git a/jit/src/cc/bpf_common.h b/jit/src/cc/bpf_common.h
new file mode 100644
index 0000000..a6bb952
--- /dev/null
+++ b/jit/src/cc/bpf_common.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void * bpf_program_create(const char *filename, const char *proto_filename, unsigned flags);
+void bpf_program_destroy(void *program);
+void * bpf_program_start(void *program, const char *name);
+size_t bpf_program_size(void *program, const char *name);
+char * bpf_program_license(void *program);
+int bpf_program_table_fd(void *program, const char *table_name);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/jit/src/cc/bpf_helpers.h b/jit/src/cc/bpf_helpers.h
new file mode 100644
index 0000000..6ccc181
--- /dev/null
+++ b/jit/src/cc/bpf_helpers.h
@@ -0,0 +1,50 @@
+#ifndef __BPF_HELPERS_H
+#define __BPF_HELPERS_H
+
+/* helper macro to place programs, maps, license in
+ * different sections in elf_bpf file. Section names
+ * are interpreted by elf_bpf loader
+ */
+#define SEC(NAME) __attribute__((section(NAME), used))
+
+/* helper functions called from eBPF programs written in C */
+static void *(*bpf_map_lookup_elem)(void *map, void *key) =
+	(void *) BPF_FUNC_map_lookup_elem;
+static int (*bpf_map_update_elem)(void *map, void *key, void *value,
+				  unsigned long long flags) =
+	(void *) BPF_FUNC_map_update_elem;
+static int (*bpf_map_delete_elem)(void *map, void *key) =
+	(void *) BPF_FUNC_map_delete_elem;
+
+/* llvm builtin functions that eBPF C program may use to
+ * emit BPF_LD_ABS and BPF_LD_IND instructions
+ */
+struct sk_buff;
+unsigned long long load_byte(void *skb,
+			     unsigned long long off) asm("llvm.bpf.load.byte");
+unsigned long long load_half(void *skb,
+			     unsigned long long off) asm("llvm.bpf.load.half");
+unsigned long long load_word(void *skb,
+			     unsigned long long off) asm("llvm.bpf.load.word");
+
+/* a helper structure used by eBPF C program
+ * to describe map attributes to elf_bpf loader
+ */
+struct bpf_map_def {
+	unsigned int type;
+	unsigned int key_size;
+	unsigned int value_size;
+	unsigned int max_entries;
+};
+
+static int (*bpf_skb_store_bytes)(void *ctx, unsigned long long off, void *from,
+				  unsigned long long len, unsigned long long flags) =
+	(void *) BPF_FUNC_skb_store_bytes;
+static int (*bpf_l3_csum_replace)(void *ctx, unsigned long long off, unsigned long long from,
+				  unsigned long long to, unsigned long long flags) =
+	(void *) BPF_FUNC_l3_csum_replace;
+static int (*bpf_l4_csum_replace)(void *ctx, unsigned long long off, unsigned long long from,
+				  unsigned long long to, unsigned long long flags) =
+	(void *) BPF_FUNC_l4_csum_replace;
+
+#endif
diff --git a/jit/src/cc/bpf_program.cc b/jit/src/cc/bpf_program.cc
new file mode 100644
index 0000000..6cb200a
--- /dev/null
+++ b/jit/src/cc/bpf_program.cc
@@ -0,0 +1,228 @@
+// Generated by llvm2cpp - DO NOT MODIFY!
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include <llvm/ADT/STLExtras.h>
+#include <llvm/ExecutionEngine/MCJIT.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
+#include <llvm/IRReader/IRReader.h>
+#include <llvm/IR/IRPrintingPasses.h>
+#include <llvm/IR/LegacyPassManager.h>
+#include <llvm/IR/LLVMContext.h>
+#include <llvm/IR/Module.h>
+#include <llvm/IR/Verifier.h>
+#include <llvm/Object/ObjectFile.h>
+#include <llvm/Support/FormattedStream.h>
+#include <llvm/Support/SourceMgr.h>
+#include <llvm/Transforms/IPO.h>
+#include <llvm/Transforms/IPO/PassManagerBuilder.h>
+
+#include "exception.h"
+#include "parser.h"
+#include "type_check.h"
+#include "codegen_llvm.h"
+#include "bpf_program.h"
+
+namespace ebpf {
+
+using std::get;
+using std::make_tuple;
+using std::map;
+using std::move;
+using std::string;
+using std::tuple;
+using std::unique_ptr;
+using namespace llvm;
+
+// Snooping class to remember the sections as the JIT creates them
+class MyMemoryManager : public SectionMemoryManager {
+ public:
+
+  explicit MyMemoryManager(map<string, tuple<uint8_t *, uintptr_t>> *sections)
+      : sections_(sections) {
+  }
+
+  virtual ~MyMemoryManager() {}
+  uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
+                               unsigned SectionID,
+                               StringRef SectionName) override {
+    uint8_t *Addr = SectionMemoryManager::allocateCodeSection(Size, Alignment, SectionID, SectionName);
+    //printf("allocateCodeSection: %s Addr %p Size %ld Alignment %d SectionID %d\n",
+    //       SectionName.str().c_str(), (void *)Addr, Size, Alignment, SectionID);
+    (*sections_)[SectionName.str()] = make_tuple(Addr, Size);
+    return Addr;
+  }
+  uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
+                               unsigned SectionID, StringRef SectionName,
+                               bool isReadOnly) override {
+    uint8_t *Addr = SectionMemoryManager::allocateDataSection(Size, Alignment, SectionID, SectionName, isReadOnly);
+    //printf("allocateDataSection: %s Addr %p Size %ld Alignment %d SectionID %d RO %d\n",
+    //       SectionName.str().c_str(), (void *)Addr, Size, Alignment, SectionID, isReadOnly);
+    (*sections_)[SectionName.str()] = make_tuple(Addr, Size);
+    return Addr;
+  }
+  map<string, tuple<uint8_t *, uintptr_t>> *sections_;
+};
+
+BPFProgram::BPFProgram(unsigned flags)
+    : flags_(flags) {
+  LLVMInitializeBPFTarget();
+  LLVMInitializeBPFTargetMC();
+  LLVMInitializeBPFTargetInfo();
+  LLVMInitializeBPFAsmPrinter();
+  LLVMLinkInMCJIT(); /* call empty function to force linking of MCJIT */
+}
+
+BPFProgram::~BPFProgram() {
+  engine_.reset();
+  LLVMShutdown();
+}
+
+int BPFProgram::parse() {
+  int rc;
+
+  proto_parser_ = make_unique<ebpf::cc::Parser>(proto_filename_);
+  rc = proto_parser_->parse();
+  if (rc) return rc;
+
+  parser_ = make_unique<ebpf::cc::Parser>(filename_);
+  rc = parser_->parse();
+  if (rc) return rc;
+
+  //ebpf::cc::Printer printer(stderr);
+  //printer.visit(parser_->root_node_);
+
+  ebpf::cc::TypeCheck type_check(parser_->scopes_.get(), proto_parser_->scopes_.get(), parser_->pragmas_);
+  auto ret = type_check.visit(parser_->root_node_);
+  if (get<0>(ret) != 0 || get<1>(ret).size()) {
+    fprintf(stderr, "Type error @line=%d: %s\n", get<0>(ret), get<1>(ret).c_str());
+    exit(1);
+  }
+
+  codegen_ = ebpf::make_unique<ebpf::cc::CodegenLLVM>(mod_, parser_->scopes_.get(),
+                                                      proto_parser_->scopes_.get(),
+                                                      /*use_pre_header*/false,
+                                                      parser_->pragma("name"));
+  ret = codegen_->visit(parser_->root_node_);
+  if (get<0>(ret) != 0 || get<1>(ret).size()) {
+    fprintf(stderr, "Codegen error @line=%d: %s\n", get<0>(ret), get<1>(ret).c_str());
+    return get<0>(ret);
+  }
+
+  return 0;
+}
+
+string BPFProgram::load_helper() const {
+  // generated from bitops.cc -> bitops.bc -> hexdump -> bitops.h
+#include "cc/bitops.h"
+  return string((const char *)bitops_bc, bitops_bc_len);
+}
+
+// Load in a pre-built list of functions into the initial Module object, then
+// build an ExecutionEngine.
+int BPFProgram::init_engine() {
+  SMDiagnostic diag;
+  string helper = load_helper();
+  MemoryBufferRef helper_mem(helper, "helper");
+  unique_ptr<Module> mod = parseIR(helper_mem, diag, getGlobalContext());
+  if (!mod) {
+    diag.print("bitops", errs());
+    exit(1);
+  }
+  mod_ = mod.get();
+
+  mod_->setDataLayout("e-m:e-i64:64-f80:128-n8:16:32:64-S128");
+  mod_->setTargetTriple("bpf");
+
+  for (auto fn = mod_->getFunctionList().begin(); fn != mod_->getFunctionList().end(); ++fn)
+    fn->addFnAttr(Attribute::AlwaysInline);
+
+  string err;
+  engine_ = unique_ptr<ExecutionEngine>(EngineBuilder(move(mod))
+      .setErrorStr(&err)
+      .setMCJITMemoryManager(make_unique<MyMemoryManager>(&sections_))
+      .setMArch("bpf")
+      .create());
+  if (!engine_) {
+    fprintf(stderr, "Could not create ExecutionEngine: %s\n", err.c_str());
+    return -1;
+  }
+
+  return 0;
+}
+
+void BPFProgram::dump_ir() {
+  legacy::PassManager PM;
+  PM.add(createPrintModulePass(outs()));
+  PM.run(*mod_);
+}
+
+int BPFProgram::finalize() {
+  if (verifyModule(*mod_, &errs())) {
+    if (flags_ & 1)
+      dump_ir();
+    return -1;
+  }
+
+  legacy::PassManager PM;
+  PassManagerBuilder PMB;
+  PMB.OptLevel = 3;
+  PM.add(createFunctionInliningPass());
+  PM.add(createAlwaysInlinerPass());
+  PMB.populateModulePassManager(PM);
+  if (flags_ & 1)
+    PM.add(createPrintModulePass(outs()));
+  PM.run(*mod_);
+
+  engine_->finalizeObject();
+
+  return 0;
+}
+
+uint8_t * BPFProgram::start(const string &name) const {
+  auto section = sections_.find(name);
+  if (section == sections_.end())
+    return nullptr;
+
+  return get<0>(section->second);
+}
+
+size_t BPFProgram::size(const string &name) const {
+  auto section = sections_.find(name);
+  if (section == sections_.end())
+    return 0;
+
+  return get<1>(section->second);
+}
+
+char * BPFProgram::license() const {
+  auto section = sections_.find("license");
+  if (section == sections_.end())
+    return nullptr;
+
+  return (char *)get<0>(section->second);
+}
+
+int BPFProgram::table_fd(const string &name) const {
+  return codegen_->get_table_fd(name);
+}
+
+int BPFProgram::load(const string &filename, const string &proto_filename) {
+  if (!sections_.empty()) {
+    fprintf(stderr, "Program already initialized\n");
+    return -1;
+  }
+  filename_ = filename;
+  proto_filename_ = proto_filename;
+  if (int rc = init_engine())
+    return rc;
+  if (int rc = parse())
+    return rc;
+  if (int rc = finalize())
+    return rc;
+  return 0;
+}
+
+} // namespace ebpf
diff --git a/jit/src/cc/bpf_program.h b/jit/src/cc/bpf_program.h
new file mode 100644
index 0000000..9fb0157
--- /dev/null
+++ b/jit/src/cc/bpf_program.h
@@ -0,0 +1,66 @@
+/*
+ * ====================================================================
+ * Copyright (c) 2015, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * ====================================================================
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace llvm {
+class ExecutionEngine;
+class Module;
+}
+
+namespace ebpf {
+
+namespace cc {
+class CodegenLLVM;
+class Parser;
+}
+
+class BPFProgram {
+ private:
+  int init_engine();
+  int parse();
+  int finalize();
+  void dump_ir();
+  std::string load_helper() const;
+ public:
+  BPFProgram(unsigned flags);
+  ~BPFProgram();
+  int load(const std::string &filename, const std::string &proto_filename);
+  uint8_t * start(const std::string &name) const;
+  size_t size(const std::string &name) const;
+  int table_fd(const std::string &name) const;
+  char * license() const;
+ private:
+  unsigned flags_;  // 0x1 for printing
+  std::string filename_;
+  std::string proto_filename_;
+  std::unique_ptr<llvm::ExecutionEngine> engine_;
+  llvm::Module *mod_;
+  std::unique_ptr<ebpf::cc::Parser> parser_;
+  std::unique_ptr<ebpf::cc::Parser> proto_parser_;
+  std::unique_ptr<ebpf::cc::CodegenLLVM> codegen_;
+  std::map<std::string, std::tuple<uint8_t *, uintptr_t>> sections_;
+};
+
+}  // namespace ebpf
diff --git a/jit/src/cc/codegen_c.cc b/jit/src/cc/codegen_c.cc
new file mode 100644
index 0000000..eb55841
--- /dev/null
+++ b/jit/src/cc/codegen_c.cc
@@ -0,0 +1,1191 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#include <set>
+#include <algorithm>
+#include <sstream>
+#include <assert.h>
+#include "exception.h"
+#include "cc/codegen_c.h"
+#include "cc/lexer.h"
+#include "cc/type_helper.h"
+
+namespace ebpf {
+namespace cc {
+
+using std::set;
+using std::for_each;
+using std::pair;
+using std::stringstream;
+
+template <typename... Args>
+void CodegenC::emitln(const char *fmt, Args&&... params) {
+  fprintf(out_, fmt, std::forward<Args>(params)...);
+  fprintf(out_, "\n%*s", indent_ * 2, "");
+}
+void CodegenC::emitln(const char *s) {
+  fprintf(out_, "%s", s);
+  fprintf(out_, "\n%*s", indent_ * 2, "");
+}
+
+template <typename... Args>
+void CodegenC::emit(const char *fmt, Args&&... params) {
+  fprintf(out_, fmt, std::forward<Args>(params)...);
+}
+void CodegenC::emit(const char *s) {
+  fprintf(out_, "%s", s);
+}
+
+template <typename... Args>
+void CodegenC::lnemit(const char *fmt, Args&&... params) {
+  fprintf(out_, "\n%*s", indent_ * 2, "");
+  fprintf(out_, fmt, std::forward<Args>(params)...);
+}
+void CodegenC::lnemit(const char *s) {
+  fprintf(out_, "\n%*s", indent_ * 2, "");
+  fprintf(out_, "%s", s);
+}
+
+void CodegenC::indent() {
+  fprintf(out_, "%*s", indent_ * 2, "");
+}
+
+void CodegenC::emit_comment(Node* n) {
+  // if (!n->text_.empty()) {
+  //   emitln("/* %s */", n->text_.c_str());
+  // }
+}
+
+void CodegenC::visit_block_stmt_node(BlockStmtNode* n) {
+
+  ++indent_;
+  emit("{");
+
+  // enter scope
+  auto scope = scopes_->current_var();
+  if (n->scope_) {
+    scopes_->set_current(n->scope_);
+  }
+
+
+  if (!n->stmts_.empty()) {
+    for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+      emitln("");
+      (*it)->accept(this);
+    }
+  }
+  // exit scope
+  scopes_->set_current(scope);
+
+  --indent_;
+  emitln("");
+  emit("}");
+}
+
+void CodegenC::visit_version_stmt_node(VersionStmtNode* n) {
+  uint32_t version;
+  version = MAKE_VERSION(n->major_, n->minor_, n->rev_);
+  emit("static const uint32_t  plumlet_version   __attribute__"
+      "((section (\".version\"), used)) = 0x%x;\n", version);
+}
+
+void CodegenC::visit_if_stmt_node(IfStmtNode* n) {
+  emit_comment(n);
+  emit("if (");
+  n->cond_->accept(this);
+  emit(") ");
+  n->true_block_->accept(this);
+  if (n->false_block_) {
+    emit(" else ");
+    n->false_block_->accept(this);
+  }
+}
+
+void CodegenC::visit_onvalid_stmt_node(OnValidStmtNode* n) {
+  auto sdecl = static_cast<StructVariableDeclStmtNode*>(n->cond_->decl_);
+  emit_comment(n);
+  // cheat a little not using n->cond_->accept(this) to prevent the dereference
+  emit("if (%s%s) ", sdecl->scope_id(), sdecl->id_->c_str());
+  n->block_->accept(this);
+  if (n->else_block_) {
+    emit(" else ");
+    n->else_block_->accept(this);
+  }
+}
+
+void CodegenC::visit_switch_stmt_node(SwitchStmtNode* n) {
+  emit_comment(n);
+  emit("switch (");
+  n->cond_->accept(this);
+  emit(") ");
+  n->block_->accept(this);
+}
+
+void CodegenC::visit_case_stmt_node(CaseStmtNode* n) {
+  if (n->value_) {
+    emit("case ");
+    n->value_->accept(this);
+  } else {
+    emit("default");
+  }
+  emit(": ");
+  ++indent_;
+  n->block_->accept(this);
+  emitln("");
+  emit("break;");
+  --indent_;
+}
+
+void CodegenC::visit_ident_expr_node(IdentExprNode* n) {
+  if (!n->decl_)
+    throw CompilerException("variable lookup failed: %s", n->name_.c_str());
+  if (n->decl_->storage_type_ == VariableDeclStmtNode::STRUCT_REFERENCE) {
+    if (n->sub_name_.size()) {
+      if (n->bitop_) {
+        // ident is holding a host endian number, don't use dext
+        if (n->flags_[ExprNode::IS_LHS]) {
+          emit("%s%s->%s", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str());
+        } else {
+          emit("(((%s%s->%s) >> %d) & (((%s)1 << %d) - 1))", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str(),
+              n->bitop_->bit_offset_, bits_to_uint(n->bitop_->bit_width_ + 1), n->bitop_->bit_width_);
+        }
+      } else {
+        if (n->struct_type_->id_->name_ == "_Packet" && n->sub_name_.substr(0, 3) == "arg") {
+          // convert arg1~arg8 into args[0]~args[7] assuming type_check verified the range already
+          auto arg_num = stoi(n->sub_name_.substr(3, 3));
+          if (arg_num < 5) {
+            emit("%s%s->args_lo[%d]", n->decl_->scope_id(), n->c_str(), arg_num - 1);
+          } else {
+            emit("%s%s->args_hi[%d]", n->decl_->scope_id(), n->c_str(), arg_num - 5);
+          }
+        } else {
+          emit("%s%s->%s", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str());
+        }
+      }
+    } else {
+      emit("*%s%s", n->decl_->scope_id(), n->c_str());
+    }
+  } else {
+    if (n->sub_name_.size()) {
+      emit("%s%s.%s", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str());
+    } else {
+      if (n->bitop_) {
+        // ident is holding a host endian number, don't use dext
+        if (n->flags_[ExprNode::IS_LHS]) {
+          assert(0);
+        } else {
+          emit("(((%s%s) >> %d) & (((%s)1 << %d) - 1))", n->decl_->scope_id(), n->c_str(),
+               n->bitop_->bit_offset_, bits_to_uint(n->bitop_->bit_width_ + 1), n->bitop_->bit_width_);
+        }
+      } else {
+        emit("%s%s", n->decl_->scope_id(), n->c_str());
+      }
+    }
+  }
+}
+
+void CodegenC::visit_assign_expr_node(AssignExprNode* n) {
+  if (n->bitop_) {
+    n->id_->accept(this);
+    emit(" = (");
+    n->id_->accept(this);
+    emit(" & ~((((%s)1 << %d) - 1) << %d)) | (", bits_to_uint(n->id_->bit_width_),
+         n->bitop_->bit_width_, n->bitop_->bit_offset_);
+    n->rhs_->accept(this);
+    emit(" << %d)", n->bitop_->bit_offset_);
+  } else {
+    if (n->id_->flags_[ExprNode::PROTO]) {
+      auto f = n->id_->struct_type_->field(n->id_->sub_name_);
+      emit("bpf_dins(%s%s + %zu, %zu, %zu, ", n->id_->decl_->scope_id(), n->id_->c_str(),
+           f->bit_offset_ >> 3, f->bit_offset_ & 0x7, f->bit_width_);
+      n->rhs_->accept(this);
+      emit(")");
+    } else {
+      n->id_->accept(this);
+      emit(" = ");
+      n->rhs_->accept(this);
+    }
+  }
+}
+
+void CodegenC::visit_packet_expr_node(PacketExprNode* n) {
+  auto p = proto_scopes_->top_struct()->lookup(n->id_->name_, true);
+  if (p) {
+    auto f = p->field(n->id_->sub_name_);
+    if (f) {
+      size_t bit_offset = f->bit_offset_;
+      size_t bit_width = f->bit_width_;
+      if (n->bitop_) {
+        bit_offset += f->bit_width_ - (n->bitop_->bit_offset_ + n->bitop_->bit_width_);
+        bit_width = std::min(bit_width - n->bitop_->bit_offset_, n->bitop_->bit_width_);
+      }
+      if (n->flags_[ExprNode::IS_LHS])
+        emit("bpf_dins_pkt(pkt, %s + %zu, %zu, %zu, ", n->id_->c_str(), bit_offset >> 3, bit_offset & 0x7, bit_width);
+      else
+        emit("bpf_dext_pkt(pkt, %s + %zu, %zu, %zu)", n->id_->c_str(), bit_offset >> 3, bit_offset & 0x7, bit_width);
+    } else {
+      emit("pkt->start + pkt->offset + %s", n->id_->c_str());
+    }
+  }
+}
+
+void CodegenC::visit_integer_expr_node(IntegerExprNode* n) {
+  emit("%s", n->val_.c_str());
+}
+
+void CodegenC::visit_binop_expr_node(BinopExprNode* n) {
+  n->lhs_->accept(this);
+  switch (n->op_) {
+    case Tok::TCEQ: emit(" == "); break;
+    case Tok::TCNE: emit(" != "); break;
+    case Tok::TXOR: emit(" ^ "); break;
+    case Tok::TAND: emit(" && "); break;
+    case Tok::TOR: emit(" || "); break;
+    case Tok::TMOD: emit("%");  break;
+    case Tok::TCLT: emit(" < "); break;
+    case Tok::TCLE: emit(" <= "); break;
+    case Tok::TCGT: emit(" > "); break;
+    case Tok::TCGE: emit(" >= "); break;
+    case Tok::TPLUS: emit(" + "); break;
+    case Tok::TMINUS: emit(" - "); break;
+    case Tok::TLAND: emit(" & "); break;
+    case Tok::TLOR: emit(" | "); break;
+    default: emit(" ?%d? ", n->op_); break;
+  }
+  n->rhs_->accept(this);
+}
+
+void CodegenC::visit_unop_expr_node(UnopExprNode* n) {
+  const char* s = "";
+  switch (n->op_) {
+    case Tok::TNOT: s = "!"; break;
+    case Tok::TCMPL: s = "~"; break;
+    default: {}
+  }
+  emit("%s", s);
+  n->expr_->accept(this);
+}
+
+void CodegenC::visit_bitop_expr_node(BitopExprNode* n) {
+}
+
+void CodegenC::visit_goto_expr_node(GotoExprNode* n) {
+  if (n->id_->name_ == "DONE") {
+    for (auto ii = free_instructions_.rbegin(); ii != free_instructions_.rend(); ++ii)
+      for (auto jj = ii->rbegin(); jj != ii->rend(); ++jj)
+        emitln("%s;", jj->c_str());
+    emit("goto DONE");
+    return;
+  }
+  string jump_label;
+  // when dealing with multistates, goto statements may be overridden
+  auto rewrite_it = proto_rewrites_.find(n->id_->full_name());
+  auto default_it = proto_rewrites_.find("");
+  if (rewrite_it != proto_rewrites_.end()) {
+    jump_label = rewrite_it->second;
+  } else if (default_it != proto_rewrites_.end()) {
+    jump_label = default_it->second;
+  } else {
+    auto state = scopes_->current_state()->lookup(n->id_->full_name(), false);
+    if (state) {
+      jump_label = state->scoped_name();
+      if (n->is_continue_) {
+        jump_label += "_continue";
+      }
+    } else {
+      state = scopes_->current_state()->lookup("EOP", false);
+      if (state) {
+        jump_label = state->scoped_name();
+      }
+    }
+  }
+  for (auto ii = free_instructions_.rbegin(); ii != free_instructions_.rend(); ++ii)
+    for (auto jj = ii->rbegin(); jj != ii->rend(); ++jj)
+      emitln("%s;", jj->c_str());
+  emit("goto %s", jump_label.c_str());
+}
+
+void CodegenC::emit_table_lookup(MethodCallExprNode* n) {
+  TableDeclStmtNode* table = scopes_->top_table()->lookup(n->id_->name_);
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  stringstream free_inst;
+  IdentExprNode* arg1;
+  StructVariableDeclStmtNode* arg1_type;
+
+  emitln("{ if (unlikely(pkt->capture)) {");
+  emitln("    bpf_capture(pkt, BPF_CAP_TABLE_LOOKUP, TABLE_ID_%s, 0);", n->id_->c_str());
+  emitln("} }");
+  emit("%s* %s_key = &", table->key_id()->c_str(), n->id_->c_str());
+  arg0->accept(this);
+  emitln(";");
+  emitln("%s *%s_element = (%s*)",
+         table->leaf_id()->c_str(), n->id_->c_str(), table->leaf_id()->c_str());
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED" ||
+      table->type_id()->name_ == "LPM") {
+    emit("  bpf_table_lookup(pkt, TABLE_ID_%s, %s_key)", n->id_->c_str(), n->id_->c_str());
+    if (n->args_.size() == 2) {
+      arg1 = static_cast<IdentExprNode*>(n->args_.at(1).get());
+      arg1_type = static_cast<StructVariableDeclStmtNode*>(arg1->decl_);
+      if (table->leaf_id()->name_ != arg1_type->struct_id_->name_) {
+        throw CompilerException("lookup pointer type mismatch %s != %s", table->leaf_id()->c_str(),
+                                 arg1_type->struct_id_->c_str());
+      }
+      emitln(";");
+      // cheat a little not using arg1->accept(this) to prevent the dereference
+      emit("%s%s = %s_element", arg1_type->scope_id(), arg1_type->id_->c_str(), n->id_->c_str());
+    }
+  } else {
+    throw CompilerException("lookup in table type %s unsupported", table->type_id()->c_str());
+  }
+  free_instructions_.back().push_back(free_inst.str());
+}
+
+void CodegenC::emit_table_update(MethodCallExprNode* n) {
+  TableDeclStmtNode* table = scopes_->top_table()->lookup(n->id_->name_);
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  IdentExprNode* arg1 = static_cast<IdentExprNode*>(n->args_.at(1).get());
+  IdentExprNode* type0 = table->templates_.at(0).get();
+
+  emit("%s* %s_ukey = &", type0->c_str(), n->id_->c_str());
+  arg0->accept(this);
+  emitln(";");
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED") {
+    emit("bpf_table_update(pkt, TABLE_ID_%s, %s_ukey", n->id_->c_str(), n->id_->c_str());
+    emit(", &");
+    arg1->accept(this);
+    emitln(");");
+  } else if (table->type_id()->name_ == "LPM") {
+  }
+}
+
+void CodegenC::emit_table_delete(MethodCallExprNode* n) {
+  TableDeclStmtNode* table = scopes_->top_table()->lookup(n->id_->name_);
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  IdentExprNode* type0 = table->templates_.at(0).get();
+
+  emit("%s* %s_dkey = &", type0->c_str(), n->id_->c_str());
+  arg0->accept(this);
+  emitln(";");
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED") {
+    emit("bpf_table_delete(pkt, TABLE_ID_%s, %s_dkey", n->id_->c_str(), n->id_->c_str());
+    emitln(");");
+  } else if (table->type_id()->name_ == "LPM") {
+  }
+}
+
+void CodegenC::emit_channel_push_generic(MethodCallExprNode* n) {
+  /* computation of orig_length of packet:
+   * orig_lenth = pkt->length - (orig_offset - pkt->offset)
+   * push_header(N) does pkt->length += N; pkt->offset -= N;
+   * pop_header(N) does pg_may_access(N); pkt->length -=N; pkt->offset +=N;
+   *
+   * therefore push_header(); pop_header(); sequence is currently broken, ticket #930
+   */
+  emit("bpf_channel_push_packet(pkt");
+  emit(")");
+}
+
+void CodegenC::emit_channel_push(MethodCallExprNode* n) {
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  StructVariableDeclStmtNode* arg0_type = static_cast<StructVariableDeclStmtNode*>(arg0->decl_);
+  emit("bpf_channel_push_struct(pkt, STRUCTID_%s, &", arg0_type->struct_id_->c_str());
+  arg0->accept(this);
+  emit(", sizeof(");
+  arg0->accept(this);
+  emit("))");
+}
+
+void CodegenC::emit_log(MethodCallExprNode* n) {
+  emitln("{ if (unlikely(pkt->capture)) {");
+  emit("    bpf_capture(pkt, BPF_CAP_LOG, %d, ", n->line_);
+  n->args_[0]->accept(this);
+  emit("); } }");
+}
+
+void CodegenC::emit_packet_forward(MethodCallExprNode* n) {
+  emitln("pkt->arg1 &= ~1;");
+  emit("bpf_forward(pkt, ");
+  n->args_[0]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_packet_replicate(MethodCallExprNode*n) {
+  emitln("pkt->arg1 &= ~1;");
+  emit("bpf_replicate(pkt, ");
+  n->args_[0]->accept(this);
+  emit(",", n->id_->c_str());
+  n->args_[1]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_packet_clone_forward(MethodCallExprNode* n) {
+  emitln("pkt->arg1 &= ~1;");
+  emit("bpf_clone_forward(pkt, ");
+  n->args_[0]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_packet_forward_self(MethodCallExprNode* n) {
+  emit("bpf_forward_self(pkt, ");
+  n->args_[0]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_packet_drop(MethodCallExprNode* n) {
+  emit("bpf_drop(pkt)");
+}
+
+void CodegenC::emit_packet_push_header(MethodCallExprNode* n) {
+  emit("if (unlikely(bpf_push_header(pkt, ");
+  n->args_[0]->accept(this);
+  if (n->args_.size() == 1) {
+    emit(", %zu, 0) != 0)) goto ERROR", n->args_[0]->struct_type_->bit_width_ >> 3);
+  } else {
+    emit(", %zu, ", n->args_[0]->struct_type_->bit_width_ >> 3);
+    n->args_[1]->accept(this);
+    emit(") != 0)) goto ERROR");
+  }
+}
+
+void CodegenC::emit_packet_pop_header(MethodCallExprNode* n) {
+  emit("if (unlikely(bpf_pop_header(pkt, ");
+  if (n->args_[0]->typeof_ == ExprNode::STRUCT) {
+    emit("%zu", n->args_[0]->struct_type_->bit_width_ >> 3);
+  } else if (n->args_[0]->typeof_ == ExprNode::INTEGER) {
+    n->args_[0]->accept(this);
+  }
+  emit(", 0/*todo*/) != 0)) goto ERROR");
+}
+
+void CodegenC::emit_packet_push_vlan(MethodCallExprNode* n) {
+  emit("if (unlikely(bpf_push_vlan(pkt, bpf_htons(0x8100/*ETH_P_8021Q*/), ");
+  n->args_[0]->accept(this);
+  emit(") != 0)) goto ERROR");
+}
+
+void CodegenC::emit_packet_pop_vlan(MethodCallExprNode* n) {
+  emit("if (unlikely(bpf_pop_vlan(pkt) != 0)) goto ERROR");
+}
+
+void CodegenC::emit_packet_rewrite_field(MethodCallExprNode* n) {
+  n->args_[0]->accept(this);
+  n->args_[1]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_atomic_add(MethodCallExprNode* n) {
+  emit("__sync_fetch_and_add(&");
+  n->args_[0]->accept(this);
+  emit(", ");
+  n->args_[1]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_cksum(MethodCallExprNode* n) {
+  if (n->args_[0]->typeof_ == ExprNode::STRUCT) {
+    auto v = n->args_[0]->struct_type_;
+    size_t bit_width = v->bit_width_ >> 3;
+    auto p = proto_scopes_->top_struct()->lookup(v->id_->name_, true);
+    if (p) {
+      /* should we do store_half directly? */
+      if (!n->args_[0]->flags_[ExprNode::PROTO]) {
+        emit("bpf_ntohs(bpf_checksum_pkt(pkt, %s, %zu))", v->id_->c_str(), bit_width);
+      } else {
+        emit("bpf_ntohs(bpf_checksum(");
+        n->args_[0]->accept(this);
+        emit(", %zu))", bit_width);
+      }
+    } else {
+      throw CompilerException("cannot pg_cksum %d", n->args_[0]->typeof_);
+    }
+/**    emit("pg_cksum(");
+    n->args_[0]->accept(this);
+    emit(", %zu)", n->args_[0]->struct_type_->bit_width_ >> 3);**/
+  } else {
+    throw CompilerException("cannot pg_cksum %d", n->args_[0]->typeof_);
+  }
+}
+
+void CodegenC::emit_incr_cksum_u16(MethodCallExprNode* n) {
+  if (n->args_.size() == 3) {
+    /* ip checksum */
+    emit("bpf_ntohs(bpf_csum_replace2(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[2]->accept(this);
+    emit(")))");
+  } else {
+    /* L4 checksum */
+    emit("(");
+    /* part of pseudo header */
+    n->args_[3]->accept(this);
+    emit(" ? ");
+    emit("((pkt->hw_csum == 1) ? ");
+    /* CHECKSUM_PARTIAL update pseudo only */
+    emit("bpf_ntohs(bpf_pseudo_csum_replace2(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[2]->accept(this);
+    emit(")))");
+    emit(" : ");
+    /* CHECKSUM_NONE update normally */
+    emit("bpf_ntohs(bpf_csum_replace2(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[2]->accept(this);
+    emit(")))");
+    emit(")");
+    emit(" : ");
+    /* not part of pseudo */
+    emit("((pkt->hw_csum != 1) ? ");
+    /* CHECKSUM_NONE update normally */
+    emit("bpf_ntohs(bpf_csum_replace2(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htons(");
+    n->args_[2]->accept(this);
+    emit(")))");
+    emit(" : ");
+    /* CHECKSUM_PARTIAL no-op */
+    n->args_[0]->accept(this);
+    emit("))");
+  }
+}
+
+void CodegenC::emit_incr_cksum_u32(MethodCallExprNode* n) {
+  if (n->args_.size() == 3) {
+    /* ip checksum */
+    emit("bpf_ntohs(bpf_csum_replace4(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[2]->accept(this);
+    emit(")))");
+  } else {
+    /* L4 checksum */
+    emit("(");
+    /* part of pseudo header */
+    n->args_[3]->accept(this);
+    emit(" ? ");
+    emit("((pkt->hw_csum == 1) ? ");
+    /* CHECKSUM_PARTIAL update pseudo only */
+    emit("bpf_ntohs(bpf_pseudo_csum_replace4(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[2]->accept(this);
+    emit(")))");
+    emit(" : ");
+    /* CHECKSUM_NONE update normally */
+    emit("bpf_ntohs(bpf_csum_replace4(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[2]->accept(this);
+    emit(")))");
+    emit(")");
+    emit(" : ");
+    /* not part of pseudo */
+    emit("((pkt->hw_csum != 1) ? ");
+    /* CHECKSUM_NONE updata normally */
+    emit("bpf_ntohs(bpf_csum_replace4(bpf_htons(");
+    n->args_[0]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[1]->accept(this);
+    emit("), bpf_htonl(");
+    n->args_[2]->accept(this);
+    emit(")))");
+    emit(" : ");
+    /* CHECKSUM_PARTIAL no-op */
+    n->args_[0]->accept(this);
+    emit("))");
+  }
+}
+
+void CodegenC::emit_lb_hash(MethodCallExprNode* n) {
+  emit("pg_lb_hash(");
+  n->args_[0]->accept(this);
+  emit(", ");
+  n->args_[1]->accept(this);
+  emit(")");
+}
+
+void CodegenC::emit_sizeof(MethodCallExprNode* n) {
+  if (n->args_[0]->typeof_ == ExprNode::STRUCT) {
+    if (n->args_[0]->struct_type_->id_->name_ == "_Packet") {
+      emit("PG_SIZEOF(pkt)");
+    } else {
+      emit("%zu", n->args_[0]->struct_type_->bit_width_ >> 3);
+    }
+  } else if (n->args_[0]->typeof_ == ExprNode::INTEGER) {
+    emit("%zu", n->args_[0]->bit_width_ >> 3);
+  }
+}
+
+void CodegenC::emit_get_usec_time(MethodCallExprNode* n) {
+  emit("bpf_get_usec_time()");
+}
+
+void CodegenC::emit_forward_to_vnf(MethodCallExprNode*n) {
+  emitln("pkt->arg1 |= 1;");
+  emit("pkt->arg2 = ");
+  n->args_[0]->accept(this);
+  emitln(";");
+  emit("bpf_forward_to_plum(pkt, ");
+  n->args_[1]->accept(this);
+  emit(")");
+
+}
+
+void CodegenC::emit_forward_to_group(MethodCallExprNode *n) {
+
+  emit("pkt->arg2 = ");
+  n->args_[0]->accept(this);
+  emitln(";");
+  emitln("pkt->arg3 = pkt->plum_id;");
+  emit("bpf_forward_to_plum(pkt, ");
+  emit("1/*TUNNEL_PLUM_ID*/");
+  emit(")");
+
+}
+
+void CodegenC::visit_method_call_expr_node(MethodCallExprNode* n) {
+  free_instructions_.push_back(vector<string>());
+
+  if (!n->stmts_.empty()) {
+    ++indent_;
+    emitln("{");
+  }
+
+  if (n->id_->sub_name_.size()) {
+    if (n->id_->sub_name_ == "lookup") {
+      emit_table_lookup(n);
+    } else if (n->id_->sub_name_ == "update") {
+      emit_table_update(n);
+    } else if (n->id_->sub_name_ == "delete") {
+      emit_table_delete(n);
+    } else if (n->id_->sub_name_ == "replicate" && n->id_->name_ == "pkt") {
+      emit_packet_replicate(n);
+    } else if (n->id_->sub_name_ == "forward" && n->id_->name_ == "pkt") {
+      emit_packet_forward(n);
+    } else if (n->id_->sub_name_ == "forward_self" && n->id_->name_ == "pkt") {
+      emit_packet_forward_self(n);
+    } else if (n->id_->sub_name_ == "push_header" && n->id_->name_ == "pkt") {
+      emit_packet_push_header(n);
+    } else if (n->id_->sub_name_ == "pop_header" && n->id_->name_ == "pkt") {
+      emit_packet_pop_header(n);
+    } else if (n->id_->sub_name_ == "push_vlan" && n->id_->name_ == "pkt") {
+      emit_packet_push_vlan(n);
+    } else if (n->id_->sub_name_ == "pop_vlan" && n->id_->name_ == "pkt") {
+      emit_packet_pop_vlan(n);
+    } else if (n->id_->sub_name_ == "rewrite_field" && n->id_->name_ == "pkt") {
+      emit_packet_rewrite_field(n);
+    } else if (n->id_->sub_name_ == "clone_forward" && n->id_->name_ == "pkt") {
+      emit_packet_clone_forward(n);
+    }
+  } else if (n->id_->name_ == "atomic_add") {
+    emit_atomic_add(n);
+  } else if (n->id_->name_ == "log") {
+    emit_log(n);
+  } else if (n->id_->name_ == "cksum") {
+    emit_cksum(n);
+  } else if (n->id_->name_ == "incr_cksum_u16") {
+    emit_incr_cksum_u16(n);
+  } else if (n->id_->name_ == "incr_cksum_u32") {
+    emit_incr_cksum_u32(n);
+  } else if (n->id_->name_ == "lb_hash") {
+    emit_lb_hash(n);
+  } else if (n->id_->name_ == "sizeof") {
+    emit_sizeof(n);
+  } else if (n->id_->name_ == "get_usec_time") {
+    emit_get_usec_time(n);
+  } else if (n->id_->name_ == "channel_push") {
+    emit_channel_push(n);
+  } else if (n->id_->name_ == "channel_push_generic") {
+    emit_channel_push_generic(n);
+  } else if (n->id_->name_ == "forward_to_vnf") {
+    emit_forward_to_vnf(n);
+  } else if (n->id_->name_ == "forward_to_group") {
+    emit_forward_to_group(n);
+  } else {
+    n->id_->accept(this);
+    emit("(");
+    for (auto it = n->args_.begin(); it != n->args_.end(); ++it) {
+      (*it)->accept(this);
+      if (it + 1 != n->args_.end()) {
+        emit(", ");
+      }
+    }
+    emit(")");
+  }
+  if (!n->stmts_.empty()) {
+    emit(";");
+    for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+      lnemit("");
+      (*it)->accept(this);
+    }
+    for (auto it = free_instructions_.back().rbegin(); it != free_instructions_.back().rend(); ++it) {
+      lnemit("%s;", it->c_str());
+    }
+    --indent_;
+    lnemit("}");
+  }
+  free_instructions_.pop_back();
+}
+
+/// on_match
+void CodegenC::visit_match_decl_stmt_node(MatchDeclStmtNode* n) {
+  if (n->formals_.size() != 2)
+    throw CompilerException("on_match expected 2 arguments, %zu given", n->formals_.size());
+  StructVariableDeclStmtNode* key_n = static_cast<StructVariableDeclStmtNode*>(n->formals_.at(0).get());
+  StructVariableDeclStmtNode* leaf_n = static_cast<StructVariableDeclStmtNode*>(n->formals_.at(1).get());
+  if (!key_n || !leaf_n)
+    throw CompilerException("invalid parameter type");
+  ++indent_;
+  emitln("if (%s_element) {", n->id_->c_str());
+  emitln("%s* %s%s = %s_key;", key_n->struct_id_->c_str(), key_n->scope_id(),
+         key_n->id_->c_str(), n->id_->c_str());
+  emitln("%s* %s%s = %s_element;", leaf_n->struct_id_->c_str(), leaf_n->scope_id(),
+         leaf_n->id_->c_str(), n->id_->c_str());
+  n->block_->accept(this);
+  --indent_;
+  emitln("");
+  emit("}");
+}
+
+/// on_miss
+void CodegenC::visit_miss_decl_stmt_node(MissDeclStmtNode* n) {
+  if (n->formals_.size() != 1)
+    throw CompilerException("on_match expected 1 argument, %zu given", n->formals_.size());
+  StructVariableDeclStmtNode* key_n = static_cast<StructVariableDeclStmtNode*>(n->formals_.at(0).get());
+  ++indent_;
+  emitln("if (!%s_element) {", n->id_->c_str());
+  emitln("%s* %s%s = %s_key;", key_n->struct_id_->c_str(),
+         key_n->scope_id(), key_n->id_->c_str(), n->id_->c_str());
+  n->block_->accept(this);
+  --indent_;
+  emitln("");
+  emit("}");
+}
+
+void CodegenC::visit_failure_decl_stmt_node(FailureDeclStmtNode* n) {
+  if (n->formals_.size() != 1)
+    throw CompilerException("on_failure expected 1 argument, %zu given", n->formals_.size());
+  StructVariableDeclStmtNode* key_n = static_cast<StructVariableDeclStmtNode*>(n->formals_.at(0).get());
+  ++indent_;
+  emitln("/*if ((unsigned long)%s_element >= (unsigned long)-4095) {", n->id_->name_.c_str());
+  emitln("%s* %s%s = %s_key;", key_n->struct_id_->c_str(),
+         key_n->scope_id(), key_n->id_->c_str(), n->id_->c_str());
+  n->block_->accept(this);
+  --indent_;
+  emitln("");
+  emit("}*/");
+}
+
+void CodegenC::visit_expr_stmt_node(ExprStmtNode* n) {
+  emit_comment(n);
+  n->expr_->accept(this);
+  emit(";");
+}
+
+void CodegenC::visit_struct_variable_decl_stmt_node(StructVariableDeclStmtNode* n) {
+  if (n->struct_id_->name_ == "" || n->struct_id_->name_[0] == '_') {
+    return;
+  }
+  emit_comment(n);
+  if (n->struct_id_->scope_name_ == "proto") {
+    auto p = proto_scopes_->top_struct()->lookup(n->struct_id_->name_, true);
+    if (p) {
+      string var = n->scope_id() + n->id_->name_;
+      /* zero initialize array to be filled in with packet header */
+      emit("uint64_t __%s[%zu] = {}; uint8_t *%s = (uint8_t*)__%s;",
+           var.c_str(), ((p->bit_width_ >> 3) + 7) >> 3, var.c_str(), var.c_str());
+      for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+        auto asn = static_cast<AssignExprNode*>(it->get());
+        if (auto f = p->field(asn->id_->sub_name_)) {
+          size_t bit_offset = f->bit_offset_;
+          size_t bit_width = f->bit_width_;
+          if (asn->bitop_) {
+            bit_offset += f->bit_width_ - (asn->bitop_->bit_offset_ + asn->bitop_->bit_width_);
+            bit_width = std::min(bit_width - asn->bitop_->bit_offset_, asn->bitop_->bit_width_);
+          }
+          emit(" bpf_dins(%s + %zu, %zu, %zu, ", var.c_str(), bit_offset >> 3, bit_offset & 0x7, bit_width);
+          asn->rhs_->accept(this);
+          emit(");");
+        }
+      }
+    }
+  } else {
+    /* all structs must be initialized with zeros, since they're alocated on stack,
+     * if struct doesn't have gaps between fields, gcc will be smart enough to avoid redundant zeroing */
+    if (n->storage_type_ == VariableDeclStmtNode::STRUCT_REFERENCE) {
+      emit("%s* %s%s = 0;", n->struct_id_->c_str(), n->scope_id(), n->id_->c_str());
+    } else {
+      emit("%s %s%s = {};", n->struct_id_->c_str(), n->scope_id(), n->id_->c_str());
+      if (!n->init_.empty()) {
+        for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+          emit(" ");
+          (*it)->accept(this);
+          emit(";");
+        }
+      }
+    }
+  }
+}
+
+void CodegenC::visit_integer_variable_decl_stmt_node(IntegerVariableDeclStmtNode* n) {
+  if (n->id_->name_ == "timer_delay" || n->id_->name_ == "parsed_bytes")
+    return;
+  emit_comment(n);
+  emit("%s %s%s", bits_to_uint(n->bit_width_), n->scope_id(), n->id_->c_str());
+  if (!n->scope_id_.empty())
+    emit(" = 0");
+  if (!n->init_.empty()) {
+    emit("; ");
+    n->init_[0]->accept(this);
+  }
+  emit(";");
+}
+
+void CodegenC::visit_struct_decl_stmt_node(StructDeclStmtNode* n) {
+  emit("typedef struct {\n");
+  ++indent_;
+  for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+    indent();
+    (*it)->accept(this);
+    emit("\n");
+  }
+  --indent_;
+  indent();
+  emit("} __attribute__((aligned(4))) ");
+  emit("%s", n->id_->c_str());
+}
+
+void CodegenC::visit_parser_state_stmt_node(ParserStateStmtNode* n) {
+  string jump_label = n->scoped_name() + "_continue";
+  emit("%s: {", jump_label.c_str());
+  ++indent_;
+  lnemit("PG_TRACE(%.14s);", jump_label.c_str());
+  if (n->next_state_) {
+    lnemit("");
+    n->next_state_->accept(this);
+  }
+  --indent_;
+  lnemit("}");
+}
+
+void CodegenC::visit_timer_decl_stmt_node(TimerDeclStmtNode* n) {
+  auto scope = scopes_->current_state();
+  scopes_->set_current(n->scope_);
+  n->block_->accept(this);
+  scopes_->set_current(scope);
+}
+void CodegenC::visit_state_decl_stmt_node(StateDeclStmtNode* n) {
+  if (!n->id_) {
+    return;
+  }
+  string jump_label = n->scoped_name();
+  ++indent_;
+  emitln("JUMP_GUARD; %s: {", jump_label.c_str());
+  emitln("PG_TRACE(%.14s);", jump_label.c_str());
+  if (auto p = proto_scopes_->top_struct()->lookup(n->id_->name_, true)) {
+    emitln("%s = parsed_bytes; /* remember the offset of this header */", n->id_->c_str());
+    emitln("parsed_bytes += %zu;", p->bit_width_ >> 3);
+    //emitln("if (!pg_may_access(pkt, parsed_bytes)) goto ERROR; /* pull data from fragments to access this header */");
+  }
+  // collect the protocols used in this state scope and declare them
+  set<string> protos;
+  for (auto it = n->subs_.begin(); it != n->subs_.end(); ++it) {
+    if (!it->scope_) {
+      continue;
+    }
+    auto scope = scopes_->current_state();
+    scopes_->set_current(it->scope_);
+    for (auto it2 = scopes_->current_state()->obegin(); it2 != scopes_->current_state()->oend(); ++it2) {
+      if (proto_scopes_->top_struct()->lookup((*it2)->id_->name_, true)) {
+        protos.insert((*it2)->id_->name_);
+      }
+      for (auto it3 = (*it2)->subs_.begin(); it3 != (*it2)->subs_.end(); ++it3) {
+        if (proto_scopes_->top_struct()->lookup(it3->id_->name_, true)) {
+          protos.insert(it3->id_->name_);
+        }
+      }
+    }
+    scopes_->set_current(scope);
+  }
+  for (auto it = protos.begin(); it != protos.end(); ++it) {
+    emitln("uint32_t %s = 0; /* header offset */", it->c_str());
+  }
+
+  auto it = n->subs_.begin();
+  if (n->subs_.size() == 1 && it->id_->name_ == "") {
+    // this is not a multistate protocol, emit everything and finish
+    auto scope = scopes_->current_state();
+    scopes_->set_current(it->scope_);
+    it->block_->accept(this);
+    if (n->parser_) {
+      emitln("");
+      n->parser_->accept(this);
+    }
+    scopes_->set_current(scope);
+  } else {
+    if (n->parser_) {
+      for (auto it2 = n->subs_.begin(); it2 != n->subs_.end(); ++it2) {
+        proto_rewrites_[it2->id_->full_name()] = n->scoped_name() + "_" + it2->id_->name_;
+      }
+      n->parser_->accept(this);
+      proto_rewrites_.clear();
+      emitln("");
+    }
+    for (; it != n->subs_.end(); ++it) {
+      auto scope = scopes_->current_state();
+      scopes_->set_current(it->scope_);
+
+      string jump_label = n->scoped_name() + "_" + it->id_->name_;
+      ++indent_;
+      emitln("JUMP_GUARD; %s: {", jump_label.c_str());
+      emitln("PG_TRACE(%.14s);", jump_label.c_str());
+      if (auto p = proto_scopes_->top_struct()->lookup(it->id_->name_, true)) {
+        emitln("%s = pkt->offset + parsed_bytes; /* remember the offset of this header */", it->id_->c_str());
+        emitln("parsed_bytes += %zu;", p->bit_width_ >> 3);
+        emitln("if (!pg_may_access(pkt, parsed_bytes)) goto ERROR; /* pull data from fragments to access this header */");
+      }
+      it->block_->accept(this);
+      if (it->parser_) {
+        emitln("");
+        it->parser_->accept(this);
+      }
+      --indent_;
+      emitln("");
+      emitln("}");
+
+      scopes_->set_current(scope);
+    }
+  }
+
+  --indent_;
+  emitln("");
+  emit("}");
+}
+
+void CodegenC::visit_table_decl_stmt_node(TableDeclStmtNode* n) {
+  if (n->table_type_->name_ == "Table"
+      || n->table_type_->name_ == "SharedTable") {
+    if (n->templates_.size() != 4)
+      throw CompilerException("%s expected 4 arguments, %zu given", n->table_type_->c_str(), n->templates_.size());
+    const char *key_type = n->key_id()->c_str();
+    const char *leaf_type = n->leaf_id()->c_str();
+    char buf[128];
+    if (n->type_id()->name_ == "FIXED_MATCH" || n->type_id()->name_ == "INDEXED") {
+      //emitln("struct %s_Element {", n->id_->c_str());
+      //emitln("  PG_HASH_TABLE_ELEMENT_COMMON");
+      //emitln("  %s key;", key_type);
+      //emitln("  %s leaf;", leaf_type);
+      //emitln("} __attribute__((aligned(8)));");
+      //emitln("static struct PGHashTable %s;", n->id_->c_str());
+      //emitln("#define N_BUCKETS_%s %zu", n->id_->c_str(), n->size_);
+      //emitln("PG_HASH_TABLE_DECL(%d, %s, sizeof(%s), sizeof(struct %s_Element), N_BUCKETS_%s)",
+      //       table_inits_.size(), n->id_->c_str(), key_type, n->id_->c_str(), n->id_->c_str());
+      emitln("#define TABLE_ID_%s %zd", n->id_->c_str(), table_inits_.size());
+      snprintf(buf, sizeof(buf), "[%zd] = {%zd, PG_TABLE_HASH, sizeof(%s), sizeof(%s), %zd, 0}, // %s",
+               table_inits_.size(), table_inits_.size(), key_type, leaf_type, n->size_, n->id_->c_str());
+    } else if (n->type_id()->name_ == "LPM") {
+      //emitln("struct %s_Element {", n->id_->c_str());
+      //emitln("  PG_LPM_TABLE_ELEMENT_COMMON");
+      //emitln("  %s key;", key_type);
+      //emitln("  %s leaf;", leaf_type);
+      //emitln("} __attribute__((aligned(8)));");
+      //emitln("static struct PGLpmTable %s;", n->id_->c_str());
+      //emitln("#define N_BUCKETS_%s %zu", n->id_->c_str(), n->size_);
+      //emitln("PG_LPM_TABLE_DECL(%d, %s, sizeof(%s), sizeof(struct %s_Element), N_BUCKETS_%s, %u)",
+      //       table_inits_.size(), n->id_->c_str(), key_type, n->id_->c_str(), n->id_->c_str(),
+      //       n->key_id()->bit_width_);
+      emitln("#define TABLE_ID_%s %zd", n->id_->c_str(), table_inits_.size());
+      snprintf(buf, sizeof(buf), "[%zd] = {%zd, PG_TABLE_LPM, sizeof(%s), sizeof(%s), %zd, %zd}, // %s",
+               table_inits_.size(), table_inits_.size(), key_type, leaf_type, n->size_,
+               n->key_id()->bit_width_, n->id_->c_str());
+    } else {
+      throw CompilerException("table type \"%s\" unknown", n->type_id()->c_str());
+    }
+    //table_inits_.push_back(n->id_->name_);
+    table_inits_.push_back(buf);
+  }
+}
+
+int CodegenC::visit(Node* root) {
+  BlockStmtNode* b = static_cast<BlockStmtNode*>(root);
+
+
+  scopes_->set_current(scopes_->top_state());
+  scopes_->set_current(scopes_->top_var());
+
+  print_header();
+
+  b->ver_.accept(this);
+
+  for (auto it = scopes_->top_table()->obegin(); it != scopes_->top_table()->oend(); ++it) {
+    (*it)->accept(this);
+    emit("\n");
+  }
+
+  print_parser();
+
+  print_footer();
+
+  return 0;
+}
+
+void CodegenC::print_timer() {
+  // visit timers
+  ++indent_;
+  emitln("PG_PARSE_DECL(timer) {");
+  emitln("uint32_t timer_delay = 0;");
+  // visit function scoped variables
+  for (auto it = scopes_->current_var()->obegin(); it != scopes_->current_var()->oend(); ++it) {
+    (*it)->accept(this);
+    emitln("");
+  }
+  for (auto it = scopes_->top_timer()->obegin(); it != scopes_->top_timer()->oend(); ++it) {
+    (*it)->accept(this);
+    emitln("");
+  }
+  ++indent_;
+  emitln("DONE: {");
+  emitln("PG_TRACE(DONE);");
+  emitln("pg_timer_forward(pkt, timer_delay);");
+  --indent_;
+  emitln("return;");
+  emitln("}");
+
+  ++indent_;
+  emitln("ERROR: {");
+  emitln("PG_TRACE(ERROR);");
+  emitln("pg_drop(pkt);");
+  emitln("pg_timer_forward(pkt, timer_delay);");
+  --indent_;
+  emitln("return;");
+  --indent_;
+  emitln("}");
+  emitln("}");
+}
+
+void CodegenC::print_parser() {
+  ++indent_;
+  emitln("PG_PARSE_DECL(parse) {");
+  /* emitln("uint8_t *pp;"); */
+  emitln("uint32_t parsed_bytes = 0;");
+  emitln("uint16_t orig_offset = 0;/*pkt->offset;*/");
+
+  // visit function scoped variables
+  for (auto it = scopes_->current_var()->obegin(); it != scopes_->current_var()->oend(); ++it) {
+    (*it)->accept(this);
+    emitln("");
+  }
+
+  for (auto it = scopes_->current_state()->obegin(); it != scopes_->current_state()->oend(); ++it) {
+    if (proto_scopes_->top_struct()->lookup((*it)->id_->name_, true)) {
+      emitln("uint32_t %s = 0; /* header offset */", (*it)->id_->c_str());
+    }
+  }
+
+  /* emitln("pp = pkt->start + pkt->offset;"); */
+  emitln("goto s1_INIT;");
+
+  // finally, visit the states
+  for (auto it = scopes_->current_state()->obegin(); it != scopes_->current_state()->oend(); ++it) {
+    (*it)->accept(this);
+    emitln("");
+  }
+
+  ++indent_;
+  emitln("ERROR: {");
+  emitln("PG_TRACE(ERROR);");
+  --indent_;
+  emitln("goto CLEANUP;");
+  emitln("}");
+
+  ++indent_;
+  emitln("DONE: {");
+  emitln("PG_TRACE(DONE);");
+  --indent_;
+  emitln("goto CLEANUP;");
+  emitln("}");
+
+  ++indent_;
+  emitln("CLEANUP: {");
+  --indent_;
+  emitln("/* cleanup is done by PE */;");
+  --indent_;
+  emitln("}");
+
+  emitln("}");
+
+  //print_timer();
+}
+
+void CodegenC::print_header() {
+  if (use_pre_header_) {
+    //emit("%s", PRE_HEADER.c_str());
+    emitln("");
+  } else {
+    emitln("#include <stdint.h>");
+    emitln("#include \"../dp/linux/filter.h\"");
+    emitln("#include \"container/pg_api.h\"");
+    emitln("#include \"container/pg_defs.h\"");
+  }
+  emitln("#define JUMP_GUARD goto DONE");
+  emitln("#define PG_SIZEOF(_pkt) ((int)_pkt->length - (int)pkt->offset + orig_offset)");
+
+  int i = 0;
+  // declare structures
+  for (auto it = scopes_->top_struct()->obegin(); it != scopes_->top_struct()->oend(); ++it) {
+    if ((*it)->id_->name_ == "_Packet")
+      continue;
+    (*it)->accept(this);
+    emit(";\n");
+    emitln("#define STRUCTID_%s %d", (*it)->id_->c_str(), i++);
+  }
+  emitln("#define STRUCTID_generic %d", i);
+}
+
+void CodegenC::print_footer() {
+  //emitln("#define EXPAND_TABLES(E) \\");
+  emitln("struct bpf_table plum_tables[] = {");
+  for (auto it = table_inits_.begin(); it != table_inits_.end(); ++it) {
+    //emit("E(%s) ", it->c_str());
+    emitln("  %s", it->c_str());
+  }
+  emitln("  {0,0,0,0,0,0} // last table marker");
+  emitln("};");
+  emitln("");
+  emitln("PG_INIT");
+  emitln("PG_CLEANUP");
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/codegen_c.h b/jit/src/cc/codegen_c.h
new file mode 100644
index 0000000..747e1ad
--- /dev/null
+++ b/jit/src/cc/codegen_c.h
@@ -0,0 +1,103 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <vector>
+#include <string>
+#include <set>
+
+#include "cc/node.h"
+#include "cc/scope.h"
+
+namespace ebpf {
+namespace cc {
+
+using std::vector;
+using std::string;
+using std::set;
+
+class CodegenC : public Visitor {
+ public:
+  CodegenC(FILE* out, Scopes::Ptr scopes, Scopes::Ptr proto_scopes, bool use_pre_header)
+      : out_(out), indent_(0), tmp_reg_index_(0), scopes_(scopes),
+      proto_scopes_(proto_scopes), use_pre_header_(use_pre_header) {}
+
+#define VISIT(type, func) virtual void visit_##func(type* n);
+  EXPAND_NODES(VISIT)
+#undef VISIT
+
+  virtual int visit(Node* n);
+
+  void emit_table_lookup(MethodCallExprNode* n);
+  void emit_table_update(MethodCallExprNode* n);
+  void emit_table_delete(MethodCallExprNode* n);
+  void emit_channel_push(MethodCallExprNode* n);
+  void emit_channel_push_generic(MethodCallExprNode* n);
+  void emit_log(MethodCallExprNode* n);
+  void emit_packet_forward(MethodCallExprNode* n);
+  void emit_packet_replicate(MethodCallExprNode* n);
+  void emit_packet_clone_forward(MethodCallExprNode* n);
+  void emit_packet_forward_self(MethodCallExprNode* n);
+  void emit_packet_drop(MethodCallExprNode* n);
+  void emit_packet_broadcast(MethodCallExprNode* n);
+  void emit_packet_multicast(MethodCallExprNode* n);
+  void emit_packet_push_header(MethodCallExprNode* n);
+  void emit_packet_pop_header(MethodCallExprNode* n);
+  void emit_packet_push_vlan(MethodCallExprNode* n);
+  void emit_packet_pop_vlan(MethodCallExprNode* n);
+  void emit_packet_rewrite_field(MethodCallExprNode* n);
+  void emit_atomic_add(MethodCallExprNode* n);
+  void emit_cksum(MethodCallExprNode* n);
+  void emit_incr_cksum_u16(MethodCallExprNode* n);
+  void emit_incr_cksum_u32(MethodCallExprNode* n);
+  void emit_lb_hash(MethodCallExprNode* n);
+  void emit_sizeof(MethodCallExprNode* n);
+  void emit_get_usec_time(MethodCallExprNode* n);
+  void emit_forward_to_vnf(MethodCallExprNode* n);
+  void emit_forward_to_group(MethodCallExprNode* n);
+  void print_parser();
+  void print_timer();
+  void print_header();
+  void print_footer();
+
+ private:
+  void indent();
+
+  template <typename... Args> void emitln(const char *fmt, Args&&... params);
+  template <typename... Args> void lnemit(const char *fmt, Args&&... params);
+  template <typename... Args> void emit(const char *fmt, Args&&... params);
+  void emitln(const char *s);
+  void lnemit(const char *s);
+  void emit(const char *s);
+  void emit_comment(Node* n);
+
+  FILE* out_;
+  int indent_;
+  int tmp_reg_index_;
+  Scopes::Ptr scopes_;
+  Scopes::Ptr proto_scopes_;
+  bool use_pre_header_;
+  vector<vector<string> > free_instructions_;
+  vector<string> table_inits_;
+  map<string, string> proto_rewrites_;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/codegen_llvm.cc b/jit/src/cc/codegen_llvm.cc
new file mode 100644
index 0000000..5d948d0
--- /dev/null
+++ b/jit/src/cc/codegen_llvm.cc
@@ -0,0 +1,1597 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#include <set>
+#include <algorithm>
+#include <sstream>
+#include <assert.h>
+
+#include <llvm/IR/BasicBlock.h>
+#include <llvm/IR/CallingConv.h>
+#include <llvm/IR/CFG.h>
+#include <llvm/IR/Constants.h>
+#include <llvm/IR/DerivedTypes.h>
+#include <llvm/IR/Function.h>
+#include <llvm/IR/GlobalVariable.h>
+#include <llvm/IR/InlineAsm.h>
+#include <llvm/IR/Instructions.h>
+#include <llvm/IR/IRPrintingPasses.h>
+#include <llvm/IR/IRBuilder.h>
+#include <llvm/IR/LLVMContext.h>
+#include <llvm/IR/Module.h>
+
+#include "exception.h"
+#include "cc/codegen_llvm.h"
+#include "cc/lexer.h"
+#include "cc/type_helper.h"
+#include "linux/bpf.h"
+
+extern "C"
+int bpf_create_map(int map_type, int key_size, int value_size, int max_entries);
+
+#define ENABLE_RELOCATIONS 0
+
+namespace ebpf {
+namespace cc {
+
+using namespace llvm;
+
+using std::for_each;
+using std::make_tuple;
+using std::pair;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::to_string;
+using std::vector;
+
+// can't forward declare IRBuilder in .h file (template with default
+// parameters), so cast it instead :(
+#define B (*((IRBuilder<> *)this->b_))
+
+class BlockStack {
+ public:
+  explicit BlockStack(CodegenLLVM *cc, BasicBlock *bb)
+    : old_bb_(cc->b_->GetInsertBlock()), cc_(cc) {
+    cc_->b_->SetInsertPoint(bb);
+  }
+  ~BlockStack() {
+    if (old_bb_)
+      cc_->b_->SetInsertPoint(old_bb_);
+    else
+      cc_->b_->ClearInsertionPoint();
+  }
+ private:
+  BasicBlock *old_bb_;
+  CodegenLLVM *cc_;
+};
+
+class SwitchStack {
+ public:
+  explicit SwitchStack(CodegenLLVM *cc, SwitchInst *sw)
+    : old_sw_(cc->cur_switch_), cc_(cc) {
+    cc_->cur_switch_ = sw;
+  }
+  ~SwitchStack() {
+    cc_->cur_switch_ = old_sw_;
+  }
+ private:
+  SwitchInst *old_sw_;
+  CodegenLLVM *cc_;
+};
+
+CodegenLLVM::CodegenLLVM(llvm::Module *mod, Scopes *scopes, Scopes *proto_scopes,
+                         bool use_pre_header, const string &section)
+  : out_(stdout), mod_(mod), indent_(0), tmp_reg_index_(0), scopes_(scopes),
+    proto_scopes_(proto_scopes), use_pre_header_(use_pre_header),
+    section_(section), expr_(nullptr) {
+  b_ = new IRBuilder<>(ctx());
+}
+CodegenLLVM::~CodegenLLVM() {
+  delete b_;
+}
+
+template <typename... Args>
+void CodegenLLVM::emitln(const char *fmt, Args&&... params) {
+  //fprintf(out_, fmt, std::forward<Args>(params)...);
+  //fprintf(out_, "\n%*s", indent_ * 2, "");
+  //fflush(out_);
+}
+void CodegenLLVM::emitln(const char *s) {
+  //fprintf(out_, "%s", s);
+  //fprintf(out_, "\n%*s", indent_ * 2, "");
+  //fflush(out_);
+}
+
+template <typename... Args>
+void CodegenLLVM::emit(const char *fmt, Args&&... params) {
+  //fprintf(out_, fmt, std::forward<Args>(params)...);
+  //fflush(out_);
+}
+void CodegenLLVM::emit(const char *s) {
+  //fprintf(out_, "%s", s);
+  //fflush(out_);
+}
+
+template <typename... Args>
+void CodegenLLVM::lnemit(const char *fmt, Args&&... params) {
+  //fprintf(out_, "\n%*s", indent_ * 2, "");
+  //fprintf(out_, fmt, std::forward<Args>(params)...);
+  //fflush(out_);
+}
+void CodegenLLVM::lnemit(const char *s) {
+  //fprintf(out_, "\n%*s", indent_ * 2, "");
+  //fprintf(out_, "%s", s);
+  //fflush(out_);
+}
+
+void CodegenLLVM::indent() {
+  //fprintf(out_, "%*s", indent_ * 2, "");
+  //fflush(out_);
+}
+
+void CodegenLLVM::emit_comment(Node *n) {
+  // if (!n->text_.empty()) {
+  //   emitln("/* %s */", n->text_.c_str());
+  // }
+}
+
+StatusTuple CodegenLLVM::visit_block_stmt_node(BlockStmtNode *n) {
+
+  // enter scope
+  auto scope = scopes_->current_var();
+  if (n->scope_) {
+    scopes_->set_current(n->scope_);
+  }
+
+  if (!n->stmts_.empty()) {
+    for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it)
+      TRY2((*it)->accept(this));
+  }
+  // exit scope
+  scopes_->set_current(scope);
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_version_stmt_node(VersionStmtNode *n) {
+  uint32_t version;
+  version = MAKE_VERSION(n->major_, n->minor_, n->rev_);
+  emit("static const uint32_t  plumlet_version   __attribute__"
+      "((section (\".version\"), used)) = 0x%x;\n", version);
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_if_stmt_node(IfStmtNode *n) {
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_then = BasicBlock::Create(ctx(), "if.then", parent);
+  BasicBlock *label_else = n->false_block_ ? BasicBlock::Create(ctx(), "if.else", parent) : nullptr;
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "if.end", parent);
+
+  TRY2(n->cond_->accept(this));
+
+  if (n->false_block_)
+    B.CreateCondBr(pop_expr(), label_then, label_else);
+  else
+    B.CreateCondBr(pop_expr(), label_then, label_end);
+
+  {
+    BlockStack bstack(this, label_then);
+    TRY2(n->true_block_->accept(this));
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+
+  if (n->false_block_) {
+    BlockStack bstack(this, label_else);
+    TRY2(n->false_block_->accept(this));
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+
+  B.SetInsertPoint(label_end);
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_onvalid_stmt_node(OnValidStmtNode *n) {
+  TRY2(n->cond_->accept(this));
+
+  Value *is_null = B.CreateIsNotNull(pop_expr());
+
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_then = BasicBlock::Create(ctx(), "onvalid.then", parent);
+  BasicBlock *label_else = n->else_block_ ? BasicBlock::Create(ctx(), "onvalid.else", parent) : nullptr;
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "onvalid.end", parent);
+
+  if (n->else_block_)
+    B.CreateCondBr(is_null, label_then, label_else);
+  else
+    B.CreateCondBr(is_null, label_then, label_end);
+
+  {
+    BlockStack bstack(this, label_then);
+    TRY2(n->block_->accept(this));
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+
+  if (n->else_block_) {
+    BlockStack bstack(this, label_else);
+    TRY2(n->else_block_->accept(this));
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+
+  B.SetInsertPoint(label_end);
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_switch_stmt_node(SwitchStmtNode *n) {
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_default = BasicBlock::Create(ctx(), "switch.default", parent);
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "switch.end", parent);
+  // switch (cond)
+  TRY2(n->cond_->accept(this));
+  SwitchInst *switch_inst = B.CreateSwitch(pop_expr(), label_default);
+  B.SetInsertPoint(label_end);
+  {
+    // case 1..N
+    SwitchStack sstack(this, switch_inst);
+    TRY2(n->block_->accept(this));
+  }
+  // if other cases are terminal, erase the end label
+  if (pred_empty(label_end))
+    label_end->eraseFromParent();
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_case_stmt_node(CaseStmtNode *n) {
+  if (!cur_switch_) return mkstatus_(n, "no valid switch instruction");
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_end = B.GetInsertBlock();
+  BasicBlock *dest;
+  if (n->value_) {
+    TRY2(n->value_->accept(this));
+    dest = BasicBlock::Create(ctx(), "switch.case", parent);
+    Value *cond = B.CreateIntCast(pop_expr(), cur_switch_->getCondition()->getType(), false);
+    cur_switch_->addCase(cast<ConstantInt>(cond), dest);
+  } else {
+    dest = cur_switch_->getDefaultDest();
+  }
+  {
+    BlockStack bstack(this, dest);
+    TRY2(n->block_->accept(this));
+    // if no trailing goto, fall to end
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_ident_expr_node(IdentExprNode *n) {
+  if (!n->decl_)
+    return mkstatus_(n, "variable lookup failed: %s", n->name_.c_str());
+  if (n->decl_->is_pointer()) {
+    if (n->sub_name_.size()) {
+      if (n->bitop_) {
+        // ident is holding a host endian number, don't use dext
+        if (n->is_lhs()) {
+          emit("%s%s->%s", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str());
+        } else {
+          emit("(((%s%s->%s) >> %d) & (((%s)1 << %d) - 1))", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str(),
+              n->bitop_->bit_offset_, bits_to_uint(n->bitop_->bit_width_ + 1), n->bitop_->bit_width_);
+        }
+        return mkstatus_(n, "unsupported");
+      } else {
+        if (n->struct_type_->id_->name_ == "_Packet" && n->sub_name_.substr(0, 3) == "arg") {
+          // convert arg1~arg8 into args[0]~args[7] assuming type_check verified the range already
+          auto arg_num = stoi(n->sub_name_.substr(3, 3));
+          if (arg_num < 5) {
+            emit("%s%s->args_lo[%d]", n->decl_->scope_id(), n->c_str(), arg_num - 1);
+          } else {
+            emit("%s%s->args_hi[%d]", n->decl_->scope_id(), n->c_str(), arg_num - 5);
+          }
+          return mkstatus_(n, "unsupported");
+        } else {
+          emit("%s%s->%s", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str());
+          auto it = vars_.find(n->decl_);
+          if (it == vars_.end()) return mkstatus_(n, "Cannot locate variable %s in vars_ table", n->c_str());
+          LoadInst *load_1 = B.CreateLoad(it->second);
+          vector<Value *> indices({B.getInt32(0), B.getInt32(n->sub_decl_->slot_)});
+          expr_ = B.CreateInBoundsGEP(load_1, indices);
+          if (!n->is_lhs())
+            expr_ = B.CreateLoad(pop_expr());
+        }
+      }
+    } else {
+      emit("*%s%s", n->decl_->scope_id(), n->c_str());
+      auto it = vars_.find(n->decl_);
+      if (it == vars_.end()) return mkstatus_(n, "Cannot locate variable %s in vars_ table", n->c_str());
+      LoadInst *load_1 = B.CreateAlignedLoad(it->second, 4);
+      expr_ = load_1;
+    }
+  } else {
+    if (n->sub_name_.size()) {
+      emit("%s%s.%s", n->decl_->scope_id(), n->c_str(), n->sub_name_.c_str());
+      auto it = vars_.find(n->decl_);
+      if (it == vars_.end()) return mkstatus_(n, "Cannot locate variable %s in vars_ table", n->c_str());
+      vector<Value *> indices({const_int(0), const_int(n->sub_decl_->slot_, 32)});
+      expr_ = B.CreateGEP(nullptr, it->second, indices);
+      if (!n->is_lhs())
+        expr_ = B.CreateLoad(pop_expr());
+    } else {
+      if (n->bitop_) {
+        // ident is holding a host endian number, don't use dext
+        if (n->is_lhs())
+          return mkstatus_(n, "illegal: ident %s is a left-hand-side type", n->name_.c_str());
+        if (n->decl_->is_struct())
+          return mkstatus_(n, "illegal: can only take bitop of a struct subfield");
+        emit("(((%s%s) >> %d) & (((%s)1 << %d) - 1))", n->decl_->scope_id(), n->c_str(),
+             n->bitop_->bit_offset_, bits_to_uint(n->bitop_->bit_width_ + 1), n->bitop_->bit_width_);
+      } else {
+        emit("%s%s", n->decl_->scope_id(), n->c_str());
+        auto it = vars_.find(n->decl_);
+        if (it == vars_.end()) return mkstatus_(n, "Cannot locate variable %s in vars_ table", n->c_str());
+        if (n->is_lhs() || n->decl_->is_struct())
+          expr_ = it->second;
+        else
+          expr_ = B.CreateLoad(it->second);
+      }
+    }
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_assign_expr_node(AssignExprNode *n) {
+  if (n->bitop_) {
+    TRY2(n->id_->accept(this));
+    emit(" = (");
+    TRY2(n->id_->accept(this));
+    emit(" & ~((((%s)1 << %d) - 1) << %d)) | (", bits_to_uint(n->id_->bit_width_),
+         n->bitop_->bit_width_, n->bitop_->bit_offset_);
+    TRY2(n->rhs_->accept(this));
+    emit(" << %d)", n->bitop_->bit_offset_);
+    return mkstatus_(n, "unsupported");
+  } else {
+    if (n->id_->flags_[ExprNode::PROTO]) {
+      auto f = n->id_->struct_type_->field(n->id_->sub_name_);
+      emit("bpf_dins(%s%s + %zu, %zu, %zu, ", n->id_->decl_->scope_id(), n->id_->c_str(),
+           f->bit_offset_ >> 3, f->bit_offset_ & 0x7, f->bit_width_);
+      TRY2(n->rhs_->accept(this));
+      emit(")");
+      return mkstatus_(n, "unsupported");
+    } else {
+      TRY2(n->id_->accept(this));
+      Value *lhs = pop_expr();
+      TRY2(n->rhs_->accept(this));
+      expr_ = B.CreateIntCast(expr_, cast<PointerType>(lhs->getType())->getElementType(), false);
+      B.CreateStore(pop_expr(), lhs);
+    }
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::lookup_var(Node *n, const string &name, Scopes::VarScope *scope,
+                                    VariableDeclStmtNode **decl, Value **mem) const {
+  *decl = scope->lookup(name, false);
+  if (!*decl) return mkstatus_(n, "cannot find %s variable", name.c_str());
+  auto it = vars_.find(*decl);
+  if (it == vars_.end()) return mkstatus_(n, "unable to find %s memory location", name.c_str());
+  *mem = it->second;
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_packet_expr_node(PacketExprNode *n) {
+  auto p = proto_scopes_->top_struct()->lookup(n->id_->name_, true);
+  VariableDeclStmtNode *offset_decl, *skb_decl;
+  Value *offset_mem, *skb_mem;
+  TRY2(lookup_var(n, "skb", scopes_->top_var(), &skb_decl, &skb_mem));
+  TRY2(lookup_var(n, "$" + n->id_->name_, scopes_->current_var(), &offset_decl, &offset_mem));
+
+  if (p) {
+    auto f = p->field(n->id_->sub_name_);
+    if (f) {
+      size_t bit_offset = f->bit_offset_;
+      size_t bit_width = f->bit_width_;
+      if (n->bitop_) {
+        bit_offset += f->bit_width_ - (n->bitop_->bit_offset_ + n->bitop_->bit_width_);
+        bit_width = std::min(bit_width - n->bitop_->bit_offset_, n->bitop_->bit_width_);
+      }
+      if (n->is_ref()) {
+        // e.g.: @ip.hchecksum, return offset of the header within packet
+        LoadInst *offset_ptr = B.CreateLoad(offset_mem);
+        Value *skb_hdr_offset = B.CreateAdd(offset_ptr, B.getInt64(bit_offset >> 3));
+        expr_ = B.CreateIntCast(skb_hdr_offset, B.getInt64Ty(), false);
+      } else if (n->is_lhs()) {
+        emit("bpf_dins_pkt(pkt, %s + %zu, %zu, %zu, ", n->id_->c_str(), bit_offset >> 3, bit_offset & 0x7, bit_width);
+        Function *store_fn = mod_->getFunction("bpf_dins_pkt");
+        if (!store_fn) return mkstatus_(n, "unable to find function bpf_dins_pkt");
+        LoadInst *skb_ptr = B.CreateLoad(skb_mem);
+        Value *skb_ptr8 = B.CreateBitCast(skb_ptr, B.getInt8PtrTy());
+        LoadInst *offset_ptr = B.CreateLoad(offset_mem);
+        Value *skb_hdr_offset = B.CreateAdd(offset_ptr, B.getInt64(bit_offset >> 3));
+        Value *rhs = B.CreateIntCast(pop_expr(), B.getInt64Ty(), false);
+        B.CreateCall5(store_fn, skb_ptr8, skb_hdr_offset, B.getInt64(bit_offset & 0x7), B.getInt64(bit_width), rhs);
+      } else {
+        emit("bpf_dext_pkt(pkt, %s + %zu, %zu, %zu)", n->id_->c_str(), bit_offset >> 3, bit_offset & 0x7, bit_width);
+        Function *load_fn = mod_->getFunction("bpf_dext_pkt");
+        if (!load_fn) return mkstatus_(n, "unable to find function bpf_dext_pkt");
+        LoadInst *skb_ptr = B.CreateLoad(skb_mem);
+        Value *skb_ptr8 = B.CreateBitCast(skb_ptr, B.getInt8PtrTy());
+        LoadInst *offset_ptr = B.CreateLoad(offset_mem);
+        Value *skb_hdr_offset = B.CreateAdd(offset_ptr, B.getInt64(bit_offset >> 3));
+        expr_ = B.CreateCall4(load_fn, skb_ptr8, skb_hdr_offset, B.getInt64(bit_offset & 0x7), B.getInt64(bit_width));
+        // this generates extra trunc insns whereas the bpf.load fns already
+        // trunc the values internally in the bpf interpeter
+        //expr_ = B.CreateTrunc(pop_expr(), B.getIntNTy(bit_width));
+      }
+    } else {
+      emit("pkt->start + pkt->offset + %s", n->id_->c_str());
+      return mkstatus_(n, "unsupported");
+    }
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_integer_expr_node(IntegerExprNode *n) {
+  APInt val;
+  StringRef(n->val_).getAsInteger(0, val);
+  expr_ = ConstantInt::get(mod_->getContext(), val);
+  if (n->bits_)
+    expr_ = B.CreateIntCast(expr_, B.getIntNTy(n->bits_), false);
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_short_circuit_and(BinopExprNode *n) {
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_start = B.GetInsertBlock();
+  BasicBlock *label_then = BasicBlock::Create(ctx(), "and.then", parent);
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "and.end", parent);
+
+  TRY2(n->lhs_->accept(this));
+  Value *neq_zero = B.CreateICmpNE(pop_expr(), B.getIntN(n->lhs_->bit_width_, 0));
+  B.CreateCondBr(neq_zero, label_then, label_end);
+
+  {
+    BlockStack bstack(this, label_then);
+    TRY2(n->rhs_->accept(this));
+    expr_ = B.CreateICmpNE(pop_expr(), B.getIntN(n->rhs_->bit_width_, 0));
+    B.CreateBr(label_end);
+  }
+
+  B.SetInsertPoint(label_end);
+
+  PHINode *phi = B.CreatePHI(B.getInt1Ty(), 2);
+  phi->addIncoming(B.getFalse(), label_start);
+  phi->addIncoming(pop_expr(), label_then);
+  expr_ = phi;
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_short_circuit_or(BinopExprNode *n) {
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_start = B.GetInsertBlock();
+  BasicBlock *label_then = BasicBlock::Create(ctx(), "or.then", parent);
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "or.end", parent);
+
+  TRY2(n->lhs_->accept(this));
+  Value *neq_zero = B.CreateICmpNE(pop_expr(), B.getIntN(n->lhs_->bit_width_, 0));
+  B.CreateCondBr(neq_zero, label_end, label_then);
+
+  {
+    BlockStack bstack(this, label_then);
+    TRY2(n->rhs_->accept(this));
+    expr_ = B.CreateICmpNE(pop_expr(), B.getIntN(n->rhs_->bit_width_, 0));
+    B.CreateBr(label_end);
+  }
+
+  B.SetInsertPoint(label_end);
+
+  PHINode *phi = B.CreatePHI(B.getInt1Ty(), 2);
+  phi->addIncoming(B.getTrue(), label_start);
+  phi->addIncoming(pop_expr(), label_then);
+  expr_ = phi;
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_binop_expr_node(BinopExprNode *n) {
+  if (n->op_ == Tok::TAND)
+    return emit_short_circuit_and(n);
+  if (n->op_ == Tok::TOR)
+    return emit_short_circuit_or(n);
+
+  TRY2(n->lhs_->accept(this));
+  Value *lhs = pop_expr();
+  TRY2(n->rhs_->accept(this));
+  Value *rhs = B.CreateIntCast(pop_expr(), lhs->getType(), false);
+  switch (n->op_) {
+    case Tok::TCEQ: expr_ = B.CreateICmpEQ(lhs, rhs); break;
+    case Tok::TCNE: expr_ = B.CreateICmpNE(lhs, rhs); break;
+    case Tok::TXOR: expr_ = B.CreateXor(lhs, rhs); break;
+    case Tok::TMOD: expr_ = B.CreateURem(lhs, rhs); break;
+    case Tok::TCLT: expr_ = B.CreateICmpULT(lhs, rhs); break;
+    case Tok::TCLE: expr_ = B.CreateICmpULE(lhs, rhs); break;
+    case Tok::TCGT: expr_ = B.CreateICmpUGT(lhs, rhs); break;
+    case Tok::TCGE: expr_ = B.CreateICmpUGE(lhs, rhs); break;
+    case Tok::TPLUS: expr_ = B.CreateAdd(lhs, rhs); break;
+    case Tok::TMINUS: expr_ = B.CreateSub(lhs, rhs); break;
+    case Tok::TLAND: expr_ = B.CreateAnd(lhs, rhs); break;
+    case Tok::TLOR: expr_ = B.CreateOr(lhs, rhs); break;
+    default: return mkstatus_(n, "unsupported binary operator");
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_unop_expr_node(UnopExprNode *n) {
+  TRY2(n->expr_->accept(this));
+  switch (n->op_) {
+    case Tok::TNOT: expr_ = B.CreateNot(pop_expr()); break;
+    case Tok::TCMPL: expr_ = B.CreateNeg(pop_expr()); break;
+    default: {}
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_bitop_expr_node(BitopExprNode *n) {
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_goto_expr_node(GotoExprNode *n) {
+  if (n->id_->name_ == "DONE") {
+    return mkstatus_(n, "use return statement instead");
+  }
+  string jump_label;
+  // when dealing with multistates, goto statements may be overridden
+  auto rewrite_it = proto_rewrites_.find(n->id_->full_name());
+  auto default_it = proto_rewrites_.find("");
+  if (rewrite_it != proto_rewrites_.end()) {
+    jump_label = rewrite_it->second;
+  } else if (default_it != proto_rewrites_.end()) {
+    jump_label = default_it->second;
+  } else {
+    auto state = scopes_->current_state()->lookup(n->id_->full_name(), false);
+    if (state) {
+      jump_label = state->scoped_name();
+      if (n->is_continue_) {
+        jump_label += "_continue";
+      }
+    } else {
+      state = scopes_->current_state()->lookup("EOP", false);
+      if (state) {
+        jump_label = state->scoped_name();
+      }
+    }
+  }
+  B.CreateBr(resolve_label(jump_label));
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_return_expr_node(ReturnExprNode *n) {
+  TRY2(n->expr_->accept(this));
+  Value *cast_1 = B.CreateIntCast(pop_expr(), cast<PointerType>(retval_->getType())->getElementType(), true);
+  B.CreateStore(cast_1, retval_);
+  B.CreateBr(resolve_label("DONE"));
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_table_lookup(MethodCallExprNode *n) {
+  TableDeclStmtNode* table = scopes_->top_table()->lookup(n->id_->name_);
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  stringstream free_inst;
+  IdentExprNode* arg1;
+  StructVariableDeclStmtNode* arg1_type;
+
+  auto table_fd_it = table_fds_.find(table);
+  if (table_fd_it == table_fds_.end())
+    return mkstatus_(n, "unable to find table %s in table_fds_", n->id_->c_str());
+
+  Function *pseudo_fn = mod_->getFunction("llvm.bpf.pseudo");
+  if (!pseudo_fn) return mkstatus_(n, "pseudo fd loader doesn't exist");
+  Function *lookup_fn = mod_->getFunction("bpf_map_lookup_elem_");
+  if (!lookup_fn) return mkstatus_(n, "bpf_map_lookup_elem_ undefined");
+
+  CallInst *pseudo_call = B.CreateCall2(pseudo_fn, B.getInt64(BPF_PSEUDO_MAP_FD), B.getInt64(table_fd_it->second));
+  Value *pseudo_map_fd = pseudo_call;
+
+  TRY2(arg0->accept(this));
+  Value *key_ptr = B.CreateBitCast(pop_expr(), B.getInt8PtrTy());
+
+  expr_ = B.CreateCall2(lookup_fn, pseudo_map_fd, key_ptr);
+
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED") {
+    if (n->args_.size() == 2) {
+      arg1 = static_cast<IdentExprNode*>(n->args_.at(1).get());
+      arg1_type = static_cast<StructVariableDeclStmtNode*>(arg1->decl_);
+      if (table->leaf_id()->name_ != arg1_type->struct_id_->name_) {
+        return mkstatus_(n, "lookup pointer type mismatch %s != %s", table->leaf_id()->c_str(),
+                        arg1_type->struct_id_->c_str());
+      }
+      auto it = vars_.find(arg1_type);
+      if (it == vars_.end()) return mkstatus_(n, "Cannot locate variable %s in vars_ table", n->id_->c_str());
+      expr_ = B.CreateBitCast(pop_expr(), cast<PointerType>(it->second->getType())->getElementType());
+      B.CreateStore(pop_expr(), it->second);
+    }
+  } else {
+    return mkstatus_(n, "lookup in table type %s unsupported", table->type_id()->c_str());
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_table_update(MethodCallExprNode *n) {
+  TableDeclStmtNode* table = scopes_->top_table()->lookup(n->id_->name_);
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  IdentExprNode* arg1 = static_cast<IdentExprNode*>(n->args_.at(1).get());
+  IdentExprNode* type0 = table->templates_.at(0).get();
+
+  auto table_fd_it = table_fds_.find(table);
+  if (table_fd_it == table_fds_.end())
+    return mkstatus_(n, "unable to find table %s in table_fds_", n->id_->c_str());
+  Function *pseudo_fn = mod_->getFunction("llvm.bpf.pseudo");
+  if (!pseudo_fn) return mkstatus_(n, "pseudo fd loader doesn't exist");
+  Function *update_fn = mod_->getFunction("bpf_map_update_elem_");
+  if (!update_fn) return mkstatus_(n, "bpf_map_update_elem_ undefined");
+
+  CallInst *pseudo_call = B.CreateCall2(pseudo_fn, B.getInt64(BPF_PSEUDO_MAP_FD),
+                                        B.getInt64(table_fd_it->second));
+  Value *pseudo_map_fd = pseudo_call;
+
+  emit("%s* %s_ukey = &", type0->c_str(), n->id_->c_str());
+  TRY2(arg0->accept(this));
+  Value *key_ptr = B.CreateBitCast(pop_expr(), B.getInt8PtrTy());
+
+  emitln(";");
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED") {
+    emit("bpf_table_update(pkt, TABLE_ID_%s, %s_ukey", n->id_->c_str(), n->id_->c_str());
+    emit(", &");
+    TRY2(arg1->accept(this));
+    Value *value_ptr = B.CreateBitCast(pop_expr(), B.getInt8PtrTy());
+
+    expr_ = B.CreateCall4(update_fn, pseudo_map_fd, key_ptr, value_ptr, B.getInt64(BPF_ANY));
+    emitln(");");
+  } else if (table->type_id()->name_ == "LPM") {
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_table_delete(MethodCallExprNode *n) {
+  TableDeclStmtNode* table = scopes_->top_table()->lookup(n->id_->name_);
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  IdentExprNode* type0 = table->templates_.at(0).get();
+
+  emit("%s* %s_dkey = &", type0->c_str(), n->id_->c_str());
+  TRY2(arg0->accept(this));
+  emitln(";");
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED") {
+    emit("bpf_table_delete(pkt, TABLE_ID_%s, %s_dkey", n->id_->c_str(), n->id_->c_str());
+    emitln(");");
+  } else if (table->type_id()->name_ == "LPM") {
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_channel_push_generic(MethodCallExprNode *n) {
+  /* computation of orig_length of packet:
+   * orig_lenth = pkt->length - (orig_offset - pkt->offset)
+   * push_header(N) does pkt->length += N; pkt->offset -= N;
+   * pop_header(N) does pg_may_access(N); pkt->length -=N; pkt->offset +=N;
+   *
+   * therefore push_header(); pop_header(); sequence is currently broken, ticket #930
+   */
+  emit("bpf_channel_push_packet(pkt");
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_channel_push(MethodCallExprNode *n) {
+  IdentExprNode* arg0 = static_cast<IdentExprNode*>(n->args_.at(0).get());
+  StructVariableDeclStmtNode* arg0_type = static_cast<StructVariableDeclStmtNode*>(arg0->decl_);
+  emit("bpf_channel_push_struct(pkt, STRUCTID_%s, &", arg0_type->struct_id_->c_str());
+  TRY2(arg0->accept(this));
+  emit(", sizeof(");
+  TRY2(arg0->accept(this));
+  emit("))");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_log(MethodCallExprNode *n) {
+  emitln("{ if (unlikely(pkt->capture)) {");
+  emit("    bpf_capture(pkt, BPF_CAP_LOG, %d, ", n->line_);
+  TRY2(n->args_[0]->accept(this));
+  emit("); } }");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_forward(MethodCallExprNode *n) {
+  emitln("pkt->arg1 &= ~1;");
+  emit("bpf_forward(pkt, ");
+  TRY2(n->args_[0]->accept(this));
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_replicate(MethodCallExprNode*n) {
+  emitln("pkt->arg1 &= ~1;");
+  emit("bpf_replicate(pkt, ");
+  TRY2(n->args_[0]->accept(this));
+  emit(",", n->id_->c_str());
+  TRY2(n->args_[1]->accept(this));
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_clone_forward(MethodCallExprNode *n) {
+  emitln("pkt->arg1 &= ~1;");
+  emit("bpf_clone_forward(pkt, ");
+  TRY2(n->args_[0]->accept(this));
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_forward_self(MethodCallExprNode *n) {
+  emit("bpf_forward_self(pkt, ");
+  TRY2(n->args_[0]->accept(this));
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_drop(MethodCallExprNode *n) {
+  emit("bpf_drop(pkt)");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_push_header(MethodCallExprNode *n) {
+  emit("if (unlikely(bpf_push_header(pkt, ");
+  TRY2(n->args_[0]->accept(this));
+  if (n->args_.size() == 1) {
+    emit(", %zu, 0) != 0)) goto ERROR", n->args_[0]->struct_type_->bit_width_ >> 3);
+  } else {
+    emit(", %zu, ", n->args_[0]->struct_type_->bit_width_ >> 3);
+    TRY2(n->args_[1]->accept(this));
+    emit(") != 0)) goto ERROR");
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_pop_header(MethodCallExprNode *n) {
+  emit("if (unlikely(bpf_pop_header(pkt, ");
+  if (n->args_[0]->typeof_ == ExprNode::STRUCT) {
+    emit("%zu", n->args_[0]->struct_type_->bit_width_ >> 3);
+  } else if (n->args_[0]->typeof_ == ExprNode::INTEGER) {
+    TRY2(n->args_[0]->accept(this));
+  }
+  emit(", 0/*todo*/) != 0)) goto ERROR");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_push_vlan(MethodCallExprNode *n) {
+  emit("if (unlikely(bpf_push_vlan(pkt, bpf_htons(0x8100/*ETH_P_8021Q*/), ");
+  TRY2(n->args_[0]->accept(this));
+  emit(") != 0)) goto ERROR");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_pop_vlan(MethodCallExprNode *n) {
+  emit("if (unlikely(bpf_pop_vlan(pkt) != 0)) goto ERROR");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_packet_rewrite_field(MethodCallExprNode *n) {
+  TRY2(n->args_[1]->accept(this));
+  TRY2(n->args_[0]->accept(this));
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_atomic_add(MethodCallExprNode *n) {
+  TRY2(n->args_[0]->accept(this));
+  Value *lhs = B.CreateBitCast(pop_expr(), Type::getInt64PtrTy(ctx()));
+  TRY2(n->args_[1]->accept(this));
+  Value *rhs = B.CreateSExt(pop_expr(), B.getInt64Ty());
+  AtomicRMWInst *atomic_inst = B.CreateAtomicRMW(AtomicRMWInst::Add, lhs, rhs, SequentiallyConsistent);
+  atomic_inst->setVolatile(false);
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_cksum(MethodCallExprNode *n) {
+  if (n->args_[0]->typeof_ == ExprNode::STRUCT) {
+    auto v = n->args_[0]->struct_type_;
+    size_t bit_width = v->bit_width_ >> 3;
+    auto p = proto_scopes_->top_struct()->lookup(v->id_->name_, true);
+    if (p) {
+      /* should we do store_half directly? */
+      if (!n->args_[0]->flags_[ExprNode::PROTO]) {
+        emit("bpf_ntohs(bpf_checksum_pkt(pkt, %s, %zu))", v->id_->c_str(), bit_width);
+      } else {
+        emit("bpf_ntohs(bpf_checksum(");
+        TRY2(n->args_[0]->accept(this));
+        emit(", %zu))", bit_width);
+      }
+    } else {
+      return mkstatus_(n, "cannot pg_cksum %d", n->args_[0]->typeof_);
+    }
+/**    emit("pg_cksum(");
+    TRY2(n->args_[0]->accept(this));
+    emit(", %zu)", n->args_[0]->struct_type_->bit_width_ >> 3);**/
+  } else {
+    return mkstatus_(n, "cannot pg_cksum %d", n->args_[0]->typeof_);
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_incr_cksum(MethodCallExprNode *n, size_t sz) {
+  Value *is_pseudo;
+  string csum_fn_str;
+  if (n->args_.size() == 4) {
+    TRY2(n->args_[3]->accept(this));
+    is_pseudo = B.CreateIntCast(B.CreateIsNotNull(pop_expr()), B.getInt64Ty(), false);
+    csum_fn_str = "bpf_l4_csum_replace_";
+  } else {
+    is_pseudo = B.getInt64(0);
+    csum_fn_str = "bpf_l3_csum_replace_";
+  }
+
+  TRY2(n->args_[2]->accept(this));
+  Value *new_val = B.CreateZExt(pop_expr(), B.getInt64Ty());
+  TRY2(n->args_[1]->accept(this));
+  Value *old_val = B.CreateZExt(pop_expr(), B.getInt64Ty());
+  TRY2(n->args_[0]->accept(this));
+  Value *offset = B.CreateZExt(pop_expr(), B.getInt64Ty());
+
+  Function *csum_fn = mod_->getFunction(csum_fn_str);
+  if (!csum_fn) return mkstatus_(n, "Undefined built-in %s", csum_fn_str.c_str());
+
+  // flags = (is_pseudo << 4) | sizeof(old_val)
+  Value *flags_lower = B.getInt64(sz ? sz : bits_to_size(n->args_[1]->bit_width_));
+  Value *flags_upper = B.CreateShl(is_pseudo, B.getInt64(4));
+  Value *flags = B.CreateOr(flags_upper, flags_lower);
+
+  VariableDeclStmtNode *skb_decl;
+  Value *skb_mem;
+  TRY2(lookup_var(n, "skb", scopes_->top_var(), &skb_decl, &skb_mem));
+  LoadInst *skb_ptr = B.CreateLoad(skb_mem);
+  Value *skb_ptr8 = B.CreateBitCast(skb_ptr, B.getInt8PtrTy());
+
+  expr_ = B.CreateCall5(csum_fn, skb_ptr8, offset, old_val, new_val, flags);
+
+  // if (n->args_.size() == 3) {
+  //   /* ip checksum */
+  //   emit("bpf_ntohs(bpf_csum_replace4(bpf_htons(");
+  //   TRY2(n->args_[0]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[1]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[2]->accept(this));
+  //   emit(")))");
+  // } else {
+  //   /* L4 checksum */
+  //   emit("(");
+  //   /* part of pseudo header */
+  //   TRY2(n->args_[3]->accept(this));
+  //   emit(" ? ");
+  //   emit("((pkt->hw_csum == 1) ? ");
+  //   /* CHECKSUM_PARTIAL update pseudo only */
+  //   emit("bpf_ntohs(bpf_pseudo_csum_replace4(bpf_htons(");
+  //   TRY2(n->args_[0]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[1]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[2]->accept(this));
+  //   emit(")))");
+  //   emit(" : ");
+  //   /* CHECKSUM_NONE update normally */
+  //   emit("bpf_ntohs(bpf_csum_replace4(bpf_htons(");
+  //   TRY2(n->args_[0]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[1]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[2]->accept(this));
+  //   emit(")))");
+  //   emit(")");
+  //   emit(" : ");
+  //   /* not part of pseudo */
+  //   emit("((pkt->hw_csum != 1) ? ");
+  //   /* CHECKSUM_NONE updata normally */
+  //   emit("bpf_ntohs(bpf_csum_replace4(bpf_htons(");
+  //   TRY2(n->args_[0]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[1]->accept(this));
+  //   emit("), bpf_htonl(");
+  //   TRY2(n->args_[2]->accept(this));
+  //   emit(")))");
+  //   emit(" : ");
+  //   /* CHECKSUM_PARTIAL no-op */
+  //   TRY2(n->args_[0]->accept(this));
+  //   emit("))");
+  // }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_lb_hash(MethodCallExprNode *n) {
+  emit("pg_lb_hash(");
+  TRY2(n->args_[0]->accept(this));
+  emit(", ");
+  TRY2(n->args_[1]->accept(this));
+  emit(")");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_sizeof(MethodCallExprNode *n) {
+  if (n->args_[0]->typeof_ == ExprNode::STRUCT) {
+    if (n->args_[0]->struct_type_->id_->name_ == "_Packet") {
+      //emit("PG_SIZEOF(pkt)");
+      emit("(int)pkt->length");
+    } else {
+      emit("%zu", n->args_[0]->struct_type_->bit_width_ >> 3);
+      expr_ = B.getInt64(n->args_[0]->struct_type_->bit_width_ >> 3);
+    }
+  } else if (n->args_[0]->typeof_ == ExprNode::INTEGER) {
+    if (n->args_[0]->struct_type_) {
+      expr_ = B.getInt64(n->args_[0]->struct_type_->bit_width_ >> 3);
+    } else {
+      emit("%zu", n->args_[0]->bit_width_ >> 3);
+      expr_ = B.getInt64(n->args_[0]->bit_width_ >> 3);
+    }
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_get_usec_time(MethodCallExprNode *n) {
+  emit("bpf_get_usec_time()");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_forward_to_vnf(MethodCallExprNode*n) {
+  emitln("pkt->arg1 |= 1;");
+  emit("pkt->arg2 = ");
+  TRY2(n->args_[0]->accept(this));
+  emitln(";");
+  emit("bpf_forward_to_plum(pkt, ");
+  TRY2(n->args_[1]->accept(this));
+  emit(")");
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::emit_forward_to_group(MethodCallExprNode *n) {
+
+  emit("pkt->arg2 = ");
+  TRY2(n->args_[0]->accept(this));
+  emitln(";");
+  emitln("pkt->arg3 = pkt->plum_id;");
+  emit("bpf_forward_to_plum(pkt, ");
+  emit("1/*TUNNEL_PLUM_ID*/");
+  emit(")");
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_method_call_expr_node(MethodCallExprNode *n) {
+  free_instructions_.push_back(vector<string>());
+
+  if (!n->block_->stmts_.empty()) {
+    ++indent_;
+    emitln("{");
+  }
+
+  if (n->id_->sub_name_.size()) {
+    if (n->id_->sub_name_ == "lookup") {
+      TRY2(emit_table_lookup(n));
+    } else if (n->id_->sub_name_ == "update") {
+      TRY2(emit_table_update(n));
+    } else if (n->id_->sub_name_ == "delete") {
+      TRY2(emit_table_delete(n));
+    } else if (n->id_->sub_name_ == "replicate" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_replicate(n));
+    } else if (n->id_->sub_name_ == "forward" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_forward(n));
+    } else if (n->id_->sub_name_ == "forward_self" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_forward_self(n));
+    } else if (n->id_->sub_name_ == "push_header" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_push_header(n));
+    } else if (n->id_->sub_name_ == "pop_header" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_pop_header(n));
+    } else if (n->id_->sub_name_ == "push_vlan" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_push_vlan(n));
+    } else if (n->id_->sub_name_ == "pop_vlan" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_pop_vlan(n));
+    } else if (n->id_->sub_name_ == "rewrite_field" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_rewrite_field(n));
+    } else if (n->id_->sub_name_ == "clone_forward" && n->id_->name_ == "pkt") {
+      TRY2(emit_packet_clone_forward(n));
+    }
+  } else if (n->id_->name_ == "atomic_add") {
+    TRY2(emit_atomic_add(n));
+  } else if (n->id_->name_ == "log") {
+    TRY2(emit_log(n));
+  } else if (n->id_->name_ == "cksum") {
+    TRY2(emit_cksum(n));
+  } else if (n->id_->name_ == "incr_cksum_u16") {
+    TRY2(emit_incr_cksum(n, 2));
+  } else if (n->id_->name_ == "incr_cksum_u32") {
+    TRY2(emit_incr_cksum(n, 4));
+  } else if (n->id_->name_ == "incr_cksum") {
+    TRY2(emit_incr_cksum(n));
+  } else if (n->id_->name_ == "lb_hash") {
+    TRY2(emit_lb_hash(n));
+  } else if (n->id_->name_ == "sizeof") {
+    TRY2(emit_sizeof(n));
+  } else if (n->id_->name_ == "get_usec_time") {
+    TRY2(emit_get_usec_time(n));
+  } else if (n->id_->name_ == "channel_push") {
+    TRY2(emit_channel_push(n));
+  } else if (n->id_->name_ == "channel_push_generic") {
+    TRY2(emit_channel_push_generic(n));
+  } else if (n->id_->name_ == "forward_to_vnf") {
+    TRY2(emit_forward_to_vnf(n));
+  } else if (n->id_->name_ == "forward_to_group") {
+    TRY2(emit_forward_to_group(n));
+  } else {
+    TRY2(n->id_->accept(this));
+    emit("(");
+    for (auto it = n->args_.begin(); it != n->args_.end(); ++it) {
+      TRY2((*it)->accept(this));
+      if (it + 1 != n->args_.end()) {
+        emit(", ");
+      }
+    }
+  }
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+/// on_match
+StatusTuple CodegenLLVM::visit_match_decl_stmt_node(MatchDeclStmtNode *n) {
+  if (n->formals_.size() != 1)
+    return mkstatus_(n, "on_match expected 1 arguments, %zu given", n->formals_.size());
+  StructVariableDeclStmtNode* leaf_n = static_cast<StructVariableDeclStmtNode*>(n->formals_.at(0).get());
+  if (!leaf_n)
+    return mkstatus_(n, "invalid parameter type");
+  // lookup result variable
+  auto result_decl = scopes_->current_var()->lookup("_result", false);
+  if (!result_decl) return mkstatus_(n, "unable to find _result built-in");
+  auto result = vars_.find(result_decl);
+  if (result == vars_.end()) return mkstatus_(n, "unable to find memory for _result built-in");
+  vars_[leaf_n] = result->second;
+
+  Value *load_1 = B.CreateLoad(result->second);
+  Value *is_null = B.CreateIsNotNull(load_1);
+
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_then = BasicBlock::Create(ctx(), "onvalid.then", parent);
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "onvalid.end", parent);
+  B.CreateCondBr(is_null, label_then, label_end);
+
+  {
+    BlockStack bstack(this, label_then);
+    TRY2(n->block_->accept(this));
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+
+  B.SetInsertPoint(label_end);
+  return mkstatus(0);
+}
+
+/// on_miss
+StatusTuple CodegenLLVM::visit_miss_decl_stmt_node(MissDeclStmtNode *n) {
+  if (n->formals_.size() != 0)
+    return mkstatus_(n, "on_match expected 0 arguments, %zu given", n->formals_.size());
+  auto result_decl = scopes_->current_var()->lookup("_result", false);
+  if (!result_decl) return mkstatus_(n, "unable to find _result built-in");
+  auto result = vars_.find(result_decl);
+  if (result == vars_.end()) return mkstatus_(n, "unable to find memory for _result built-in");
+
+  Value *load_1 = B.CreateLoad(result->second);
+  Value *is_null = B.CreateIsNull(load_1);
+
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_then = BasicBlock::Create(ctx(), "onvalid.then", parent);
+  BasicBlock *label_end = BasicBlock::Create(ctx(), "onvalid.end", parent);
+  B.CreateCondBr(is_null, label_then, label_end);
+
+  {
+    BlockStack bstack(this, label_then);
+    TRY2(n->block_->accept(this));
+    if (!B.GetInsertBlock()->getTerminator())
+      B.CreateBr(label_end);
+  }
+
+  B.SetInsertPoint(label_end);
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_failure_decl_stmt_node(FailureDeclStmtNode *n) {
+  if (n->formals_.size() != 1)
+    return mkstatus_(n, "on_failure expected 1 argument, %zu given", n->formals_.size());
+  StructVariableDeclStmtNode* key_n = static_cast<StructVariableDeclStmtNode*>(n->formals_.at(0).get());
+  ++indent_;
+  emitln("/*if ((unsigned long)%s_element >= (unsigned long)-4095) {", n->id_->name_.c_str());
+  emitln("%s* %s%s = %s_key;", key_n->struct_id_->c_str(),
+         key_n->scope_id(), key_n->id_->c_str(), n->id_->c_str());
+  TRY2(n->block_->accept(this));
+  --indent_;
+  emitln("");
+  emit("}*/");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_expr_stmt_node(ExprStmtNode *n) {
+  TRY2(n->expr_->accept(this));
+  expr_ = nullptr;
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_struct_variable_decl_stmt_node(StructVariableDeclStmtNode *n) {
+  if (n->struct_id_->name_ == "" || n->struct_id_->name_[0] == '_') {
+    return mkstatus(0);
+  }
+  if (n->struct_id_->scope_name_ == "proto") {
+    auto p = proto_scopes_->top_struct()->lookup(n->struct_id_->name_, true);
+    if (p) {
+      string var = n->scope_id() + n->id_->name_;
+      /* zero initialize array to be filled in with packet header */
+      emit("uint64_t __%s[%zu] = {}; uint8_t *%s = (uint8_t*)__%s;",
+           var.c_str(), ((p->bit_width_ >> 3) + 7) >> 3, var.c_str(), var.c_str());
+      for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+        auto asn = static_cast<AssignExprNode*>(it->get());
+        if (auto f = p->field(asn->id_->sub_name_)) {
+          size_t bit_offset = f->bit_offset_;
+          size_t bit_width = f->bit_width_;
+          if (asn->bitop_) {
+            bit_offset += f->bit_width_ - (asn->bitop_->bit_offset_ + asn->bitop_->bit_width_);
+            bit_width = std::min(bit_width - asn->bitop_->bit_offset_, asn->bitop_->bit_width_);
+          }
+          emit(" bpf_dins(%s + %zu, %zu, %zu, ", var.c_str(), bit_offset >> 3, bit_offset & 0x7, bit_width);
+          TRY2(asn->rhs_->accept(this));
+          emit(");");
+        }
+      }
+    }
+  } else {
+    StructDeclStmtNode *decl = scopes_->top_struct()->lookup(n->struct_id_->name_);
+    if (!decl) return mkstatus_(n, "Cannot find struct %s decl", n->id_->c_str());
+
+    auto it = structs_.find(decl);
+    if (it == structs_.end()) return mkstatus_(n, "Cannot find struct %s decl", n->id_->c_str());
+    Type *stype = n->is_pointer() ? PointerType::get(it->second, 0) : (PointerType *)it->second;
+    AllocaInst *ptr_a = new AllocaInst(stype, nullptr, "", entry_bb_);
+    vars_[n] = ptr_a;
+    if (n->is_pointer()) {
+      if (n->id_->name_ == "_result") {
+        // special case for capturing the return value of a previous method call
+        Value *cast_1 = B.CreateBitCast(pop_expr(), stype);
+        B.CreateStore(cast_1, ptr_a);
+      } else {
+        ConstantPointerNull *const_null = ConstantPointerNull::get(cast<PointerType>(stype));
+        B.CreateStore(const_null, ptr_a);
+      }
+    } else {
+      B.CreateMemSet(ptr_a, B.getInt8(0), B.getInt64(decl->bit_width_ >> 3), 1);
+      emit("%s %s%s = {};", n->struct_id_->c_str(), n->scope_id(), n->id_->c_str());
+      if (!n->init_.empty()) {
+        for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+          emit(" ");
+          TRY2((*it)->accept(this));
+          emit(";");
+        }
+      }
+    }
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_integer_variable_decl_stmt_node(IntegerVariableDeclStmtNode *n) {
+  if (!B.GetInsertBlock())
+    return mkstatus(0);
+  if (n->id_->name_ == "timer_delay")
+    return mkstatus(0);
+  emit_comment(n);
+  emit("%s %s%s", bits_to_uint(n->bit_width_), n->scope_id(), n->id_->c_str());
+
+  // uintX var = init
+  AllocaInst *ptr_a = new AllocaInst(B.getIntNTy(n->bit_width_), nullptr, n->id_->name_, entry_bb_);
+  vars_[n] = ptr_a;
+
+  // todo
+  if (!n->scope_id_.empty())
+    emit(" = 0");
+  if (!n->init_.empty()) {
+    emit("; ");
+    TRY2(n->init_[0]->accept(this));
+  }
+  emit(";");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_struct_decl_stmt_node(StructDeclStmtNode *n) {
+  ++indent_;
+  StructType *struct_type = StructType::create(ctx(), "struct." + n->id_->name_);
+  vector<Type *> fields;
+  for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it)
+    fields.push_back(B.getIntNTy((*it)->bit_width_));
+  struct_type->setBody(fields, n->is_packed());
+  structs_[n] = struct_type;
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_parser_state_stmt_node(ParserStateStmtNode *n) {
+  string jump_label = n->scoped_name() + "_continue";
+  BasicBlock *label_entry = resolve_label(jump_label);
+  B.SetInsertPoint(label_entry);
+  if (n->next_state_)
+    TRY2(n->next_state_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_timer_decl_stmt_node(TimerDeclStmtNode *n) {
+  auto scope = scopes_->current_state();
+  scopes_->set_current(n->scope_);
+  TRY2(n->block_->accept(this));
+  scopes_->set_current(scope);
+  return mkstatus(0);
+}
+StatusTuple CodegenLLVM::visit_state_decl_stmt_node(StateDeclStmtNode *n) {
+  if (!n->id_) {
+    return mkstatus(0);
+  }
+  string jump_label = n->scoped_name();
+  BasicBlock *label_entry = resolve_label(jump_label);
+  B.SetInsertPoint(label_entry);
+
+  auto it = n->subs_.begin();
+
+  auto scope = scopes_->current_state();
+  scopes_->set_current(it->scope_);
+
+  for (auto in = n->init_.begin(); in != n->init_.end(); ++in) {
+    TRY2((*in)->accept(this));
+  }
+
+  if (n->subs_.size() == 1 && it->id_->name_ == "") {
+    // this is not a multistate protocol, emit everything and finish
+    TRY2(it->block_->accept(this));
+    if (n->parser_) {
+      B.CreateBr(resolve_label(jump_label + "_continue"));
+      TRY2(n->parser_->accept(this));
+    }
+  } else {
+    return mkstatus_(n, "unsupported");
+    if (n->parser_) {
+      for (auto it2 = n->subs_.begin(); it2 != n->subs_.end(); ++it2) {
+        proto_rewrites_[it2->id_->full_name()] = n->scoped_name() + "_" + it2->id_->name_;
+      }
+      TRY2(n->parser_->accept(this));
+      proto_rewrites_.clear();
+      emitln("");
+    }
+    for (; it != n->subs_.end(); ++it) {
+      auto scope = scopes_->current_state();
+      scopes_->set_current(it->scope_);
+
+      string jump_label = n->scoped_name() + "_" + it->id_->name_;
+      ++indent_;
+      emitln("JUMP_GUARD; %s: {", jump_label.c_str());
+      emitln("PG_TRACE(%.14s);", jump_label.c_str());
+      if (auto p = proto_scopes_->top_struct()->lookup(it->id_->name_, true)) {
+        emitln("%s = pkt->offset + parsed_bytes; /* remember the offset of this header */", it->id_->c_str());
+        emitln("parsed_bytes += %zu;", p->bit_width_ >> 3);
+        emitln("if (!pg_may_access(pkt, parsed_bytes)) goto ERROR; /* pull data from fragments to access this header */");
+      }
+      TRY2(it->block_->accept(this));
+      if (it->parser_) {
+        emitln("");
+        TRY2(it->parser_->accept(this));
+      }
+      --indent_;
+      emitln("");
+      emitln("}");
+
+      scopes_->set_current(scope);
+    }
+  }
+
+  scopes_->set_current(scope);
+
+  --indent_;
+  emitln("");
+  emit("}");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit_table_decl_stmt_node(TableDeclStmtNode *n) {
+  if (n->table_type_->name_ == "Table"
+      || n->table_type_->name_ == "SharedTable") {
+    if (n->templates_.size() != 4)
+      return mkstatus_(n, "%s expected 4 arguments, %zu given", n->table_type_->c_str(), n->templates_.size());
+    auto key = scopes_->top_struct()->lookup(n->key_id()->name_, /*search_local*/true);
+    if (!key) return mkstatus_(n, "cannot find key %s", n->key_id()->name_.c_str());
+    auto leaf = scopes_->top_struct()->lookup(n->leaf_id()->name_, /*search_local*/true);
+    if (!leaf) return mkstatus_(n, "cannot find leaf %s", n->leaf_id()->name_.c_str());
+
+    int map_type = BPF_MAP_TYPE_UNSPEC;
+    if (n->type_id()->name_ == "FIXED_MATCH")
+      map_type = BPF_MAP_TYPE_HASH;
+    else if (n->type_id()->name_ == "INDEXED")
+      map_type = BPF_MAP_TYPE_ARRAY;
+    else
+      return mkstatus_(n, "Table type %s not implemented", n->type_id()->name_.c_str());
+
+    StructType *decl_struct = mod_->getTypeByName("struct." + n->id_->name_);
+    if (!decl_struct)
+      decl_struct = StructType::create(ctx(), "struct." + n->id_->name_);
+    if (decl_struct->isOpaque())
+      decl_struct->setBody(std::vector<Type *>({Type::getInt32Ty(ctx()), Type::getInt32Ty(ctx()),
+                                                Type::getInt32Ty(ctx()), Type::getInt32Ty(ctx())}),
+                           /*isPacked=*/false);
+    GlobalVariable *decl_gvar = new GlobalVariable(*mod_, decl_struct, false,
+                                                   GlobalValue::ExternalLinkage, 0, n->id_->name_);
+    decl_gvar->setSection("maps");
+    decl_gvar->setAlignment(4);
+    vector<Constant *> struct_init = { B.getInt32(map_type), B.getInt32(key->bit_width_ / 8),
+                                       B.getInt32(leaf->bit_width_ / 8), B.getInt32(n->size_)};
+    Constant *const_struct = ConstantStruct::get(decl_struct, struct_init);
+    decl_gvar->setInitializer(const_struct);
+    tables_[n] = decl_gvar;
+
+    int map_fd = bpf_create_map(map_type, key->bit_width_ / 8, leaf->bit_width_ / 8, n->size_);
+    if (map_fd >= 0 || !ENABLE_RELOCATIONS)
+      table_fds_[n] = map_fd;
+  } else {
+    return mkstatus_(n, "Table %s not implemented", n->table_type_->name_.c_str());
+  }
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::visit(Node* root) {
+  BlockStmtNode* b = static_cast<BlockStmtNode*>(root);
+
+
+  scopes_->set_current(scopes_->top_state());
+  scopes_->set_current(scopes_->top_var());
+
+  TRY2(print_header());
+
+  TRY2(b->ver_.accept(this));
+
+  for (auto it = scopes_->top_table()->obegin(); it != scopes_->top_table()->oend(); ++it) {
+    TRY2((*it)->accept(this));
+    emit("\n");
+  }
+
+  TRY2(print_parser());
+
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::print_timer() {
+  // visit timers
+  ++indent_;
+  emitln("PG_PARSE_DECL(timer) {");
+  emitln("uint32_t timer_delay = 0;");
+  // visit function scoped variables
+  for (auto it = scopes_->current_var()->obegin(); it != scopes_->current_var()->oend(); ++it) {
+    TRY2((*it)->accept(this));
+    emitln("");
+  }
+  for (auto it = scopes_->top_timer()->obegin(); it != scopes_->top_timer()->oend(); ++it) {
+    TRY2((*it)->accept(this));
+    emitln("");
+  }
+  ++indent_;
+  emitln("DONE: {");
+  emitln("PG_TRACE(DONE);");
+  emitln("pg_timer_forward(pkt, timer_delay);");
+  --indent_;
+  emitln("return;");
+  emitln("}");
+
+  ++indent_;
+  emitln("ERROR: {");
+  emitln("PG_TRACE(ERROR);");
+  emitln("pg_drop(pkt);");
+  emitln("pg_timer_forward(pkt, timer_delay);");
+  --indent_;
+  emitln("return;");
+  --indent_;
+  emitln("}");
+  emitln("}");
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::print_parser() {
+  auto skbuff_decl = scopes_->top_struct()->lookup("_skbuff");
+  if (!skbuff_decl) return mkstatus(-1, "could not find built-in struct decl _skbuff");
+  auto struct_it = structs_.find(skbuff_decl);
+  if (struct_it == structs_.end()) return mkstatus(-1, "could not find built-in type _skbuff");
+
+  // int parse(struct sk_buff *skb)
+  FunctionType *parse_fn_type = FunctionType::get(B.getInt32Ty(),
+                                                  vector<Type *>({PointerType::get(struct_it->second, 0)}),
+                                                  /*isVarArg=*/false);
+
+  Function *prog = mod_->getFunction("main");
+  if (!prog) {
+    prog = Function::Create(parse_fn_type, GlobalValue::ExternalLinkage, "main", mod_);
+    if (section_.empty()) return mkstatus(-1, "Empty section pragma");
+    prog->setSection(section_);
+  }
+
+  entry_bb_ = BasicBlock::Create(ctx(), "entry", prog);
+  labels_["entry"] = entry_bb_;
+
+  B.SetInsertPoint(entry_bb_);
+
+  auto args = prog->arg_begin();
+  Value *skb_arg = args++;
+  skb_arg->setName("skb");
+  auto skb = scopes_->top_var()->lookup("skb", true);
+  if (!skb) return mkstatus(-1, "unable to find declaration of built-in skb");
+  Type *stype = PointerType::get(struct_it->second, 0);
+  AllocaInst *ptr_skb = new AllocaInst(stype, nullptr, "skb", entry_bb_);
+  ptr_skb->setAlignment(4);
+  B.CreateStore(skb_arg, ptr_skb);
+
+  retval_ = new AllocaInst(B.getInt32Ty(), nullptr, "ret", entry_bb_);
+  B.CreateStore(B.getInt32(0), retval_);
+
+  vars_[skb] = ptr_skb;
+
+  BasicBlock *label_return = resolve_label("DONE");
+
+  ++indent_;
+  emitln("PG_PARSE_DECL(parse) {");
+  /* emitln("uint8_t *pp;"); */
+  emitln("uint32_t parsed_bytes = 0;");
+  //emitln("uint16_t orig_offset = 0;/*pkt->offset;*/");
+
+  // visit function scoped variables
+  {
+    BlockStack bstack(this, entry_bb_);
+    B.SetInsertPoint(entry_bb_);
+    for (auto it = scopes_->current_var()->obegin(); it != scopes_->current_var()->oend(); ++it)
+      TRY2((*it)->accept(this));
+  }
+
+  for (auto it = scopes_->current_state()->obegin(); it != scopes_->current_state()->oend(); ++it) {
+    if (proto_scopes_->top_struct()->lookup((*it)->id_->name_, true)) {
+      emitln("uint32_t %s = 0; /* header offset */", (*it)->id_->c_str());
+    }
+  }
+
+  /* emitln("pp = pkt->start + pkt->offset;"); */
+  emitln("goto s1_INIT;");
+
+  // finally, visit the states
+  for (auto it = scopes_->current_state()->obegin(); it != scopes_->current_state()->oend(); ++it) {
+    emitln("");
+    TRY2((*it)->accept(this));
+  }
+
+  B.SetInsertPoint(entry_bb_);
+  B.CreateBr(resolve_label("s1_INIT"));
+
+  B.SetInsertPoint(label_return);
+  expr_ = B.CreateLoad(retval_);
+  B.CreateRet(pop_expr());
+
+  ++indent_;
+  emitln("ERROR: {");
+  emitln("PG_TRACE(ERROR);");
+  --indent_;
+  emitln("goto CLEANUP;");
+  emitln("}");
+
+  ++indent_;
+  emitln("DONE: {");
+  emitln("PG_TRACE(DONE);");
+  --indent_;
+  emitln("goto CLEANUP;");
+  emitln("}");
+
+  ++indent_;
+  emitln("CLEANUP: {");
+  --indent_;
+  emitln("/* cleanup is done by PE */;");
+  --indent_;
+  emitln("}");
+
+  emitln("}");
+
+  //print_timer();
+  return mkstatus(0);
+}
+
+StatusTuple CodegenLLVM::print_header() {
+  if (use_pre_header_) {
+    //emit("%s", PRE_HEADER.c_str());
+    emitln("");
+  } else {
+    emitln("#include <stdint.h>");
+    emitln("#include \"../dp/linux/filter.h\"");
+    emitln("#include \"container/pg_api.h\"");
+    emitln("#include \"container/pg_defs.h\"");
+  }
+  emitln("#define JUMP_GUARD goto DONE");
+  emitln("#define PG_SIZEOF(_pkt) ((int)_pkt->length - (int)_pkt->offset + 0/*orig_offset*/)");
+
+  GlobalVariable *gvar_license = new GlobalVariable(*mod_, ArrayType::get(Type::getInt8Ty(ctx()), 4),
+                                                    false, GlobalValue::ExternalLinkage, 0, "_license");
+  gvar_license->setSection("license");
+  gvar_license->setAlignment(1);
+  gvar_license->setInitializer(ConstantDataArray::getString(ctx(), "GPL", true));
+
+  Function *pseudo_fn = mod_->getFunction("llvm.bpf.pseudo");
+  if (!pseudo_fn) {
+    pseudo_fn = Function::Create(
+        FunctionType::get(B.getInt64Ty(), vector<Type *>({B.getInt64Ty(), B.getInt64Ty()}), false),
+        GlobalValue::ExternalLinkage, "llvm.bpf.pseudo", mod_);
+  }
+
+  int i = 0;
+  // declare structures
+  for (auto it = scopes_->top_struct()->obegin(); it != scopes_->top_struct()->oend(); ++it) {
+    if ((*it)->id_->name_ == "_Packet")
+      continue;
+    TRY2((*it)->accept(this));
+    emit(";\n");
+    emitln("#define STRUCTID_%s %d", (*it)->id_->c_str(), i++);
+  }
+  emitln("#define STRUCTID_generic %d", i);
+  return mkstatus(0);
+}
+
+int CodegenLLVM::get_table_fd(const std::string &name) const {
+  TableDeclStmtNode *table = scopes_->top_table()->lookup(name);
+  if (!table)
+    return -1;
+
+  auto table_fd_it = table_fds_.find(table);
+  if (table_fd_it == table_fds_.end())
+    return -1;
+
+  return table_fd_it->second;
+}
+
+LLVMContext & CodegenLLVM::ctx() const {
+  return mod_->getContext();
+}
+
+Constant * CodegenLLVM::const_int(uint64_t val, unsigned bits, bool is_signed) {
+  return ConstantInt::get(ctx(), APInt(bits, val, is_signed));
+}
+
+Value * CodegenLLVM::pop_expr() {
+  Value *ret = expr_;
+  expr_ = nullptr;
+  return ret;
+}
+
+BasicBlock * CodegenLLVM::resolve_label(const string &label) {
+  auto it = labels_.find(label);
+  if (it != labels_.end()) return it->second;
+  Function *parent = B.GetInsertBlock()->getParent();
+  BasicBlock *label_new = BasicBlock::Create(ctx(), label, parent);
+  labels_[label] = label_new;
+  return label_new;
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/codegen_llvm.h b/jit/src/cc/codegen_llvm.h
new file mode 100644
index 0000000..945c5d4
--- /dev/null
+++ b/jit/src/cc/codegen_llvm.h
@@ -0,0 +1,141 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <vector>
+#include <string>
+#include <set>
+
+#include "cc/node.h"
+#include "cc/scope.h"
+
+namespace llvm {
+class AllocaInst;
+class BasicBlock;
+class BranchInst;
+class Constant;
+class Instruction;
+class IRBuilderBase;
+class LLVMContext;
+class Module;
+class StructType;
+class SwitchInst;
+}
+
+namespace ebpf {
+namespace cc {
+
+class BlockStack;
+class SwitchStack;
+
+using std::vector;
+using std::string;
+using std::set;
+
+class CodegenLLVM : public Visitor {
+  friend class BlockStack;
+  friend class SwitchStack;
+ public:
+  CodegenLLVM(llvm::Module *mod, Scopes *scopes, Scopes *proto_scopes,
+              bool use_pre_header, const std::string &section);
+  virtual ~CodegenLLVM();
+
+#define VISIT(type, func) virtual STATUS_RETURN visit_##func(type* n);
+  EXPAND_NODES(VISIT)
+#undef VISIT
+
+  virtual STATUS_RETURN visit(Node* n);
+
+  int get_table_fd(const std::string &name) const;
+
+ private:
+  STATUS_RETURN emit_short_circuit_and(BinopExprNode* n);
+  STATUS_RETURN emit_short_circuit_or(BinopExprNode* n);
+  STATUS_RETURN emit_table_lookup(MethodCallExprNode* n);
+  STATUS_RETURN emit_table_update(MethodCallExprNode* n);
+  STATUS_RETURN emit_table_delete(MethodCallExprNode* n);
+  STATUS_RETURN emit_channel_push(MethodCallExprNode* n);
+  STATUS_RETURN emit_channel_push_generic(MethodCallExprNode* n);
+  STATUS_RETURN emit_log(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_forward(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_replicate(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_clone_forward(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_forward_self(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_drop(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_broadcast(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_multicast(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_push_header(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_pop_header(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_push_vlan(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_pop_vlan(MethodCallExprNode* n);
+  STATUS_RETURN emit_packet_rewrite_field(MethodCallExprNode* n);
+  STATUS_RETURN emit_atomic_add(MethodCallExprNode* n);
+  STATUS_RETURN emit_cksum(MethodCallExprNode* n);
+  STATUS_RETURN emit_incr_cksum(MethodCallExprNode* n, size_t sz = 0);
+  STATUS_RETURN emit_lb_hash(MethodCallExprNode* n);
+  STATUS_RETURN emit_sizeof(MethodCallExprNode* n);
+  STATUS_RETURN emit_get_usec_time(MethodCallExprNode* n);
+  STATUS_RETURN emit_forward_to_vnf(MethodCallExprNode* n);
+  STATUS_RETURN emit_forward_to_group(MethodCallExprNode* n);
+  STATUS_RETURN print_parser();
+  STATUS_RETURN print_timer();
+  STATUS_RETURN print_header();
+
+  void indent();
+  llvm::LLVMContext & ctx() const;
+  llvm::Constant * const_int(uint64_t val, unsigned bits = 64, bool is_signed = false);
+  llvm::Value * pop_expr();
+  llvm::BasicBlock * resolve_label(const string &label);
+  StatusTuple lookup_var(Node *n, const std::string &name, Scopes::VarScope *scope,
+                         VariableDeclStmtNode **decl, llvm::Value **mem) const;
+
+  template <typename... Args> void emitln(const char *fmt, Args&&... params);
+  template <typename... Args> void lnemit(const char *fmt, Args&&... params);
+  template <typename... Args> void emit(const char *fmt, Args&&... params);
+  void emitln(const char *s);
+  void lnemit(const char *s);
+  void emit(const char *s);
+  void emit_comment(Node* n);
+
+  FILE* out_;
+  llvm::Module* mod_;
+  llvm::IRBuilderBase *b_;
+  int indent_;
+  int tmp_reg_index_;
+  Scopes *scopes_;
+  Scopes *proto_scopes_;
+  bool use_pre_header_;
+  std::string section_;
+  vector<vector<string> > free_instructions_;
+  vector<string> table_inits_;
+  map<string, string> proto_rewrites_;
+  map<TableDeclStmtNode *, llvm::GlobalVariable *> tables_;
+  map<TableDeclStmtNode *, int> table_fds_;
+  map<VariableDeclStmtNode *, llvm::Value *> vars_;
+  map<StructDeclStmtNode *, llvm::StructType *> structs_;
+  map<string, llvm::BasicBlock *> labels_;
+  llvm::BasicBlock *entry_bb_;
+  llvm::SwitchInst *cur_switch_;
+  llvm::Value *expr_;
+  llvm::AllocaInst *retval_;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/exception.h b/jit/src/cc/exception.h
new file mode 100644
index 0000000..216d9ad
--- /dev/null
+++ b/jit/src/cc/exception.h
@@ -0,0 +1,166 @@
+/*
+ * ====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * ====================================================================
+ */
+
+#pragma once
+
+#include <exception>
+#include <string>
+#include <tuple>
+#include <cstring>
+#include <cerrno>
+#include <cstdlib>
+#undef NDEBUG
+
+namespace ebpf {
+
+class Exception : public std::exception {
+ public:
+  virtual ~Exception() throw() {}
+};
+
+class StringException : public Exception {
+ public:
+  StringException() : errstr_("unknown") {}
+  virtual ~StringException() throw() {}
+  explicit StringException(const std::string& s) : errstr_(s) {}
+  explicit StringException(const char* s) : errstr_(s) {}
+  template <typename... Args>
+  StringException(const char* s, Args... args) {
+    char x[1024];
+    snprintf(x, sizeof(x), s, args...);
+    errstr_.assign(x);
+  }
+  virtual const char* what() const throw() {
+    return errstr_.c_str();
+  }
+ protected:
+  std::string errstr_;
+};
+
+class ErrnoException : public StringException {
+ public:
+  ErrnoException() : StringException(strerror(errno)) {}
+  explicit ErrnoException(const std::string& s) : StringException(s + ": " + strerror(errno)) {}
+  explicit ErrnoException(const std::string& s, int err) : StringException(s + ": " + strerror(err)) {}
+};
+
+class SystemException : public StringException {
+ public:
+  explicit SystemException(int status) {
+    if (status == -1) {
+      errstr_.assign("command not found");
+    } else {
+      errstr_.assign("command exited with ");
+      errstr_ += std::to_string(WEXITSTATUS(status));
+    }
+  }
+  SystemException(int status, const std::string& s) {
+    if (status == -1) {
+      errstr_.assign("command not found");
+    } else {
+      errstr_.assign("command exited with ");
+      errstr_ += std::to_string(WEXITSTATUS(status));
+    }
+    errstr_ += "; " + s + ": " + strerror(errno);
+  }
+};
+
+class CompilerException : public StringException {
+ public:
+  explicit CompilerException(const std::string& s) : StringException(s) {}
+  template <typename... Args>
+  CompilerException(const char* s, Args... args) : StringException(s, args...) {}
+};
+
+class WatermarkException : public Exception {
+ public:
+  WatermarkException() {}
+  virtual const char* what() const throw() {
+    return "Reached High Watermark";
+  }
+};
+
+class TerminateException : public Exception {
+ public:
+  TerminateException() {}
+  virtual const char* what() const throw() {
+    return "Terminated";
+  }
+};
+
+class StatusException : public Exception {
+ public:
+  explicit StatusException(int st, const std::string &msg) : st_(st), msg_(msg) {}
+  virtual const char* what() const throw() {
+    return msg_.c_str();
+  }
+  int status() const { return st_; }
+  const std::string & message() { return msg_; }
+ protected:
+  int st_;
+  std::string msg_;
+};
+
+template <typename... Args>
+std::tuple<int, std::string> mkstatus(int ret, const char *fmt, Args... args) {
+  char buf[1024];
+  snprintf(buf, sizeof(buf), fmt, args...);
+  return std::make_tuple(ret, std::string(buf));
+}
+
+static inline std::tuple<int, std::string> mkstatus(int ret, const char *msg) {
+  return std::make_tuple(ret, std::string(msg));
+}
+
+static inline std::tuple<int, std::string> mkstatus(int ret) {
+  return std::make_tuple(ret, std::string());
+}
+
+#define TRYT(CMD) \
+  do { \
+    int __status = (CMD); \
+    if (__status != 0) { \
+      throw StatusException(__status); \
+    } \
+  } while (0)
+
+#define TRY2T(CMD) \
+  do { \
+    std::tuple<int, std::string> __stp = (CMD); \
+    if (std::get<0>(__stp) != 0) { \
+      throw StatusException(std::get<0>(__stp)); \
+    } \
+  } while (0)
+
+#define TRY(CMD) \
+  do { \
+    int __status = (CMD); \
+    if (__status != 0) { \
+      return __status; \
+    } \
+  } while (0)
+
+#define TRY2(CMD) \
+  do { \
+    std::tuple<int, std::string> __stp = (CMD); \
+    if (std::get<0>(__stp) != 0) { \
+      return __stp; \
+    } \
+  } while (0)
+
+}  // namespace ebpf
diff --git a/jit/src/cc/lexer.h b/jit/src/cc/lexer.h
new file mode 100644
index 0000000..6eda36e
--- /dev/null
+++ b/jit/src/cc/lexer.h
@@ -0,0 +1,136 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#pragma once
+
+#ifndef yyFlexLexerOnce
+#undef yyFlexLexer
+#define yyFlexLexer ebpfccFlexLexer
+#include <FlexLexer.h>
+#endif
+
+#undef YY_DECL
+#define YY_DECL int ebpf::cc::Lexer::yylex()
+
+#include <iostream> // NOLINT
+#include <list>
+#include "cc/parser.yy.hh"
+
+namespace ebpf {
+namespace cc {
+
+typedef BisonParser::token::yytokentype Tok;
+
+class Lexer : public yyFlexLexer {
+ public:
+  explicit Lexer(std::istream* in)
+      : yyFlexLexer(in), prev_tok_(Tok::TSEMI), lines_({""}), yylval_(NULL), yylloc_(NULL) {
+    if (!in || !*in)
+      fprintf(stderr, "Unable to open input stream\n");
+  }
+  int yylex(BisonParser::semantic_type *lval, BisonParser::location_type *lloc) {
+    yylval_ = lval;
+    yylloc_ = lloc;
+    return yylex();
+  }
+  std::string text(const BisonParser::location_type& loc) const {
+    return text(loc.begin, loc.end);
+  }
+  std::string text(const position& begin, const position& end) const {
+    std::string result;
+    for (size_t i = begin.line; i <= end.line; ++i) {
+      if (i == begin.line && i == end.line) {
+        result += lines_.at(i - 1).substr(begin.column - 1, end.column - begin.column);
+      } else if (i == begin.line && i < end.line) {
+        result += lines_.at(i - 1).substr(begin.column - 1);
+      } else if (i > begin.line && i == end.line) {
+        result += lines_.at(i - 1).substr(0, end.column);
+      } else if (i > begin.line && i == end.line) {
+        result += lines_.at(i - 1);
+      }
+    }
+    return result;
+  }
+ private:
+
+  // true if a semicolon should be replaced here
+  bool next_line() {
+    lines_.push_back("");
+    yylloc_->lines();
+    yylloc_->step();
+    switch (prev_tok_) {
+    case Tok::TIDENTIFIER:
+    case Tok::TINTEGER:
+    case Tok::THEXINTEGER:
+    case Tok::TRBRACE:
+    case Tok::TRPAREN:
+    case Tok::TRBRACK:
+    case Tok::TTRUE:
+    case Tok::TFALSE:
+      return true;
+    default:
+      break;
+    }
+    return false;
+  }
+
+  Tok save(Tok tok, bool ignore_text = false) {
+    if (!ignore_text) {
+      save_text();
+    }
+
+    switch (tok) {
+    case Tok::TIDENTIFIER:
+    case Tok::TINTEGER:
+    case Tok::THEXINTEGER:
+      yylval_->string = new std::string(yytext, yyleng);
+      break;
+    default:
+      yylval_->token = tok;
+    }
+    prev_tok_ = tok;
+    return tok;
+  }
+
+  /*
+  std::string * alloc_string(const char *c, size_t len) {
+    strings_.push_back(std::unique_ptr<std::string>(new std::string(c, len)));
+    return strings_.back().get();
+  }
+
+  std::string * alloc_string(const std::string &s) {
+    strings_.push_back(std::unique_ptr<std::string>(new std::string(s)));
+    return strings_.back().get();
+  }
+  */
+
+  void save_text() {
+    lines_.back().append(yytext, yyleng);
+    yylloc_->columns(yyleng);
+  }
+
+  int yylex();
+  Tok prev_tok_;
+  std::vector<std::string> lines_;
+  //std::list<std::unique_ptr<std::string>> strings_;
+  BisonParser::semantic_type *yylval_;
+  BisonParser::location_type *yylloc_;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/lexer.ll b/jit/src/cc/lexer.ll
new file mode 100644
index 0000000..9424787
--- /dev/null
+++ b/jit/src/cc/lexer.ll
@@ -0,0 +1,108 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+%{
+#include "cc/lexer.h"
+%}
+
+%option yylineno nodefault yyclass="Lexer" noyywrap c++ prefix="ebpfcc"
+%option never-interactive
+%{
+#include <string>
+#include "cc/parser.yy.hh"
+std::string tmp_str_cc;
+%}
+
+%x STRING_
+%%
+
+\'                      {BEGIN STRING_;}
+<STRING_>\'             { BEGIN 0;
+                        yylval_->string = new std::string(tmp_str_cc);
+                        tmp_str_cc = "";
+                        return Tok::TSTRING;
+                        }
+<STRING_>.              {tmp_str_cc += *yytext; }
+<STRING_>\n              {tmp_str_cc += "\n"; }
+
+
+
+[ \t]+                  { save_text(); }
+\n                      { if (next_line()) { return save(Tok::TSEMI, true); } }
+"//".*\n                { if (next_line()) { return save(Tok::TSEMI, true); } }
+^"#"                    return save(Tok::TPRAGMA);
+"="                     return save(Tok::TEQUAL);
+"=="                    return save(Tok::TCEQ);
+"!="                    return save(Tok::TCNE);
+"<"                     return save(Tok::TCLT);
+"<="                    return save(Tok::TCLE);
+">"                     return save(Tok::TCGT);
+">="                    return save(Tok::TCGE);
+"("                     return save(Tok::TLPAREN);
+")"                     return save(Tok::TRPAREN);
+"{"                     return save(Tok::TLBRACE);
+"}"                     return save(Tok::TRBRACE);
+"["                     return save(Tok::TLBRACK);
+"]"                     return save(Tok::TRBRACK);
+"."                     return save(Tok::TDOT);
+","                     return save(Tok::TCOMMA);
+"+"                     return save(Tok::TPLUS);
+"-"                     return save(Tok::TMINUS);
+"*"                     return save(Tok::TMUL);
+"/"                     return save(Tok::TDIV);
+"%"                     return save(Tok::TMOD);
+"^"                     return save(Tok::TXOR);
+"$"                     return save(Tok::TDOLLAR);
+"!"                     return save(Tok::TNOT);
+"~"                     return save(Tok::TCMPL);
+":"                     return save(Tok::TCOLON);
+"::"                    return save(Tok::TSCOPE);
+";"                     return save(Tok::TSEMI);
+"&&"                    return save(Tok::TAND);
+"||"                    return save(Tok::TOR);
+"&"                     return save(Tok::TLAND);
+"|"                     return save(Tok::TLOR);
+"@"                     return save(Tok::TAT);
+
+"const"                 return save(Tok::TCONST);
+"struct"                return save(Tok::TSTRUCT);
+"var"                   return save(Tok::TVAR);
+"state"                 return save(Tok::TSTATE);
+"timer"                 return save(Tok::TTIMER);
+"goto"                  return save(Tok::TGOTO);
+"continue"              return save(Tok::TCONTINUE);
+"next"                  return save(Tok::TNEXT);
+"on_match"              return save(Tok::TMATCH);
+"on_miss"               return save(Tok::TMISS);
+"on_failure"            return save(Tok::TFAILURE);
+"on_valid"              return save(Tok::TVALID);
+"true"                  return save(Tok::TTRUE);
+"false"                 return save(Tok::TFALSE);
+"if"                    return save(Tok::TIF);
+"else"                  return save(Tok::TELSE);
+"switch"                return save(Tok::TSWITCH);
+"case"                  return save(Tok::TCASE);
+"return"                return save(Tok::TRETURN);
+
+[a-zA-Z][a-zA-Z0-9_]*   return save(Tok::TIDENTIFIER);
+[0-9]+                  return save(Tok::TINTEGER);
+0x[0-9a-fA-F]+          return save(Tok::THEXINTEGER);
+
+.                       printf("Unknown token\n"); yyterminate();
+
+%%
diff --git a/jit/src/cc/libbpf.c b/jit/src/cc/libbpf.c
new file mode 100644
index 0000000..da76131
--- /dev/null
+++ b/jit/src/cc/libbpf.c
@@ -0,0 +1,211 @@
+/* eBPF mini library */
+#include <stdlib.h>
+#include <stdio.h>
+#include <linux/unistd.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/bpf.h>
+#include <errno.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <linux/if_packet.h>
+#include <linux/pkt_sched.h>
+#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include "libbpf.h"
+
+static __u64 ptr_to_u64(void *ptr)
+{
+  return (__u64) (unsigned long) ptr;
+}
+
+int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, int max_entries)
+{
+  union bpf_attr attr = {
+    .map_type = map_type,
+    .key_size = key_size,
+    .value_size = value_size,
+    .max_entries = max_entries
+  };
+
+  return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
+}
+
+int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags)
+{
+  union bpf_attr attr = {
+    .map_fd = fd,
+    .key = ptr_to_u64(key),
+    .value = ptr_to_u64(value),
+    .flags = flags,
+  };
+
+  return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
+}
+
+int bpf_lookup_elem(int fd, void *key, void *value)
+{
+  union bpf_attr attr = {
+    .map_fd = fd,
+    .key = ptr_to_u64(key),
+    .value = ptr_to_u64(value),
+  };
+
+  return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
+}
+
+int bpf_delete_elem(int fd, void *key)
+{
+  union bpf_attr attr = {
+    .map_fd = fd,
+    .key = ptr_to_u64(key),
+  };
+
+  return syscall(__NR_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
+}
+
+int bpf_get_next_key(int fd, void *key, void *next_key)
+{
+  union bpf_attr attr = {
+    .map_fd = fd,
+    .key = ptr_to_u64(key),
+    .next_key = ptr_to_u64(next_key),
+  };
+
+  return syscall(__NR_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
+}
+
+#define ROUND_UP(x, n) (((x) + (n) - 1u) & ~((n) - 1u))
+
+char bpf_log_buf[LOG_BUF_SIZE];
+
+int bpf_prog_load(enum bpf_prog_type prog_type,
+    const struct bpf_insn *insns, int prog_len,
+    const char *license)
+{
+  union bpf_attr attr = {
+    .prog_type = prog_type,
+    .insns = ptr_to_u64((void *) insns),
+    .insn_cnt = prog_len / sizeof(struct bpf_insn),
+    .license = ptr_to_u64((void *) license),
+    .log_buf = ptr_to_u64(bpf_log_buf),
+    .log_size = LOG_BUF_SIZE,
+    .log_level = 1,
+  };
+
+  bpf_log_buf[0] = 0;
+
+  return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
+}
+
+int bpf_open_raw_sock(const char *name)
+{
+  struct sockaddr_ll sll;
+  int sock;
+
+  sock = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, htons(ETH_P_ALL));
+  if (sock < 0) {
+    printf("cannot create raw socket\n");
+    return -1;
+  }
+
+  memset(&sll, 0, sizeof(sll));
+  sll.sll_family = AF_PACKET;
+  sll.sll_ifindex = if_nametoindex(name);
+  sll.sll_protocol = htons(ETH_P_ALL);
+  if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
+    printf("bind to %s: %s\n", name, strerror(errno));
+    close(sock);
+    return -1;
+  }
+
+  return sock;
+}
+
+int bpf_attach_socket(int sock, int prog) {
+  return setsockopt(sock, SOL_SOCKET, 50 /*SO_ATTACH_BPF*/, &prog, sizeof(prog));
+}
+
+static int cb(const struct nlmsghdr *nlh, void *data) {
+  struct nlmsgerr *err;
+  if (nlh->nlmsg_type == NLMSG_ERROR) {
+    err = mnl_nlmsg_get_payload(nlh);
+    if (err->error != 0) {
+      fprintf(stderr, "bpf tc netlink command failed (%d): %s\n",
+          err->error, strerror(-1 * err->error));
+      return -1;
+    } else {
+      return 0;
+    }
+  } else {
+    return -1;
+  }
+}
+
+int bpf_attach_filter(int progfd, const char *prog_name, uint32_t ifindex, uint8_t prio, uint32_t classid)
+{
+  int rc = -1;
+  char buf[1024];
+  struct nlmsghdr *nlh;
+  struct tcmsg *tc;
+  struct nlattr *opt;
+  struct mnl_socket *nl = NULL;
+  unsigned int portid;
+  ssize_t bytes;
+  int seq = getpid();
+
+  memset(buf, 0, sizeof(buf));
+
+  nlh = mnl_nlmsg_put_header(buf);
+
+  nlh->nlmsg_type = RTM_NEWTFILTER;
+  nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK | NLM_F_EXCL;
+  nlh->nlmsg_seq = seq;
+  tc = mnl_nlmsg_put_extra_header(nlh, sizeof(*tc));
+  tc->tcm_family = AF_UNSPEC;
+  tc->tcm_info = TC_H_MAKE(prio << 16, htons(ETH_P_ALL));
+  tc->tcm_ifindex = ifindex;
+  mnl_attr_put_strz(nlh, TCA_KIND, "bpf");
+  opt = mnl_attr_nest_start(nlh, TCA_OPTIONS);
+  mnl_attr_put_u32(nlh, 6 /*TCA_BPF_FD*/, progfd);
+  mnl_attr_put_strz(nlh, 7 /*TCP_BPF_NAME*/, prog_name);
+  mnl_attr_put_u32(nlh, 3 /*TCA_BPF_CLASSID*/, classid);
+  mnl_attr_nest_end(nlh, opt);
+
+  nl = mnl_socket_open(NETLINK_ROUTE);
+  if (!nl || (uintptr_t)nl == (uintptr_t)-1) {
+    perror("mnl_socket_open");
+    goto cleanup;
+  }
+
+  if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+    perror("mnl_socket_bind");
+    goto cleanup;
+  }
+
+  portid = mnl_socket_get_portid(nl);
+
+  if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+    perror("mnl_socket_sendto");
+    goto cleanup;
+  }
+  if ((bytes = mnl_socket_recvfrom(nl, buf, sizeof(buf))) < 0) {
+    perror("mnl_socket_recvfrom");
+    goto cleanup;
+  }
+
+  if (mnl_cb_run(buf, bytes, seq, portid, cb, NULL) < 0) {
+    perror("mnl_cb_run");
+    goto cleanup;
+  }
+
+  rc = 0;
+
+cleanup:
+  if (nl && (uintptr_t)nl != (uintptr_t)-1)
+    if (mnl_socket_close(nl) < 0)
+      perror("mnl_socket_close");
+  return rc;
+}
diff --git a/jit/src/cc/node.cc b/jit/src/cc/node.cc
new file mode 100644
index 0000000..c479421
--- /dev/null
+++ b/jit/src/cc/node.cc
@@ -0,0 +1,53 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#include <stdio.h>
+#include <vector>
+#include <string>
+
+#include "cc/node.h"
+
+namespace ebpf {
+namespace cc {
+
+#define ACCEPT(type, func) \
+  STATUS_RETURN type::accept(Visitor* v) { return v->visit_##func(this); }
+EXPAND_NODES(ACCEPT)
+#undef ACCEPT
+
+VariableDeclStmtNode* StructDeclStmtNode::field(const string& name) const {
+  for (auto it = stmts_.begin(); it != stmts_.end(); ++it) {
+    if ((*it)->id_->name_ == name) {
+      return it->get();
+    }
+  }
+  return NULL;
+}
+
+int StructDeclStmtNode::indexof(const string& name) const {
+  int i = 0;
+  for (auto it = stmts_.begin(); it != stmts_.end(); ++it, ++i) {
+    if ((*it)->id_->name_ == name) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/node.h b/jit/src/cc/node.h
new file mode 100644
index 0000000..e5821fd
--- /dev/null
+++ b/jit/src/cc/node.h
@@ -0,0 +1,613 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#pragma once
+
+#include <vector>
+#include <bitset>
+#include <string>
+#include <memory>
+#include <algorithm>
+#include <stdint.h>
+#include "cc/scope.h"
+
+#define REVISION_MASK 0xfff
+#define MAJOR_VER_POS 22
+#define MAJOR_VER_MASK ~((1 << MAJOR_VER_POS) - 1)
+#define MINOR_VER_POS 12
+#define MINOR_VER_MASK (~((1 << MINOR_VER_POS) - 1) & (~(MAJOR_VER_MASK)))
+#define GET_MAJOR_VER(version) ((version & MAJOR_VER_MASK) >> MAJOR_VER_POS)
+#define GET_MINOR_VER(version) ((version & MINOR_VER_MASK) >> MINOR_VER_POS)
+#define GET_REVISION(version) (version & REVISION_MASK)
+#define MAKE_VERSION(major, minor, rev) \
+    ((major << MAJOR_VER_POS) | \
+     (minor << MINOR_VER_POS) | \
+     (rev & REVISION_MASK))
+
+#define STATUS_RETURN __attribute((warn_unused_result)) StatusTuple
+
+namespace ebpf {
+
+template <class T, class... Args>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(Args &&... args) {
+  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+typedef std::tuple<int, std::string> StatusTuple;
+
+namespace cc {
+
+using std::unique_ptr;
+using std::move;
+using std::string;
+using std::vector;
+using std::bitset;
+using std::find;
+
+typedef unique_ptr<string> String;
+
+#define NODE_EXPRESSIONS(EXPAND) \
+  EXPAND(IdentExprNode, ident_expr_node) \
+  EXPAND(AssignExprNode, assign_expr_node) \
+  EXPAND(PacketExprNode, packet_expr_node) \
+  EXPAND(IntegerExprNode, integer_expr_node) \
+  EXPAND(BinopExprNode, binop_expr_node) \
+  EXPAND(UnopExprNode, unop_expr_node) \
+  EXPAND(BitopExprNode, bitop_expr_node) \
+  EXPAND(GotoExprNode, goto_expr_node) \
+  EXPAND(ReturnExprNode, return_expr_node) \
+  EXPAND(MethodCallExprNode, method_call_expr_node)
+
+#define NODE_STATEMENTS(EXPAND) \
+  EXPAND(ExprStmtNode, expr_stmt_node) \
+  EXPAND(BlockStmtNode, block_stmt_node) \
+  EXPAND(IfStmtNode, if_stmt_node) \
+  EXPAND(OnValidStmtNode, onvalid_stmt_node) \
+  EXPAND(SwitchStmtNode, switch_stmt_node) \
+  EXPAND(CaseStmtNode, case_stmt_node) \
+  EXPAND(StructVariableDeclStmtNode, struct_variable_decl_stmt_node) \
+  EXPAND(IntegerVariableDeclStmtNode, integer_variable_decl_stmt_node) \
+  EXPAND(StructDeclStmtNode, struct_decl_stmt_node) \
+  EXPAND(StateDeclStmtNode, state_decl_stmt_node) \
+  EXPAND(TimerDeclStmtNode, timer_decl_stmt_node) \
+  EXPAND(ParserStateStmtNode, parser_state_stmt_node) \
+  EXPAND(MatchDeclStmtNode, match_decl_stmt_node) \
+  EXPAND(MissDeclStmtNode, miss_decl_stmt_node) \
+  EXPAND(FailureDeclStmtNode, failure_decl_stmt_node) \
+  EXPAND(TableDeclStmtNode, table_decl_stmt_node) \
+  EXPAND(VersionStmtNode, version_stmt_node)
+
+#define EXPAND_NODES(EXPAND) \
+  NODE_EXPRESSIONS(EXPAND) \
+  NODE_STATEMENTS(EXPAND)
+
+class Visitor;
+
+// forward declare all classes
+#define FORWARD(type, func) class type;
+EXPAND_NODES(FORWARD)
+#undef FORWARD
+
+#define DECLARE(type) \
+  typedef unique_ptr<type> Ptr; \
+  virtual StatusTuple accept(Visitor* v);
+
+class Node {
+ public:
+  typedef unique_ptr<Node> Ptr;
+  Node() : line_(-1), column_(-1) {}
+  virtual ~Node() {}
+  virtual StatusTuple accept(Visitor* v) = 0;
+  int line_;
+  int column_;
+  string text_;
+};
+
+template <typename... Args>
+std::tuple<int, std::string> mkstatus_(Node *n, const char *fmt, Args... args) {
+  char buf[1024];
+  snprintf(buf, sizeof(buf), fmt, args...);
+  string out_msg(buf);
+  if (n->line_ > 0)
+    out_msg += "\n" + n->text_;
+  return std::make_tuple(n->line_ ? n->line_ : -1, out_msg);
+}
+
+static inline std::tuple<int, std::string> mkstatus_(Node *n, const char *msg) {
+  string out_msg(msg);
+  if (n->line_ > 0)
+    out_msg += "\n" + n->text_;
+  return std::make_tuple(n->line_ ? n->line_ : -1, out_msg);
+}
+
+class StmtNode : public Node {
+ public:
+  typedef unique_ptr<StmtNode> Ptr;
+  virtual StatusTuple accept(Visitor* v) = 0;
+
+};
+typedef vector<StmtNode::Ptr> StmtNodeList;
+
+class ExprNode : public Node {
+ public:
+  typedef unique_ptr<ExprNode> Ptr;
+  virtual StatusTuple accept(Visitor* v) = 0;
+  enum expr_type { STRUCT, INTEGER, VOID, UNKNOWN };
+  enum prop_flag { READ = 0, WRITE, PROTO, IS_LHS, IS_REF, LAST };
+  expr_type typeof_;
+  StructDeclStmtNode *struct_type_;
+  size_t bit_width_;
+  bitset<LAST> flags_;
+  unique_ptr<BitopExprNode> bitop_;
+  ExprNode() : typeof_(UNKNOWN), struct_type_(NULL), flags_(1 << READ) {}
+  void copy_type(const ExprNode& other) {
+    typeof_ = other.typeof_;
+    struct_type_ = other.struct_type_;
+    bit_width_ = other.bit_width_;
+    flags_ = other.flags_;
+  }
+  bool is_lhs() const { return flags_[IS_LHS]; }
+  bool is_ref() const { return flags_[IS_REF]; }
+};
+
+typedef vector<ExprNode::Ptr> ExprNodeList;
+
+class IdentExprNode : public ExprNode {
+ public:
+  DECLARE(IdentExprNode)
+
+  string name_;
+  string sub_name_;
+  string scope_name_;
+  VariableDeclStmtNode *decl_;
+  VariableDeclStmtNode *sub_decl_;
+  IdentExprNode(const IdentExprNode& other) {
+    name_ = other.name_;
+    sub_name_ = other.sub_name_;
+    scope_name_ = other.scope_name_;
+    decl_ = other.decl_;
+    sub_decl_ = other.sub_decl_;
+  }
+  IdentExprNode::Ptr copy() const {
+    return IdentExprNode::Ptr(new IdentExprNode(*this));
+  }
+  explicit IdentExprNode(const string& id) : name_(id) {}
+  explicit IdentExprNode(const char* id) : name_(id) {}
+  void prepend_scope(const string& id) {
+    scope_name_ = id;
+  }
+  void append_scope(const string& id) {
+    scope_name_ = move(name_);
+    name_ = id;
+  }
+  void prepend_dot(const string& id) {
+    sub_name_ = move(name_);
+    name_ = id;
+  }
+  void append_dot(const string& id) {
+    // we don't support nested struct so keep all subs as single variable
+    if (!sub_name_.empty()) {
+      sub_name_ += "." + id;
+    } else {
+      sub_name_ = id;
+    }
+  }
+  const string& full_name() {
+    if (full_name_.size()) {
+      return full_name_;  // lazy init
+    }
+    if (scope_name_.size()) {
+      full_name_ += scope_name_ + "::";
+    }
+    full_name_ += name_;
+    if (sub_name_.size()) {
+      full_name_ += "." + sub_name_;
+    }
+    return full_name_;
+  }
+  const char* c_str() const { return name_.c_str(); }
+ private:
+  string full_name_;
+};
+
+class BitopExprNode : public ExprNode {
+ public:
+  DECLARE(BitopExprNode)
+
+  ExprNode::Ptr expr_;
+  size_t bit_offset_;
+  size_t bit_width_;
+  BitopExprNode(const string& bofs, const string& bsz)
+      : bit_offset_(strtoul(bofs.c_str(), NULL, 0)), bit_width_(strtoul(bsz.c_str(), NULL, 0)) {}
+};
+
+typedef vector<IdentExprNode::Ptr> IdentExprNodeList;
+
+class AssignExprNode : public ExprNode {
+ public:
+  DECLARE(AssignExprNode)
+
+  IdentExprNode::Ptr id_;
+  ExprNode::Ptr rhs_;
+  AssignExprNode(IdentExprNode::Ptr id, ExprNode::Ptr rhs)
+      : id_(move(id)), rhs_(move(rhs)) {
+    id_->flags_[ExprNode::IS_LHS] = true;
+  }
+};
+
+class PacketExprNode : public ExprNode {
+ public:
+  DECLARE(PacketExprNode)
+
+  IdentExprNode::Ptr id_;
+  explicit PacketExprNode(IdentExprNode::Ptr id) : id_(move(id)) {}
+};
+
+class IntegerExprNode : public ExprNode {
+ public:
+  DECLARE(IntegerExprNode)
+
+  size_t bits_;
+  string val_;
+  IntegerExprNode(string* val, string* bits)
+      : bits_(strtoul(bits->c_str(), NULL, 0)), val_(move(*val)) {
+    delete val;
+    delete bits;
+  }
+  explicit IntegerExprNode(string* val)
+      : bits_(0), val_(move(*val)) {
+    delete val;
+  }
+  explicit IntegerExprNode(const string& val) : bits_(0), val_(val) {}
+  explicit IntegerExprNode(const string& val, size_t bits) : bits_(bits), val_(val) {}
+};
+
+class BinopExprNode : public ExprNode {
+ public:
+  DECLARE(BinopExprNode)
+
+  ExprNode::Ptr lhs_;
+  int op_;
+  ExprNode::Ptr rhs_;
+  BinopExprNode(ExprNode::Ptr lhs, int op, ExprNode::Ptr rhs)
+      : lhs_(move(lhs)), op_(op), rhs_(move(rhs))
+  {}
+};
+
+class UnopExprNode : public ExprNode {
+ public:
+  DECLARE(UnopExprNode)
+
+  ExprNode::Ptr expr_;
+  int op_;
+  UnopExprNode(int op, ExprNode::Ptr expr) : expr_(move(expr)), op_(op) {}
+};
+
+class GotoExprNode : public ExprNode {
+ public:
+  DECLARE(GotoExprNode)
+
+  bool is_continue_;
+  IdentExprNode::Ptr id_;
+  GotoExprNode(IdentExprNode::Ptr id, bool is_continue = false)
+      : is_continue_(is_continue), id_(move(id)) {}
+};
+
+class ReturnExprNode : public ExprNode {
+ public:
+  DECLARE(ReturnExprNode)
+
+  ExprNode::Ptr expr_;
+  ReturnExprNode(ExprNode::Ptr expr)
+      : expr_(move(expr)) {}
+};
+
+class VersionStmtNode : public StmtNode {
+  public:
+    DECLARE(VersionStmtNode)
+
+    VersionStmtNode(const int major, const int minor, const int rev)
+      : major_(major), minor_(minor), rev_(rev) {}
+    uint32_t major_, minor_, rev_;
+};
+
+class BlockStmtNode : public StmtNode {
+ public:
+  DECLARE(BlockStmtNode)
+
+  explicit BlockStmtNode(StmtNodeList stmts = StmtNodeList())
+    : stmts_(move(stmts)), scope_(NULL), ver_(0, 0, 0) {}
+  ~BlockStmtNode() { delete scope_; }
+  StmtNodeList stmts_;
+  Scopes::VarScope* scope_;
+  VersionStmtNode ver_;
+};
+
+class MethodCallExprNode : public ExprNode {
+ public:
+  DECLARE(MethodCallExprNode)
+
+  IdentExprNode::Ptr id_;
+  ExprNodeList args_;
+  BlockStmtNode::Ptr block_;
+  MethodCallExprNode(IdentExprNode::Ptr id, ExprNodeList&& args, int lineno)
+      : id_(move(id)), args_(move(args)), block_(make_unique<BlockStmtNode>()) {
+    line_ = lineno;
+  }
+};
+
+class ExprStmtNode : public StmtNode {
+ public:
+  DECLARE(ExprStmtNode)
+
+  ExprNode::Ptr expr_;
+  explicit ExprStmtNode(ExprNode::Ptr expr) : expr_(move(expr)) {}
+};
+
+class IfStmtNode : public StmtNode {
+ public:
+  DECLARE(IfStmtNode)
+
+  ExprNode::Ptr cond_;
+  StmtNode::Ptr true_block_;
+  StmtNode::Ptr false_block_;
+  // create an if () {} expression
+  IfStmtNode(ExprNode::Ptr cond, StmtNode::Ptr true_block)
+      : cond_(move(cond)), true_block_(move(true_block)) {}
+  // create an if () {} else {} expression
+  IfStmtNode(ExprNode::Ptr cond, StmtNode::Ptr true_block, StmtNode::Ptr false_block)
+      : cond_(move(cond)), true_block_(move(true_block)),
+      false_block_(move(false_block)) {}
+};
+
+class OnValidStmtNode : public StmtNode {
+ public:
+  DECLARE(OnValidStmtNode)
+
+  IdentExprNode::Ptr cond_;
+  StmtNode::Ptr block_;
+  StmtNode::Ptr else_block_;
+  // create an onvalid () {} expression
+  OnValidStmtNode(IdentExprNode::Ptr cond, StmtNode::Ptr block)
+      : cond_(move(cond)), block_(move(block)) {}
+  // create an onvalid () {} else {} expression
+  OnValidStmtNode(IdentExprNode::Ptr cond, StmtNode::Ptr block, StmtNode::Ptr else_block)
+      : cond_(move(cond)), block_(move(block)),
+      else_block_(move(else_block)) {}
+};
+
+class SwitchStmtNode : public StmtNode {
+ public:
+  DECLARE(SwitchStmtNode)
+  ExprNode::Ptr cond_;
+  BlockStmtNode::Ptr block_;
+  SwitchStmtNode(ExprNode::Ptr cond, BlockStmtNode::Ptr block)
+      : cond_(move(cond)), block_(move(block)) {}
+};
+
+class CaseStmtNode : public StmtNode {
+ public:
+  DECLARE(CaseStmtNode)
+  IntegerExprNode::Ptr value_;
+  BlockStmtNode::Ptr block_;
+  CaseStmtNode(IntegerExprNode::Ptr value, BlockStmtNode::Ptr block)
+      : value_(move(value)), block_(move(block)) {}
+  explicit CaseStmtNode(BlockStmtNode::Ptr block) : block_(move(block)) {}
+};
+
+class VariableDeclStmtNode : public StmtNode {
+ public:
+  typedef unique_ptr<VariableDeclStmtNode> Ptr;
+  virtual StatusTuple accept(Visitor* v) = 0;
+  enum storage_type { INTEGER, STRUCT, STRUCT_REFERENCE };
+
+  IdentExprNode::Ptr id_;
+  ExprNodeList init_;
+  enum storage_type storage_type_;
+  size_t bit_width_;
+  size_t bit_offset_;
+  int slot_;
+  string scope_id_;
+  explicit VariableDeclStmtNode(IdentExprNode::Ptr id, storage_type t, size_t bit_width = 0, size_t bit_offset = 0)
+      : id_(move(id)), storage_type_(t), bit_width_(bit_width), bit_offset_(bit_offset), slot_(0) {}
+  const char* scope_id() const { return scope_id_.c_str(); }
+  bool is_struct() { return (storage_type_ == STRUCT || storage_type_ == STRUCT_REFERENCE); }
+  bool is_pointer() { return (storage_type_ == STRUCT_REFERENCE); }
+};
+
+typedef vector<VariableDeclStmtNode::Ptr> FormalList;
+
+class StructVariableDeclStmtNode : public VariableDeclStmtNode {
+ public:
+  DECLARE(StructVariableDeclStmtNode)
+
+  IdentExprNode::Ptr struct_id_;
+  StructVariableDeclStmtNode(IdentExprNode::Ptr struct_id, IdentExprNode::Ptr id,
+                             VariableDeclStmtNode::storage_type t = VariableDeclStmtNode::STRUCT)
+      : VariableDeclStmtNode(move(id), t), struct_id_(move(struct_id)) {}
+};
+
+class IntegerVariableDeclStmtNode : public VariableDeclStmtNode {
+ public:
+  DECLARE(IntegerVariableDeclStmtNode)
+
+  IntegerVariableDeclStmtNode(IdentExprNode::Ptr id, const string& bits)
+      : VariableDeclStmtNode(move(id), VariableDeclStmtNode::INTEGER, strtoul(bits.c_str(), NULL, 0)) {}
+};
+
+class StructDeclStmtNode : public StmtNode {
+ public:
+  DECLARE(StructDeclStmtNode)
+
+  IdentExprNode::Ptr id_;
+  FormalList stmts_;
+  size_t bit_width_;
+  bool packed_;
+  StructDeclStmtNode(IdentExprNode::Ptr id, FormalList&& stmts = FormalList())
+      : id_(move(id)), stmts_(move(stmts)), bit_width_(0), packed_(false) {}
+  VariableDeclStmtNode* field(const string& name) const;
+  int indexof(const string& name) const;
+  bool is_packed() const { return packed_; }
+};
+
+class ParserStateStmtNode : public StmtNode {
+ public:
+  DECLARE(ParserStateStmtNode)
+
+  IdentExprNode::Ptr id_;
+  StmtNode* next_state_;
+  string scope_id_;
+  explicit ParserStateStmtNode(IdentExprNode::Ptr id)
+      : id_(move(id)) {}
+  static Ptr make(const IdentExprNode::Ptr& id) {
+    return Ptr(new ParserStateStmtNode(id->copy()));
+  }
+  string scoped_name() const { return scope_id_ + id_->name_; }
+};
+
+class StateDeclStmtNode : public StmtNode {
+ public:
+  DECLARE(StateDeclStmtNode)
+
+  struct Sub {
+    IdentExprNode::Ptr id_;
+    BlockStmtNode::Ptr block_;
+    ParserStateStmtNode::Ptr parser_;
+    Scopes::StateScope* scope_;
+    Sub(decltype(id_) id, decltype(block_) block, decltype(parser_) parser, decltype(scope_) scope)
+        : id_(move(id)), block_(move(block)), parser_(move(parser)), scope_(scope) {}
+    ~Sub() { delete scope_; }
+    Sub(Sub&& other) : scope_(NULL) {
+      *this = move(other);
+    }
+    Sub& operator=(Sub&& other) {
+      if (this == &other) {
+        return *this;
+      }
+      id_ = move(other.id_);
+      block_ = move(other.block_);
+      parser_ = move(other.parser_);
+      std::swap(scope_, other.scope_);
+      return *this;
+    }
+  };
+
+  IdentExprNode::Ptr id_;
+  StmtNodeList init_;
+  string scope_id_;
+  ParserStateStmtNode::Ptr parser_;
+  vector<Sub> subs_;
+  StateDeclStmtNode() {}
+  StateDeclStmtNode(IdentExprNode::Ptr id, BlockStmtNode::Ptr block) : id_(move(id)) {
+    subs_.push_back(Sub(make_unique<IdentExprNode>(""), move(block), ParserStateStmtNode::Ptr(), NULL));
+  }
+  StateDeclStmtNode(IdentExprNode::Ptr id1, IdentExprNode::Ptr id2, BlockStmtNode::Ptr block)
+      : id_(move(id1)) {
+    subs_.push_back(Sub(move(id2), move(block), ParserStateStmtNode::Ptr(), NULL));
+  }
+  string scoped_name() const { return scope_id_ + id_->name_; }
+  vector<Sub>::iterator find_sub(const string& id) {
+    return find_if(subs_.begin(), subs_.end(), [&id] (const Sub& sub) {
+      if (sub.id_->name_ == id)
+        return true;
+      return false;
+    });
+
+  }
+};
+
+class TimerDeclStmtNode : public StateDeclStmtNode {
+ public:
+  DECLARE(TimerDeclStmtNode)
+
+  BlockStmtNode::Ptr block_;
+  Scopes::StateScope* scope_;
+  explicit TimerDeclStmtNode(BlockStmtNode::Ptr block): block_(move(block)) {
+  }
+};
+
+class MatchDeclStmtNode : public StmtNode {
+ public:
+  DECLARE(MatchDeclStmtNode)
+
+  IdentExprNode::Ptr id_;
+  FormalList formals_;
+  BlockStmtNode::Ptr block_;
+  MatchDeclStmtNode(IdentExprNode::Ptr id, FormalList&& formals, BlockStmtNode::Ptr block)
+      : id_(move(id)), formals_(move(formals)), block_(move(block)) {}
+};
+
+class MissDeclStmtNode : public StmtNode {
+ public:
+  DECLARE(MissDeclStmtNode)
+
+  IdentExprNode::Ptr id_;
+  FormalList formals_;
+  BlockStmtNode::Ptr block_;
+  MissDeclStmtNode(IdentExprNode::Ptr id, FormalList&& formals, BlockStmtNode::Ptr block)
+      : id_(move(id)), formals_(move(formals)), block_(move(block)) {}
+};
+
+class FailureDeclStmtNode : public StmtNode {
+ public:
+  DECLARE(FailureDeclStmtNode)
+
+  IdentExprNode::Ptr id_;
+  FormalList formals_;
+  BlockStmtNode::Ptr block_;
+  FailureDeclStmtNode(IdentExprNode::Ptr id, FormalList&& formals, BlockStmtNode::Ptr block)
+      : id_(move(id)), formals_(move(formals)), block_(move(block)) {}
+};
+
+class TableDeclStmtNode : public StmtNode {
+ public:
+  DECLARE(TableDeclStmtNode)
+
+  IdentExprNode::Ptr table_type_;
+  IdentExprNodeList templates_;
+  IdentExprNode::Ptr id_;
+  IdentExprNode * key_id() { return templates_.at(0).get(); }
+  IdentExprNode * leaf_id() { return templates_.at(1).get(); }
+  IdentExprNode * type_id() { return templates_.at(2).get(); }
+  IdentExprNode * policy_id() { return templates_.at(3).get(); }
+  size_t size_;
+  TableDeclStmtNode(IdentExprNode::Ptr table_type, IdentExprNodeList&& templates,
+                    IdentExprNode::Ptr id, string* size)
+      : table_type_(move(table_type)), templates_(move(templates)), id_(move(id)),
+      size_(strtoul(size->c_str(), NULL, 0)) {
+    delete size;
+  }
+};
+
+class Visitor {
+ public:
+  typedef StatusTuple Ret;
+  virtual ~Visitor() {}
+  virtual STATUS_RETURN visit(Node* n) {
+    return n->accept(this);
+  }
+#define VISIT(type, func) virtual STATUS_RETURN visit_##func(type* n) = 0;
+  EXPAND_NODES(VISIT)
+#undef VISIT
+};
+
+#undef DECLARE
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/parser.cc b/jit/src/cc/parser.cc
new file mode 100644
index 0000000..9f202c8
--- /dev/null
+++ b/jit/src/cc/parser.cc
@@ -0,0 +1,230 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#include <algorithm>
+#include <assert.h>
+#include "exception.h"
+#include "cc/parser.h"
+#include "cc/type_helper.h"
+
+namespace ebpf {
+namespace cc {
+
+using std::find;
+using std::move;
+using std::string;
+using std::unique_ptr;
+
+bool Parser::variable_exists(VariableDeclStmtNode* decl, bool search_local) {
+  if (scopes_->current_var()->lookup(decl->id_->name_, search_local) == NULL) {
+    return false;
+  }
+  return true;
+}
+
+VariableDeclStmtNode* Parser::variable_add(VariableDeclStmtNode* decl) {
+
+  if (variable_exists(decl, true)) {
+    fprintf(stderr, "redeclaration of variable %s", decl->id_->name_.c_str());
+    assert(0 && "redeclaration of variable");
+    // how to raise a bison error from here?
+    return decl;
+  }
+  decl->scope_id_ = string("v") + std::to_string(scopes_->current_var()->id_) + string("_");
+  scopes_->current_var()->add(decl->id_->name_, decl);
+  return decl;
+}
+
+VariableDeclStmtNode* Parser::variable_add(VariableDeclStmtNode* decl, ExprNode* init_expr) {
+  AssignExprNode::Ptr assign(new AssignExprNode(decl->id_->copy(), ExprNode::Ptr(init_expr)));
+  decl->init_.push_back(move(assign));
+
+  if (variable_exists(decl, true)) {
+    fprintf(stderr, "redeclaration of variable %s", decl->id_->name_.c_str());
+    assert(0 && "redeclaration of variable");
+    // how to raise a bison error from here?
+    return decl;
+  }
+  decl->scope_id_ = string("v") + std::to_string(scopes_->current_var()->id_) + string("_");
+  scopes_->current_var()->add(decl->id_->name_, decl);
+  return decl;
+}
+
+StructVariableDeclStmtNode* Parser::variable_add(StructVariableDeclStmtNode* decl, ExprNodeList* args, bool is_kv) {
+  if (is_kv) {
+    // annotate the init expressions with the declared id
+    for (auto arg = args->begin(); arg != args->end(); ++arg) {
+      // decorate with the name of this decl
+      auto n = static_cast<AssignExprNode*>(arg->get());
+      n->id_->prepend_dot(decl->id_->name_);
+    }
+  } else {
+    fprintf(stderr, "must use key = value syntax\n");
+    return NULL;
+  }
+
+  decl->init_ = move(*args);
+  delete args;
+
+  if (variable_exists(decl, true)) {
+    fprintf(stderr, "ccpg: warning: redeclaration of variable '%s'\n", decl->id_->name_.c_str());
+    // how to raise a bison error from here?
+    return decl;
+  }
+  decl->scope_id_ = string("v") + std::to_string(scopes_->current_var()->id_) + string("_");
+  scopes_->current_var()->add(decl->id_->name_, decl);
+  return decl;
+}
+
+StmtNode* Parser::timer_add(Scopes::StateScope* scope, BlockStmtNode* body) {
+  if (scopes_->top_timer()->lookup("timer", true)) {
+    fprintf(stderr, "redeclaration of timer. Only one timer supported per plum");
+    assert(0 && "redeclaration of timer. Only one timer supported per plum");
+  }
+  auto timer = new TimerDeclStmtNode(BlockStmtNode::Ptr(body));
+
+  timer->scope_ = scope;
+
+  scopes_->top_timer()->add("timer", timer);
+
+  return timer;
+}
+
+StmtNode* Parser::state_add(Scopes::StateScope* scope, IdentExprNode* id, BlockStmtNode* body) {
+  if (scopes_->current_state()->lookup(id->full_name(), true)) {
+    fprintf(stderr, "redeclaration of state %s\n", id->full_name().c_str());
+    // redeclaration
+    return NULL;
+  }
+  auto state = new StateDeclStmtNode(IdentExprNode::Ptr(id), BlockStmtNode::Ptr(body));
+    // add a reference to the lower scope
+  state->subs_[0].scope_ = scope;
+
+  // add me to the upper scope
+  scopes_->current_state()->add(state->id_->full_name(), state);
+  state->scope_id_ = string("s") + std::to_string(scopes_->current_state()->id_) + string("_");
+
+  return state;
+}
+
+StmtNode* Parser::state_add(Scopes::StateScope* scope, IdentExprNode* id1, IdentExprNode* id2, BlockStmtNode* body) {
+  auto state = scopes_->current_state()->lookup(id1->full_name(), true);
+  if (!state) {
+    state = new StateDeclStmtNode(IdentExprNode::Ptr(id1), IdentExprNode::Ptr(id2), BlockStmtNode::Ptr(body));
+    // add a reference to the lower scope
+    state->subs_[0].scope_ = scope;
+
+    // add me to the upper scope
+    scopes_->current_state()->add(state->id_->full_name(), state);
+    state->scope_id_ = string("s") + std::to_string(scopes_->current_state()->id_) + string("_");
+    return state;
+  } else {
+    if (state->find_sub(id2->name_) != state->subs_.end()) {
+      fprintf(stderr, "redeclaration of state %s, %s\n", id1->full_name().c_str(), id2->full_name().c_str());
+      return NULL;
+    }
+    state->subs_.push_back(StateDeclStmtNode::Sub(IdentExprNode::Ptr(id2), BlockStmtNode::Ptr(body),
+                                                  ParserStateStmtNode::Ptr(), scope));
+    delete id1;
+
+    return new StateDeclStmtNode(); // stub
+  }
+}
+
+bool Parser::table_exists(TableDeclStmtNode* decl, bool search_local) {
+  if (scopes_->top_table()->lookup(decl->id_->name_, search_local) == NULL) {
+    return false;
+  }
+  return true;
+}
+
+StmtNode* Parser::table_add(IdentExprNode* type, IdentExprNodeList* templates,
+                            IdentExprNode* id, string* size) {
+  auto table = new TableDeclStmtNode(IdentExprNode::Ptr(type),
+                                     move(*templates),
+                                     IdentExprNode::Ptr(id), size);
+  if (table_exists(table, true)) {
+    fprintf(stderr, "redeclaration of table %s\n", id->name_.c_str());
+    return table;
+  }
+  scopes_->top_table()->add(id->name_, table);
+  return table;
+}
+
+StmtNode* Parser::struct_add(IdentExprNode* type, FormalList* formals) {
+  auto struct_decl = new StructDeclStmtNode(IdentExprNode::Ptr(type), move(*formals));
+  if (scopes_->top_struct()->lookup(type->name_, true) != NULL) {
+    fprintf(stderr, "redeclaration of struct %s\n", type->name_.c_str());
+    return struct_decl;
+  }
+
+  auto pr_it = pragmas_.find("packed");
+  if (pr_it != pragmas_.end() && pr_it->second == "true")
+    struct_decl->packed_ = true;
+
+  int i = 0;
+  size_t offset = 0;
+  for (auto it = struct_decl->stmts_.begin(); it != struct_decl->stmts_.end(); ++it, ++i) {
+    FieldType ft = bits_to_enum((*it)->bit_width_);
+    offset = struct_decl->is_packed() ? offset : align_offset(offset, ft);
+    (*it)->slot_ = i;
+    (*it)->bit_offset_ = offset;
+    offset += (*it)->bit_width_;
+  }
+  struct_decl->bit_width_ = struct_decl->is_packed() ? offset : align_offset(offset, UINT32_T);
+
+  scopes_->top_struct()->add(type->name_, struct_decl);
+  return struct_decl;
+}
+
+StmtNode* Parser::result_add(int token, IdentExprNode* id, FormalList* formals, BlockStmtNode* body) {
+  // result arguments are pass-by-reference instead of value
+  for (auto it = formals->begin(); it != formals->end(); ++it) {
+    (*it)->storage_type_ = VariableDeclStmtNode::STRUCT_REFERENCE;
+  }
+  StmtNode *stmt = NULL;
+  switch (token) {
+    case Tok::TMATCH:
+      stmt = new MatchDeclStmtNode(IdentExprNode::Ptr(id), move(*formals), BlockStmtNode::Ptr(body));
+      break;
+    case Tok::TMISS:
+      stmt = new MissDeclStmtNode(IdentExprNode::Ptr(id), move(*formals), BlockStmtNode::Ptr(body));
+      break;
+    case Tok::TFAILURE:
+      stmt = new FailureDeclStmtNode(IdentExprNode::Ptr(id), move(*formals), BlockStmtNode::Ptr(body));
+      break;
+    default:
+      {}
+  }
+  return stmt;
+}
+
+void Parser::set_loc(Node *n, const BisonParser::location_type &loc) const {
+  n->line_ = loc.begin.line;
+  n->column_ = loc.begin.column;
+  n->text_ = lexer.text(loc);
+}
+
+string Parser::pragma(const string &name) const {
+  auto it = pragmas_.find(name);
+  if (it == pragmas_.end()) return "";
+  return it->second;
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/parser.h b/jit/src/cc/parser.h
new file mode 100644
index 0000000..c1606f0
--- /dev/null
+++ b/jit/src/cc/parser.h
@@ -0,0 +1,68 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#pragma once
+
+#include <fstream> // NOLINT
+#include "cc/node.h"
+#include "cc/lexer.h"
+#include "cc/scope.h"
+
+namespace ebpf {
+namespace cc {
+
+using std::string;
+using std::pair;
+
+class Parser {
+ public:
+  explicit Parser(const string& infile)
+      : root_node_(NULL), scopes_(new Scopes), in_(infile), lexer(&in_), parser(lexer, *this) {
+    // parser.set_debug_level(1);
+  }
+  ~Parser() { delete root_node_; }
+  int parse() {
+    return parser.parse();
+  }
+
+  VariableDeclStmtNode* variable_add(VariableDeclStmtNode* decl);
+  VariableDeclStmtNode* variable_add(VariableDeclStmtNode* decl, ExprNode* init_expr);
+  StructVariableDeclStmtNode* variable_add(StructVariableDeclStmtNode* decl, ExprNodeList* args, bool is_kv);
+  StmtNode* state_add(Scopes::StateScope* scope, IdentExprNode* id1, BlockStmtNode* body);
+  StmtNode* timer_add(Scopes::StateScope* scope, BlockStmtNode* body);
+  StmtNode* state_add(Scopes::StateScope* scope, IdentExprNode* id1, IdentExprNode* id2, BlockStmtNode* body);
+  StmtNode* table_add(IdentExprNode* type, IdentExprNodeList* templates, IdentExprNode* id, string* size);
+  StmtNode* struct_add(IdentExprNode* type, FormalList* formals);
+  StmtNode* result_add(int token, IdentExprNode* id, FormalList* formals, BlockStmtNode* body);
+  bool variable_exists(VariableDeclStmtNode* decl, bool search_local = true);
+  bool table_exists(TableDeclStmtNode* decl, bool search_local = true);
+  void add_pragma(const std::string& pr, const std::string& v) { pragmas_[pr] = v; }
+  void set_loc(Node *n, const BisonParser::location_type &loc) const;
+  std::string pragma(const std::string &name) const;
+
+  Node* root_node_;
+  Scopes::Ptr scopes_;
+  std::map<std::string, std::string> pragmas_;
+ private:
+  std::ifstream in_;
+  Lexer lexer;
+  BisonParser parser;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/parser.yy b/jit/src/cc/parser.yy
new file mode 100644
index 0000000..5ccafbe
--- /dev/null
+++ b/jit/src/cc/parser.yy
@@ -0,0 +1,589 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+%skeleton "lalr1.cc"
+%defines
+%define api.namespace { ebpf::cc }
+%define parser_class_name { BisonParser }
+%parse-param { ebpf::cc::Lexer &lexer }
+%parse-param { ebpf::cc::Parser &parser }
+%lex-param { ebpf::cc::Lexer &lexer }
+%locations
+
+%code requires {
+    #include <memory>
+    #include <vector>
+    #include <string>
+    #include "cc/node.h"
+    // forward declaration
+    namespace ebpf { namespace cc {
+        class Lexer;
+        class Parser;
+    } }
+}
+
+%code {
+    static int yylex(ebpf::cc::BisonParser::semantic_type *yylval,
+                     ebpf::cc::BisonParser::location_type *yylloc,
+                     ebpf::cc::Lexer &lexer);
+}
+
+%{
+    #include "cc/node.h"
+    #include "cc/parser.h"
+    using std::unique_ptr;
+    using std::vector;
+    using std::string;
+    using std::move;
+%}
+
+%union {
+    Scopes::StateScope *state_scope;
+    Scopes::VarScope *var_scope;
+    BlockStmtNode *block;
+    ExprNode *expr;
+    MethodCallExprNode *call;
+    StmtNode *stmt;
+    IdentExprNode *ident;
+    IntegerExprNode *numeric;
+    BitopExprNode *bitop;
+    ExprNodeList *args;
+    IdentExprNodeList *ident_args;
+    StmtNodeList *stmts;
+    FormalList *formals;
+    VariableDeclStmtNode *decl;
+    StructVariableDeclStmtNode *type_decl;
+    std::string* string;
+    int token;
+    VersionStmtNode *ver;
+}
+
+/* Define the terminal symbols. */
+%token <string> TIDENTIFIER TINTEGER THEXINTEGER TPRAGMA TSTRING
+%token <token> TEQUAL TCEQ TCNE TCLT TCLE TCGT TCGE TAND TOR
+%token <token> TLPAREN TRPAREN TLBRACE TRBRACE TLBRACK TRBRACK
+%token <token> TDOT TCOMMA TPLUS TMINUS TMUL TDIV TMOD TXOR TDOLLAR TCOLON TSCOPE TNOT TSEMI TCMPL TLAND TLOR
+%token <token> TCONST TSTRUCT TVAR TSTATE TTIMER TGOTO TCONTINUE TNEXT TTRUE TFALSE TRETURN
+%token <token> TIF TELSE TSWITCH TCASE
+%token <token> TMATCH TMISS TFAILURE TVALID
+%token <token> TAT
+
+/* Define non-terminal symbols as defined in the above union */
+%type <ident> ident scoped_ident dotted_ident any_ident
+%type <expr> expr assign_expr return_expr init_arg_kv
+%type <numeric> numeric
+%type <bitop> bitop
+%type <args> call_args init_args init_args_kv
+%type <ident_args> table_decl_args
+%type <formals> struct_decl_stmts formals
+%type <block> program block prog_decls
+%type <decl> decl_stmt int_decl ref_stmt
+%type <type_decl> type_decl ptr_decl
+%type <stmt> stmt prog_decl var_decl struct_decl state_decl timer_decl
+%type <stmt> table_decl table_result_stmt if_stmt switch_stmt case_stmt onvalid_stmt
+%type <var_scope> enter_varscope exit_varscope
+%type <state_scope> enter_statescope exit_statescope
+%type <stmts> stmts table_result_stmts case_stmts
+%type <call> call_expr
+%type <stmt> pragma_decl
+%type <ver>  version_stmt
+
+/* taken from C++ operator precedence wiki page */
+%nonassoc TSCOPE
+%left TDOT TLBRACK TLBRACE TLPAREN
+%right TNOT TCMPL
+%left TMUL
+%left TDIV
+%left TMOD
+%left TPLUS
+%left TMINUS
+%left TCLT TCLE TCGT TCGE
+%left TCEQ
+%left TCNE
+%left TXOR
+%left TAND
+%left TOR
+%left TLAND
+%left TLOR
+%right TEQUAL
+
+%start program
+
+%%
+
+program
+  : enter_statescope enter_varscope version_stmt prog_decls exit_varscope exit_statescope
+    { parser.root_node_ = $4; $4->scope_ = $2; $4->ver_ = (*$3); delete $3;}
+  ;
+
+version_stmt
+  :     TAT TINTEGER TDOT TINTEGER TDOT TINTEGER TSEMI
+{
+  $$ = new VersionStmtNode(atoi($2->c_str()), atoi($4->c_str()), atoi($6->c_str()));
+}
+;
+
+/* program is a list of declarations */
+prog_decls
+  : prog_decl
+    { $$ = new BlockStmtNode; $$->stmts_.push_back(StmtNode::Ptr($1)); }
+  | prog_decls prog_decl
+    { $1->stmts_.push_back(StmtNode::Ptr($2)); }
+  ;
+
+/*
+ possible program declarations are:
+  "struct {}"
+  "state|on_miss|on_match|on_valid {}"
+  "var <var_decl>"
+  "Table <...> <ident>(size)"
+ */
+prog_decl
+  : var_decl TSEMI
+  | struct_decl TSEMI
+  | state_decl TSEMI
+  | timer_decl TSEMI
+  | table_decl TSEMI
+  | pragma_decl TSEMI
+  ;
+
+pragma_decl
+  : TPRAGMA TIDENTIFIER TIDENTIFIER
+    { $$ = new BlockStmtNode; parser.add_pragma(*$2, *$3); delete $2; delete $3; }
+  | TPRAGMA TIDENTIFIER TSTRING
+    { $$ = new BlockStmtNode; parser.add_pragma(*$2, *$3); delete $2; delete $3; }
+  ;
+
+stmts
+  : stmt
+    { $$ = new StmtNodeList; $$->push_back(StmtNode::Ptr($1)); }
+  | stmts stmt
+    { $1->push_back(StmtNode::Ptr($2)); }
+  ;
+
+stmt
+  : expr TSEMI
+    { $$ = new ExprStmtNode(ExprNode::Ptr($1));
+      parser.set_loc($$, @$); }
+  | assign_expr TSEMI
+    { $$ = new ExprStmtNode(ExprNode::Ptr($1));
+      parser.set_loc($$, @$); }
+  | return_expr TSEMI
+    { $$ = new ExprStmtNode(ExprNode::Ptr($1));
+      parser.set_loc($$, @$); }
+  | call_expr TLBRACE enter_varscope table_result_stmts exit_varscope TRBRACE TSEMI
+    { $$ = new ExprStmtNode(ExprNode::Ptr($1));
+      $1->block_->stmts_ = move(*$4); delete $4;
+      $1->block_->scope_ = $3;
+      parser.set_loc($$, @$); }
+  | call_expr TLBRACE TRBRACE TSEMI  // support empty curly braces
+    { $$ = new ExprStmtNode(ExprNode::Ptr($1));
+      parser.set_loc($$, @$); }
+  | if_stmt TSEMI
+  | switch_stmt TSEMI
+  | var_decl TSEMI
+    { $$ = $1; }
+  | state_decl TSEMI
+  | onvalid_stmt TSEMI
+  ;
+
+call_expr
+  : any_ident TLPAREN call_args TRPAREN
+    { $$ = new MethodCallExprNode(IdentExprNode::Ptr($1), move(*$3), lexer.lineno()); delete $3;
+      parser.set_loc($$, @$); }
+  ;
+
+block
+  : TLBRACE stmts TRBRACE
+    { $$ = new BlockStmtNode; $$->stmts_ = move(*$2); delete $2;
+      parser.set_loc($$, @$); }
+  | TLBRACE TRBRACE
+    { $$ = new BlockStmtNode;
+      parser.set_loc($$, @$); }
+  ;
+
+enter_varscope : /* empty */ { $$ = parser.scopes_->enter_var_scope(); } ;
+exit_varscope : /* emtpy */ { $$ = parser.scopes_->exit_var_scope(); } ;
+enter_statescope : /* empty */ { $$ = parser.scopes_->enter_state_scope(); } ;
+exit_statescope : /* emtpy */ { $$ = parser.scopes_->exit_state_scope(); } ;
+
+struct_decl
+  : TSTRUCT ident TLBRACE struct_decl_stmts TRBRACE
+    { $$ = parser.struct_add($2, $4); delete $4;
+      parser.set_loc($$, @$); }
+  ;
+
+struct_decl_stmts
+  : decl_stmt TSEMI
+    { $$ = new FormalList; $$->push_back(VariableDeclStmtNode::Ptr($1)); }
+  | struct_decl_stmts decl_stmt TSEMI
+    { $1->push_back(VariableDeclStmtNode::Ptr($2)); }
+  ;
+
+table_decl
+  : ident TCLT table_decl_args TCGT ident TLPAREN TINTEGER TRPAREN
+    { $$ = parser.table_add($1, $3, $5, $7); delete $3;
+      parser.set_loc($$, @$); }
+  ;
+
+table_decl_args
+  : ident
+    { $$ = new IdentExprNodeList; $$->push_back(IdentExprNode::Ptr($1)); }
+  | table_decl_args TCOMMA ident
+    { $$->push_back(IdentExprNode::Ptr($3)); }
+  ;
+
+state_decl
+  : TSTATE scoped_ident enter_statescope enter_varscope block exit_varscope exit_statescope
+    { $$ = parser.state_add($3, $2, $5); $5->scope_ = $4;
+      parser.set_loc($$, @$); }
+  | TSTATE scoped_ident TCOMMA TMUL enter_statescope enter_varscope block exit_varscope exit_statescope
+    { $$ = parser.state_add($5, $2, new IdentExprNode(""), $7); $7->scope_ = $6;
+      parser.set_loc($$, @$); }
+  | TSTATE scoped_ident TCOMMA scoped_ident enter_statescope enter_varscope block exit_varscope exit_statescope
+    { $$ = parser.state_add($5, $2, $4, $7); $7->scope_ = $6;
+      parser.set_loc($$, @$); }
+  ;
+
+timer_decl
+  : TTIMER enter_statescope enter_varscope block exit_varscope exit_statescope
+    { $$ = parser.timer_add($2, $4); $4->scope_ = $3;
+      parser.set_loc($$, @$); }
+  ;
+
+table_result_stmts
+  : table_result_stmt
+    { $$ = new StmtNodeList; $$->push_back(StmtNode::Ptr($1)); }
+  | table_result_stmts table_result_stmt
+    { $$->push_back(StmtNode::Ptr($2)); }
+  ;
+
+table_result_stmt
+  : TMATCH ident enter_varscope TLPAREN formals TRPAREN block exit_varscope TSEMI
+    { $$ = parser.result_add($1, $2, $5, $7); delete $5; $7->scope_ = $3;
+      parser.set_loc($$, @$); }
+  | TMISS ident enter_varscope TLPAREN TRPAREN block exit_varscope TSEMI
+    { $$ = parser.result_add($1, $2, new FormalList, $6); $6->scope_ = $3;
+      parser.set_loc($$, @$); }
+  | TFAILURE ident enter_varscope TLPAREN formals TRPAREN block exit_varscope TSEMI
+    { $$ = parser.result_add($1, $2, $5, $7); delete $5; $7->scope_ = $3;
+      parser.set_loc($$, @$); }
+  ;
+
+formals
+  : TVAR ptr_decl
+    { $$ = new FormalList; $$->push_back(VariableDeclStmtNode::Ptr(parser.variable_add($2))); }
+  | formals TCOMMA TVAR ptr_decl
+    { $1->push_back(VariableDeclStmtNode::Ptr(parser.variable_add($4))); }
+  ;
+
+var_decl
+  : TVAR decl_stmt
+    { $$ = parser.variable_add($2);
+      parser.set_loc($$, @$); }
+  | TVAR int_decl TEQUAL expr
+    { $$ = parser.variable_add($2, $4);
+      parser.set_loc($$, @$); }
+  | TVAR type_decl TLBRACE init_args_kv TRBRACE
+    { $$ = parser.variable_add($2, $4, true);
+      parser.set_loc($$, @$); }
+  /*| TVAR type_decl TLBRACE init_args TRBRACE
+    { $$ = parser.variable_add($2, $4, false);
+      parser.set_loc($$, @$); }*/
+  | TVAR ref_stmt
+    { $$ = parser.variable_add($2);
+      parser.set_loc($$, @$); }
+  ;
+
+/* "id":"bitsize" or "type" "id" */
+decl_stmt : int_decl { $$ = $1; } | type_decl { $$ = $1; };
+int_decl : ident TCOLON TINTEGER
+    { $$ = new IntegerVariableDeclStmtNode(IdentExprNode::Ptr($1), *$3); delete $3;
+      parser.set_loc($$, @$); }
+  ;
+
+type_decl : scoped_ident ident
+    { $$ = new StructVariableDeclStmtNode(IdentExprNode::Ptr($1), IdentExprNode::Ptr($2));
+      parser.set_loc($$, @$); }
+  ;
+
+/* "type" "*" "id" */
+ref_stmt : ptr_decl { $$ = $1; };
+ptr_decl : scoped_ident TMUL ident
+    { $$ = new StructVariableDeclStmtNode(IdentExprNode::Ptr($1), IdentExprNode::Ptr($3),
+                                          VariableDeclStmtNode::STRUCT_REFERENCE);
+      parser.set_loc($$, @$); }
+  ;
+
+/* normal initializer */
+init_args
+  : expr { $$ = new ExprNodeList; $$->push_back(ExprNode::Ptr($1)); }
+  | init_args TCOMMA expr { $$->push_back(ExprNode::Ptr($3)); }
+  ;
+
+/* one or more of "field" = "expr" */
+init_args_kv
+  : init_arg_kv { $$ = new ExprNodeList; $$->push_back(ExprNode::Ptr($1)); }
+  | init_args_kv TCOMMA init_arg_kv { $$->push_back(ExprNode::Ptr($3)); }
+  ;
+init_arg_kv
+  : ident TEQUAL expr
+    { $$ = new AssignExprNode(IdentExprNode::Ptr($1), ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | ident bitop TEQUAL expr
+    { $$ = new AssignExprNode(IdentExprNode::Ptr($1), ExprNode::Ptr($4)); $$->bitop_ = BitopExprNode::Ptr($2);
+      parser.set_loc($$, @$); }
+  ;
+
+if_stmt
+  : TIF expr enter_varscope block exit_varscope
+    { $$ = new IfStmtNode(ExprNode::Ptr($2), StmtNode::Ptr($4));
+      $4->scope_ = $3;
+        parser.set_loc($$, @$); }
+  | TIF expr enter_varscope block exit_varscope TELSE enter_varscope block exit_varscope
+    { $$ = new IfStmtNode(ExprNode::Ptr($2), StmtNode::Ptr($4), StmtNode::Ptr($8));
+      $4->scope_ = $3; $8->scope_ = $7;
+      parser.set_loc($$, @$); }
+  | TIF expr enter_varscope block exit_varscope TELSE if_stmt
+    { $$ = new IfStmtNode(ExprNode::Ptr($2), StmtNode::Ptr($4), StmtNode::Ptr($7));
+      $4->scope_ = $3;
+      parser.set_loc($$, @$); }
+  ;
+
+onvalid_stmt
+  : TVALID TLPAREN ident TRPAREN enter_varscope block exit_varscope
+    { $$ = new OnValidStmtNode(IdentExprNode::Ptr($3), StmtNode::Ptr($6));
+      $6->scope_ = $5;
+      parser.set_loc($$, @$); }
+  | TVALID TLPAREN ident TRPAREN enter_varscope block exit_varscope TELSE enter_varscope block exit_varscope
+    { $$ = new OnValidStmtNode(IdentExprNode::Ptr($3), StmtNode::Ptr($6), StmtNode::Ptr($10));
+      $6->scope_ = $5; $10->scope_ = $9;
+      parser.set_loc($$, @$); }
+  ;
+
+switch_stmt
+  : TSWITCH expr TLBRACE case_stmts TRBRACE
+    { $$ = new SwitchStmtNode(ExprNode::Ptr($2), make_unique<BlockStmtNode>(move(*$4))); delete $4;
+      parser.set_loc($$, @$); }
+  ;
+
+case_stmts
+  : case_stmt
+    { $$ = new StmtNodeList; $$->push_back(StmtNode::Ptr($1)); }
+  | case_stmts case_stmt
+    { $$->push_back(StmtNode::Ptr($2)); }
+  ;
+
+case_stmt
+  : TCASE numeric block TSEMI
+    { $$ = new CaseStmtNode(IntegerExprNode::Ptr($2), BlockStmtNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | TCASE TMUL block TSEMI
+    { $$ = new CaseStmtNode(BlockStmtNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  ;
+
+numeric
+  : TINTEGER
+    { $$ = new IntegerExprNode($1);
+      parser.set_loc($$, @$); }
+  | THEXINTEGER
+    { $$ = new IntegerExprNode($1);
+      parser.set_loc($$, @$); }
+  | TINTEGER TCOLON TINTEGER
+    { $$ = new IntegerExprNode($1, $3);
+      parser.set_loc($$, @$); }
+  | THEXINTEGER TCOLON TINTEGER
+    { $$ = new IntegerExprNode($1, $3);
+      parser.set_loc($$, @$); }
+  | TTRUE
+    { $$ = new IntegerExprNode(new string("1"), new string("1"));
+      parser.set_loc($$, @$); }
+  | TFALSE
+    { $$ = new IntegerExprNode(new string("0"), new string("1"));
+      parser.set_loc($$, @$); }
+  ;
+
+assign_expr
+  : dotted_ident TEQUAL expr
+    { $$ = new AssignExprNode(IdentExprNode::Ptr($1), ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | dotted_ident bitop TEQUAL expr
+    { $$ = new AssignExprNode(IdentExprNode::Ptr($1), ExprNode::Ptr($4)); $$->bitop_ = BitopExprNode::Ptr($2);
+      parser.set_loc($$, @$); }
+  ;
+
+return_expr
+  : TRETURN expr
+    { $$ = new ReturnExprNode(ExprNode::Ptr($2));
+      parser.set_loc($$, @$); }
+  ;
+
+expr
+  : call_expr
+    { $$ = $1; }
+  | call_expr bitop
+    { $$ = $1; $$->bitop_ = BitopExprNode::Ptr($2); }
+  | any_ident
+    { $$ = $1; }
+  | TAT dotted_ident
+    { $$ = new PacketExprNode(IdentExprNode::Ptr($2));
+      $$->flags_[ExprNode::IS_REF] = true;
+      parser.set_loc($$, @$); }
+  | TDOLLAR dotted_ident
+    { $$ = new PacketExprNode(IdentExprNode::Ptr($2));
+      parser.set_loc($$, @$); }
+  | TDOLLAR dotted_ident bitop
+    { $$ = new PacketExprNode(IdentExprNode::Ptr($2)); $$->bitop_ = BitopExprNode::Ptr($3);
+      parser.set_loc($$, @$); }
+  | TGOTO scoped_ident
+    { $$ = new GotoExprNode(IdentExprNode::Ptr($2), false);
+      parser.set_loc($$, @$); }
+  | TNEXT scoped_ident
+    { $$ = new GotoExprNode(IdentExprNode::Ptr($2), false);
+      parser.set_loc($$, @$); }
+  | TCONTINUE scoped_ident
+    { $$ = new GotoExprNode(IdentExprNode::Ptr($2), true);
+      parser.set_loc($$, @$); }
+  | TLPAREN expr TRPAREN
+    { $$ = $2; }
+  | TLPAREN expr TRPAREN bitop
+    { $$ = $2; $$->bitop_ = BitopExprNode::Ptr($4); }
+  | numeric
+    { $$ = $1; }
+  | numeric bitop
+    { $$ = $1; $$->bitop_ = BitopExprNode::Ptr($2); }
+  | expr TCLT expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TCGT expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TCGE expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TCLE expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TCNE expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TCEQ expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TPLUS expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TMINUS expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TMUL expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TDIV expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TMOD expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TXOR expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TAND expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TOR expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TLAND expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  | expr TLOR expr
+    { $$ = new BinopExprNode(ExprNode::Ptr($1), $2, ExprNode::Ptr($3));
+      parser.set_loc($$, @$); }
+  /*| expr bitop
+    { $$ = $1; $$->bitop_ = BitopExprNode::Ptr($2); }*/
+  | TNOT expr
+    { $$ = new UnopExprNode($1, ExprNode::Ptr($2));
+      parser.set_loc($$, @$); }
+  | TCMPL expr
+    { $$ = new UnopExprNode($1, ExprNode::Ptr($2));
+      parser.set_loc($$, @$); }
+  ;
+
+call_args
+  : /* empty */
+    { $$ = new ExprNodeList; }
+  | expr
+    { $$ = new ExprNodeList; $$->push_back(ExprNode::Ptr($1)); }
+  | call_args TCOMMA expr
+    { $$->push_back(ExprNode::Ptr($3)); }
+  ;
+
+bitop
+  : TLBRACK TCOLON TPLUS TINTEGER TRBRACK
+    { $$ = new BitopExprNode(string("0"), *$4); delete $4;
+      parser.set_loc($$, @$); }
+  | TLBRACK TINTEGER TCOLON TPLUS TINTEGER TRBRACK
+    { $$ = new BitopExprNode(*$2, *$5); delete $2; delete $5;
+      parser.set_loc($$, @$); }
+  ;
+
+scoped_ident
+  : ident
+    { $$ = $1; }
+  | scoped_ident TSCOPE TIDENTIFIER
+    { $$->append_scope(*$3); delete $3; }
+  ;
+
+dotted_ident
+  : ident
+    { $$ = $1; }
+  | dotted_ident TDOT TIDENTIFIER
+    { $$->append_dot(*$3); delete $3; }
+  ;
+
+any_ident
+  : ident
+    { $$ = $1; }
+  | dotted_ident TDOT TIDENTIFIER
+    { $$->append_dot(*$3); delete $3; }
+  | scoped_ident TSCOPE TIDENTIFIER
+    { $$->append_scope(*$3); delete $3; }
+  ;
+
+ident
+  : TIDENTIFIER
+    { $$ = new IdentExprNode(*$1); delete $1;
+      parser.set_loc($$, @$); }
+  ;
+
+%%
+
+void ebpf::cc::BisonParser::error(const ebpf::cc::BisonParser::location_type &loc,
+                            const string& msg) {
+    std::cerr << "Error: " << loc << " " << msg << std::endl;
+}
+
+#include "cc/lexer.h"
+static int yylex(ebpf::cc::BisonParser::semantic_type *yylval,
+                 ebpf::cc::BisonParser::location_type *yylloc,
+                 ebpf::cc::Lexer &lexer) {
+    return lexer.yylex(yylval, yylloc);
+}
+
diff --git a/jit/src/cc/printer.cc b/jit/src/cc/printer.cc
new file mode 100644
index 0000000..d9d4df5
--- /dev/null
+++ b/jit/src/cc/printer.cc
@@ -0,0 +1,328 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#include "cc/printer.h"
+#include "cc/lexer.h"
+#include "exception.h"
+
+namespace ebpf {
+namespace cc {
+
+void Printer::print_indent() {
+  fprintf(out_, "%*s", indent_, "");
+}
+
+StatusTuple Printer::visit_block_stmt_node(BlockStmtNode* n) {
+  fprintf(out_, "{\n");
+
+  TRY2(n->ver_.accept(this));
+  if (!n->stmts_.empty()) {
+    ++indent_;
+    for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+      print_indent();
+      TRY2((*it)->accept(this));
+      fprintf(out_, "\n");
+    }
+    --indent_;
+  }
+  fprintf(out_, "%*s}", indent_, "");
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_version_stmt_node(VersionStmtNode* n) {
+  uint32_t version;
+  version = MAKE_VERSION(n->major_, n->minor_, n->rev_);
+  fprintf(out_, "static const uint32_t  plumlet_version   __attribute__"
+      "((section (\".version\"), used)) = 0x%x;\n", version);
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_if_stmt_node(IfStmtNode* n) {
+  fprintf(out_, "if ");
+  TRY2(n->cond_->accept(this));
+  fprintf(out_, " ");
+  TRY2(n->true_block_->accept(this));
+  if (n->false_block_) {
+    fprintf(out_, " else ");
+    TRY2(n->false_block_->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_onvalid_stmt_node(OnValidStmtNode* n) {
+  fprintf(out_, "if ");
+  TRY2(n->cond_->accept(this));
+  fprintf(out_, " ");
+  TRY2(n->block_->accept(this));
+  if (n->else_block_) {
+    fprintf(out_, " else ");
+    TRY2(n->else_block_->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_switch_stmt_node(SwitchStmtNode* n) {
+  fprintf(out_, "switch (");
+  TRY2(n->cond_->accept(this));
+  fprintf(out_, ") ");
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_case_stmt_node(CaseStmtNode* n) {
+  if (n->value_) {
+    fprintf(out_, "case ");
+    TRY2(n->value_->accept(this));
+  } else {
+    fprintf(out_, "default");
+  }
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_ident_expr_node(IdentExprNode* n) {
+  if (n->scope_name_.size()) {
+    fprintf(out_, "%s::", n->scope_name_.c_str());
+  }
+  fprintf(out_, "%s", n->name_.c_str());
+  if (n->sub_name_.size()) {
+    fprintf(out_, ".%s", n->sub_name_.c_str());
+  }
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_assign_expr_node(AssignExprNode* n) {
+  TRY2(n->id_->accept(this));
+  fprintf(out_, " = ");
+  TRY2(n->rhs_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_packet_expr_node(PacketExprNode* n) {
+  fprintf(out_, "$");
+  TRY2(n->id_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_integer_expr_node(IntegerExprNode* n) {
+  fprintf(out_, "%s:%zu", n->val_.c_str(), n->bits_);
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_binop_expr_node(BinopExprNode* n) {
+  TRY2(n->lhs_->accept(this));
+  fprintf(out_, "%d", n->op_);
+  TRY2(n->rhs_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_unop_expr_node(UnopExprNode* n) {
+  const char* s = "";
+  switch (n->op_) {
+    case Tok::TNOT: s = "!"; break;
+    case Tok::TCMPL: s = "~"; break;
+    case Tok::TMOD:  s = "%"; break;
+    default: {}
+  }
+  fprintf(out_, "%s", s);
+  TRY2(n->expr_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_bitop_expr_node(BitopExprNode* n) {
+
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_return_expr_node(ReturnExprNode* n) {
+  fprintf(out_, "return ");
+  TRY2(n->expr_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_goto_expr_node(GotoExprNode* n) {
+  const char* s = n->is_continue_ ? "continue " : "goto ";
+  fprintf(out_, "%s", s);
+  TRY2(n->id_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_method_call_expr_node(MethodCallExprNode* n) {
+  TRY2(n->id_->accept(this));
+  fprintf(out_, "(");
+  for (auto it = n->args_.begin(); it != n->args_.end(); ++it) {
+    TRY2((*it)->accept(this));
+    if (it + 1 != n->args_.end()) {
+      fprintf(out_, ", ");
+    }
+  }
+  fprintf(out_, ")");
+  if (!n->block_->stmts_.empty()) {
+    fprintf(out_, " {\n");
+    ++indent_;
+    for (auto it = n->block_->stmts_.begin(); it != n->block_->stmts_.end(); ++it) {
+      print_indent();
+      TRY2((*it)->accept(this));
+      fprintf(out_, "\n");
+    }
+    --indent_;
+    fprintf(out_, "%*s}", indent_, "");
+  }
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_expr_stmt_node(ExprStmtNode* n) {
+  TRY2(n->expr_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_struct_variable_decl_stmt_node(StructVariableDeclStmtNode* n) {
+  fprintf(out_, "var ");
+  TRY2(n->struct_id_->accept(this));
+  fprintf(out_, " ");
+  TRY2(n->id_->accept(this));
+  if (!n->init_.empty()) {
+    fprintf(out_, "{");
+    for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+      TRY2((*it)->accept(this));
+      if (it + 1 != n->init_.end()) {
+        fprintf(out_, ", ");
+      }
+    }
+    fprintf(out_, "}");
+  }
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_integer_variable_decl_stmt_node(IntegerVariableDeclStmtNode* n) {
+  fprintf(out_, "var ");
+  TRY2(n->id_->accept(this));
+  fprintf(out_, ":%zu", n->bit_width_);
+  if (!n->init_.empty()) {
+    fprintf(out_, "; ");
+    TRY2(n->init_[0]->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_struct_decl_stmt_node(StructDeclStmtNode* n) {
+  fprintf(out_, "struct ");
+  TRY2(n->id_->accept(this));
+  fprintf(out_, " {\n");
+  ++indent_;
+  for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+    print_indent();
+    TRY2((*it)->accept(this));
+    fprintf(out_, "\n");
+  }
+  --indent_;
+  fprintf(out_, "%*s}", indent_, "");
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_timer_decl_stmt_node(TimerDeclStmtNode* n) {
+  if (!n->id_) {
+    return mkstatus(0);
+  }
+  fprintf(out_, "timer ");
+  TRY2(n->id_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_state_decl_stmt_node(StateDeclStmtNode* n) {
+  if (!n->id_) {
+    return mkstatus(0);
+  }
+  fprintf(out_, "state ");
+  TRY2(n->id_->accept(this));
+  //if (!n->id2_) {
+  //  fprintf(out_, ", * ");
+  //} else {
+  //  fprintf(out_, ", ");
+  //  TRY2(n->id2_->accept(this));
+  //}
+  //TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_parser_state_stmt_node(ParserStateStmtNode* n) {
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_match_decl_stmt_node(MatchDeclStmtNode* n) {
+  fprintf(out_, "on_match ");
+  TRY2(n->id_->accept(this));
+  fprintf(out_, " (");
+  for (auto it = n->formals_.begin(); it != n->formals_.end(); ++it) {
+    TRY2((*it)->accept(this));
+    if (it + 1 != n->formals_.end()) {
+      fprintf(out_, ", ");
+    }
+  }
+  fprintf(out_, ") ");
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_miss_decl_stmt_node(MissDeclStmtNode* n) {
+  fprintf(out_, "on_miss ");
+  TRY2(n->id_->accept(this));
+  fprintf(out_, " (");
+  for (auto it = n->formals_.begin(); it != n->formals_.end(); ++it) {
+    TRY2((*it)->accept(this));
+    if (it + 1 != n->formals_.end()) {
+      fprintf(out_, ", ");
+    }
+  }
+  fprintf(out_, ") ");
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_failure_decl_stmt_node(FailureDeclStmtNode* n) {
+  fprintf(out_, "on_failure ");
+  TRY2(n->id_->accept(this));
+  fprintf(out_, " (");
+  for (auto it = n->formals_.begin(); it != n->formals_.end(); ++it) {
+    TRY2((*it)->accept(this));
+    if (it + 1 != n->formals_.end()) {
+      fprintf(out_, ", ");
+    }
+  }
+  fprintf(out_, ") ");
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple Printer::visit_table_decl_stmt_node(TableDeclStmtNode* n) {
+  TRY2(n->table_type_->accept(this));
+  fprintf(out_, "<");
+  for (auto it = n->templates_.begin(); it != n->templates_.end(); ++it) {
+    TRY2((*it)->accept(this));
+    if (it + 1 != n->templates_.end()) {
+      fprintf(out_, ", ");
+    }
+  }
+  fprintf(out_, "> ");
+  TRY2(n->id_->accept(this));
+  fprintf(out_, "(%zu)", n->size_);
+  return mkstatus(0);
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/printer.h b/jit/src/cc/printer.h
new file mode 100644
index 0000000..e457dd4
--- /dev/null
+++ b/jit/src/cc/printer.h
@@ -0,0 +1,44 @@
+/*
+ * ====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * ====================================================================
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include "cc/node.h"
+
+namespace ebpf {
+namespace cc {
+
+class Printer : public Visitor {
+ public:
+  explicit Printer(FILE* out) : out_(out), indent_(0) {}
+
+  void print_indent();
+
+#define VISIT(type, func) virtual STATUS_RETURN visit_##func(type* n);
+  EXPAND_NODES(VISIT)
+#undef VISIT
+
+ private:
+  FILE* out_;
+  int indent_;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/scope.h b/jit/src/cc/scope.h
new file mode 100644
index 0000000..9a68f07
--- /dev/null
+++ b/jit/src/cc/scope.h
@@ -0,0 +1,158 @@
+/*
+ * ====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * ====================================================================
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace ebpf {
+namespace cc {
+
+using std::string;
+using std::vector;
+using std::map;
+using std::pair;
+using std::unique_ptr;
+
+class StateDeclStmtNode;
+class TimerDeclStmtNode;
+class VariableDeclStmtNode;
+class TableDeclStmtNode;
+class StructDeclStmtNode;
+
+template <typename T>
+class Scope {
+ public:
+  Scope() {}
+  Scope(Scope<T>* scope, int id) : parent_(scope), id_(id) {}
+  enum search_type { LOCAL, GLOBAL };
+
+  T* lookup(const string& name, bool search_local = true) {
+    auto it = elems_.find(name);
+    if (it != elems_.end()) {
+      return it->second;
+    }
+
+    if (search_local || !parent_) {
+      return NULL;
+    }
+    return parent_->lookup(name, search_local);
+  }
+  void add(const string& name, T* n) {
+    elems_[name] = n;
+    elems_ordered_.push_back(n);
+  }
+  typename map<string, T*>::iterator begin() { return elems_.begin(); }
+  typename map<string, T*>::iterator end() { return elems_.end(); }
+  typename vector<T*>::iterator obegin() { return elems_ordered_.begin(); }
+  typename vector<T*>::iterator oend() { return elems_ordered_.end(); }
+
+  Scope<T> *parent_;
+  int id_;
+  map<string, T*> elems_;
+  vector<T*> elems_ordered_;
+};
+
+/**
+ * Hold the current stack of scope pointers.  Lookups search upwards.
+ * Actual scope pointers are kept in the AST.
+ */
+class Scopes {
+ public:
+  typedef unique_ptr<Scopes> Ptr;
+  typedef Scope<StructDeclStmtNode> StructScope;
+  typedef Scope<StateDeclStmtNode> StateScope;
+  typedef Scope<TimerDeclStmtNode> TimerScope;
+  typedef Scope<VariableDeclStmtNode> VarScope;
+  typedef Scope<TableDeclStmtNode> TableScope;
+
+  Scopes() : var_id__(0), state_id_(0), var_id_(0),
+    current_var_scope_(NULL), top_var_scope_(NULL),
+    current_state_scope_(NULL), top_state_scope_(NULL),
+    top_timer_scope_(new TimerScope(NULL, 1)),
+    top_struct_scope_(new StructScope(NULL, 1)),
+    top_table_scope_(new TableScope(NULL, 1)) {}
+  ~Scopes() {
+    delete top_timer_scope_;
+    delete top_struct_scope_;
+    delete top_table_scope_;
+    delete top_state_scope_;
+  }
+
+  /// While building the AST, allocate a new scope
+  VarScope* enter_var_scope() {
+    current_var_scope_ = new VarScope(current_var_scope_, next_var_id());
+    if (!top_var_scope_) {
+      top_var_scope_ = current_var_scope_;
+    }
+    return current_var_scope_;
+  }
+
+  VarScope* exit_var_scope() {
+    current_var_scope_ = current_var_scope_->parent_;
+    return current_var_scope_;
+  }
+
+  StateScope* enter_state_scope() {
+    current_state_scope_ = new StateScope(current_state_scope_, next_state_id());
+    if (!top_state_scope_) {
+      top_state_scope_ = current_state_scope_;
+    }
+    return current_state_scope_;
+  }
+
+  StateScope* exit_state_scope() {
+    current_state_scope_ = current_state_scope_->parent_;
+    return current_state_scope_;
+  }
+
+  void set_current(VarScope* s) { current_var_scope_ = s; }
+  VarScope* current_var() { return current_var_scope_; }
+  VarScope* top_var() { return top_var_scope_; }
+
+  void set_current(StateScope* s) { current_state_scope_ = s; }
+  StateScope* current_state() { return current_state_scope_; }
+  StateScope* top_state() { return top_state_scope_; }
+
+  TimerScope* top_timer() { return top_timer_scope_; }
+
+  StructScope* top_struct() { return top_struct_scope_; }
+
+  TableScope* top_table() { return top_table_scope_; }
+
+  int next_id() { return ++var_id__; }
+  int next_state_id() { return ++state_id_; }
+  int next_var_id() { return ++var_id_; }
+
+  int var_id__;
+  int state_id_;
+  int var_id_;
+  VarScope* current_var_scope_;
+  VarScope* top_var_scope_;
+  StateScope* current_state_scope_;
+  StateScope* top_state_scope_;
+  TimerScope* top_timer_scope_;
+  StructScope* top_struct_scope_;
+  TableScope* top_table_scope_;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/type_check.cc b/jit/src/cc/type_check.cc
new file mode 100644
index 0000000..00864e6
--- /dev/null
+++ b/jit/src/cc/type_check.cc
@@ -0,0 +1,642 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#include <set>
+#include <algorithm>
+#include "exception.h"
+#include "cc/type_check.h"
+#include "cc/lexer.h"
+
+namespace ebpf {
+namespace cc {
+
+using std::for_each;
+using std::set;
+
+StatusTuple TypeCheck::visit_block_stmt_node(BlockStmtNode* n) {
+  // enter scope
+  auto scope = scopes_->current_var();
+  if (n->scope_) {
+    scopes_->set_current(n->scope_);
+  }
+  n->ver_.accept(this);
+  if (!n->stmts_.empty()) {
+    for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+      //try {
+        TRY2((*it)->accept(this));
+      //} catch (CompilerException& e) {
+      //  errors_.push_back(e.what());
+      //}
+    }
+  }
+
+  // exit scope
+  scopes_->set_current(scope);
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_version_stmt_node(VersionStmtNode* n) {
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_if_stmt_node(IfStmtNode* n) {
+  TRY2(n->cond_->accept(this));
+  if (n->cond_->typeof_ != ExprNode::INTEGER)
+    return mkstatus_(n, "If condition must be a numeric type");
+  TRY2(n->true_block_->accept(this));
+  if (n->false_block_) {
+    TRY2(n->false_block_->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_onvalid_stmt_node(OnValidStmtNode* n) {
+  TRY2(n->cond_->accept(this));
+  auto sdecl = static_cast<StructVariableDeclStmtNode*>(n->cond_->decl_);
+  if (sdecl->storage_type_ != StructVariableDeclStmtNode::STRUCT_REFERENCE)
+    return mkstatus_(n, "on_valid condition must be a reference type");
+  TRY2(n->block_->accept(this));
+  if (n->else_block_) {
+    TRY2(n->else_block_->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_switch_stmt_node(SwitchStmtNode* n) {
+  TRY2(n->cond_->accept(this));
+  if (n->cond_->typeof_ != ExprNode::INTEGER)
+    return mkstatus_(n, "Switch condition must be a numeric type");
+  TRY2(n->block_->accept(this));
+  for (auto it = n->block_->stmts_.begin(); it != n->block_->stmts_.end(); ++it) {
+    /// @todo check for duplicates
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_case_stmt_node(CaseStmtNode* n) {
+  if (n->value_) {
+    TRY2(n->value_->accept(this));
+    if (n->value_->typeof_ != ExprNode::INTEGER)
+      return mkstatus_(n, "Switch condition must be a numeric type");
+  }
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_ident_expr_node(IdentExprNode* n) {
+  n->decl_ = scopes_->current_var()->lookup(n->name_, false);
+  if (!n->decl_)
+    return mkstatus_(n, "Variable %s lookup failed", n->c_str());
+
+  n->typeof_ = ExprNode::UNKNOWN;
+  if (n->sub_name_.empty()) {
+    if (n->decl_->storage_type_ == VariableDeclStmtNode::INTEGER) {
+      n->typeof_ = ExprNode::INTEGER;
+      n->bit_width_ = n->decl_->bit_width_;
+      n->flags_[ExprNode::WRITE] = true;
+    } else if (n->decl_->is_struct()) {
+      n->typeof_ = ExprNode::STRUCT;
+      auto sdecl = static_cast<StructVariableDeclStmtNode*>(n->decl_);
+      if (sdecl->struct_id_->scope_name_ == "proto") {
+        n->struct_type_ = proto_scopes_->top_struct()->lookup(sdecl->struct_id_->name_, true);
+        n->flags_[ExprNode::PROTO] = true;
+      } else {
+        n->struct_type_ = scopes_->top_struct()->lookup(sdecl->struct_id_->name_, true);
+      }
+      if (!n->struct_type_)
+        return mkstatus_(n, "Type %s has not been declared", sdecl->struct_id_->full_name().c_str());
+      n->bit_width_ = n->struct_type_->bit_width_;
+    }
+  } else {
+    if (n->decl_->storage_type_ == VariableDeclStmtNode::INTEGER)
+      return mkstatus_(n, "Subfield access not valid for numeric types");
+    auto sdecl = static_cast<StructVariableDeclStmtNode*>(n->decl_);
+    if (sdecl->struct_id_->scope_name_ == "proto") {
+      n->struct_type_ = proto_scopes_->top_struct()->lookup(sdecl->struct_id_->name_, true);
+      n->flags_[ExprNode::PROTO] = true;
+    } else {
+      n->struct_type_ = scopes_->top_struct()->lookup(sdecl->struct_id_->name_, true);
+    }
+    if (!n->struct_type_)
+      return mkstatus_(n, "Type %s has not been declared", sdecl->struct_id_->full_name().c_str());
+    n->sub_decl_ = n->struct_type_->field(n->sub_name_);
+
+    if (!n->sub_decl_)
+      return mkstatus_(n, "Access to invalid subfield %s.%s", n->c_str(), n->sub_name_.c_str());
+    if (n->sub_decl_->storage_type_ != VariableDeclStmtNode::INTEGER)
+      return mkstatus_(n, "Accessing non-numeric subfield %s.%s", n->c_str(), n->sub_name_.c_str());
+
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = n->sub_decl_->bit_width_;
+    n->flags_[ExprNode::WRITE] = true;
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_assign_expr_node(AssignExprNode* n) {
+  /// @todo check lhs is assignable
+  TRY2(n->id_->accept(this));
+  if (n->id_->typeof_ == ExprNode::STRUCT) {
+    TRY2(n->rhs_->accept(this));
+    if (n->rhs_->typeof_ != ExprNode::STRUCT)
+      return mkstatus_(n, "Right-hand side of assignment must be a struct");
+  } else {
+    if (n->id_->typeof_ != ExprNode::INTEGER)
+      return mkstatus_(n, "Left-hand side of assignment must be a numeric type");
+    if (!n->id_->flags_[ExprNode::WRITE])
+      return mkstatus_(n, "Left-hand side of assignment is read-only");
+    TRY2(n->rhs_->accept(this));
+    if (n->rhs_->typeof_ != ExprNode::INTEGER)
+      return mkstatus_(n, "Right-hand side of assignment must be a numeric type");
+  }
+  n->typeof_ = ExprNode::VOID;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_packet_expr_node(PacketExprNode* n) {
+  StructDeclStmtNode *struct_type = proto_scopes_->top_struct()->lookup(n->id_->name_, true);
+  if (!struct_type)
+    return mkstatus_(n, "Undefined packet header %s", n->id_->c_str());
+  if (n->id_->sub_name_.empty()) {
+    n->typeof_ = ExprNode::STRUCT;
+    n->struct_type_ = struct_type;
+  } else {
+    VariableDeclStmtNode *sub_decl = struct_type->field(n->id_->sub_name_);
+    if (!sub_decl)
+      return mkstatus_(n, "Access to invalid subfield %s.%s", n->id_->c_str(), n->id_->sub_name_.c_str());
+    n->typeof_ = ExprNode::INTEGER;
+    if (n->is_ref())
+      n->bit_width_ = 64;
+    else
+      n->bit_width_ = sub_decl->bit_width_;
+  }
+  n->flags_[ExprNode::WRITE] = false;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_integer_expr_node(IntegerExprNode* n) {
+  n->typeof_ = ExprNode::INTEGER;
+  n->bit_width_ = n->bits_;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_binop_expr_node(BinopExprNode* n) {
+  TRY2(n->lhs_->accept(this));
+  if (n->lhs_->typeof_ != ExprNode::INTEGER)
+    return mkstatus_(n, "Left-hand side of binary expression must be a numeric type");
+  TRY2(n->rhs_->accept(this));
+  if (n->rhs_->typeof_ != ExprNode::INTEGER)
+    return mkstatus_(n, "Right-hand side of binary expression must be a numeric type");
+  n->typeof_ = ExprNode::INTEGER;
+  switch(n->op_) {
+    case Tok::TCEQ:
+    case Tok::TCNE:
+    case Tok::TCLT:
+    case Tok::TCLE:
+    case Tok::TCGT:
+    case Tok::TCGE:
+      n->bit_width_ = 1;
+    default:
+      n->bit_width_ = std::max(n->lhs_->bit_width_, n->rhs_->bit_width_);
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_unop_expr_node(UnopExprNode* n) {
+  TRY2(n->expr_->accept(this));
+  if (n->expr_->typeof_ != ExprNode::INTEGER)
+    return mkstatus_(n, "Unary operand must be a numeric type");
+  n->copy_type(*n->expr_);
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_bitop_expr_node(BitopExprNode* n) {
+  if (n->expr_->typeof_ != ExprNode::INTEGER)
+    return mkstatus_(n, "Bitop [] can only operate on numeric types");
+  n->typeof_ = ExprNode::INTEGER;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_goto_expr_node(GotoExprNode* n) {
+  //n->id_->accept(this);
+  n->typeof_ = ExprNode::VOID;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_return_expr_node(ReturnExprNode* n) {
+  TRY2(n->expr_->accept(this));
+  n->typeof_ = ExprNode::VOID;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::expect_method_arg(MethodCallExprNode* n, size_t num, size_t num_def_args = 0) {
+  if (num_def_args == 0) {
+    if (n->args_.size() != num)
+      return mkstatus_(n, "%s expected %d argument%s, %zu given", n->id_->sub_name_.c_str(),
+                      num, num == 1 ? "" : "s", n->args_.size());
+  } else {
+    if (n->args_.size() < num - num_def_args || n->args_.size() > num)
+      return mkstatus_(n, "%s expected %d argument%s (%d default), %zu given", n->id_->sub_name_.c_str(),
+                      num, num == 1 ? "" : "s", num_def_args, n->args_.size());
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::check_lookup_method(MethodCallExprNode* n) {
+  auto table = scopes_->top_table()->lookup(n->id_->name_);
+  if (!table)
+    return mkstatus_(n, "Unknown table name %s", n->id_->c_str());
+  TRY2(expect_method_arg(n, 2, 1));
+  if (table->type_id()->name_ == "LPM")
+    return mkstatus_(n, "LPM unsupported");
+  if (n->block_->scope_) {
+    auto result = make_unique<StructVariableDeclStmtNode>(table->leaf_id()->copy(), make_unique<IdentExprNode>("_result"),
+                                                          VariableDeclStmtNode::STRUCT_REFERENCE);
+    n->block_->scope_->add("_result", result.get());
+    n->block_->stmts_.insert(n->block_->stmts_.begin(), move(result));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::check_update_method(MethodCallExprNode* n) {
+  auto table = scopes_->top_table()->lookup(n->id_->name_);
+  if (!table)
+    return mkstatus_(n, "Unknown table name %s", n->id_->c_str());
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED")
+    TRY2(expect_method_arg(n, 2));
+  else if (table->type_id()->name_ == "LPM")
+    TRY2(expect_method_arg(n, 3));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::check_delete_method(MethodCallExprNode* n) {
+  auto table = scopes_->top_table()->lookup(n->id_->name_);
+  if (!table)
+    return mkstatus_(n, "Unknown table name %s", n->id_->c_str());
+  if (table->type_id()->name_ == "FIXED_MATCH" || table->type_id()->name_ == "INDEXED")
+    TRY2(expect_method_arg(n, 1));
+  else if (table->type_id()->name_ == "LPM")
+    {}
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_method_call_expr_node(MethodCallExprNode* n) {
+  // be sure to visit those child nodes ASAP, so their properties can
+  // be propagated up to this node and be ready to be used
+  for (auto it = n->args_.begin(); it != n->args_.end(); ++it) {
+    TRY2((*it)->accept(this));
+  }
+
+  n->typeof_ = ExprNode::VOID;
+  if (n->id_->sub_name_.size()) {
+    if (n->id_->sub_name_ == "lookup") {
+      TRY2(check_lookup_method(n));
+    } else if (n->id_->sub_name_ == "update") {
+      TRY2(check_update_method(n));
+    } else if (n->id_->sub_name_ == "delete") {
+      TRY2(check_delete_method(n));
+    } else if (n->id_->sub_name_ == "forward" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 1));
+    } else if (n->id_->sub_name_ == "drop" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 0));
+    } else if (n->id_->sub_name_ == "push_header" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 2, 1));
+    } else if (n->id_->sub_name_ == "pop_header" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 1));
+    } else if (n->id_->sub_name_ == "push_vlan" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 1));
+    } else if (n->id_->sub_name_ == "pop_vlan" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 0));
+    } else if (n->id_->sub_name_ == "rewrite_field" && n->id_->name_ == "pkt") {
+      TRY2(expect_method_arg(n, 2));
+      n->args_[0]->flags_[ExprNode::IS_LHS] = true;
+    }
+  } else if (n->id_->name_ == "channel_push") {
+    TRY2(expect_method_arg(n, 1));
+  } else if (n->id_->name_ == "log") {
+    TRY2(expect_method_arg(n, 1));
+  } else if (n->id_->name_ == "atomic_add") {
+    TRY2(expect_method_arg(n, 2));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = n->args_[0]->bit_width_;
+    n->args_[0]->flags_[ExprNode::IS_LHS] = true;
+  } else if (n->id_->name_ == "cksum") {
+    TRY2(expect_method_arg(n, 1));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = 16;
+  } else if (n->id_->name_ == "incr_cksum_u16") {
+    TRY2(expect_method_arg(n, 4, 1));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = 16;
+  } else if (n->id_->name_ == "incr_cksum_u32") {
+    TRY2(expect_method_arg(n, 4, 1));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = 16;
+  } else if (n->id_->name_ == "incr_cksum") {
+    TRY2(expect_method_arg(n, 4, 1));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = 16;
+  } else if (n->id_->name_ == "lb_hash") {
+    TRY2(expect_method_arg(n, 3, 1));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = 8;
+  } else if (n->id_->name_ == "sizeof") {
+    TRY2(expect_method_arg(n, 1));
+    n->typeof_ = ExprNode::INTEGER;
+    n->bit_width_ = 32;
+  } else if (n->id_->name_ == "get_usec_time") {
+     TRY2(expect_method_arg(n, 0));
+     n->typeof_ = ExprNode::INTEGER;
+     n->bit_width_ = 64;
+  }
+
+  if (!n->block_->stmts_.empty()) {
+    if (n->id_->sub_name_ != "update" && n->id_->sub_name_ != "lookup")
+      return mkstatus_(n, "%s does not allow trailing block statements", n->id_->full_name().c_str());
+    TRY2(n->block_->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_expr_stmt_node(ExprStmtNode* n) {
+  TRY2(n->expr_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_struct_variable_decl_stmt_node(StructVariableDeclStmtNode* n) {
+  //TRY2(n->struct_id_->accept(this));
+  //TRY2(n->id_->accept(this));
+  if (!n->init_.empty()) {
+    StructDeclStmtNode *type;
+    if (n->struct_id_->scope_name_ == "proto")
+      type = proto_scopes_->top_struct()->lookup(n->struct_id_->name_, true);
+    else
+      type = scopes_->top_struct()->lookup(n->struct_id_->name_, true);
+
+    if (!type)
+      return mkstatus_(n, "type %s does not exist", n->struct_id_->full_name().c_str());
+
+    // init remaining fields to 0
+    set<string> used;
+    for (auto i = n->init_.begin(); i != n->init_.end(); ++i) {
+      auto asn = static_cast<AssignExprNode*>(i->get());
+      used.insert(asn->id_->sub_name_);
+    }
+    for (auto f = type->stmts_.begin(); f != type->stmts_.end(); ++f) {
+      if (used.find((*f)->id_->name_) == used.end()) {
+        auto id = make_unique<IdentExprNode>(n->id_->name_);
+        id->append_dot((*f)->id_->name_);
+        n->init_.push_back(make_unique<AssignExprNode>(move(id), make_unique<IntegerExprNode>("0")));
+      }
+    }
+
+    for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+      TRY2((*it)->accept(this));
+    }
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_integer_variable_decl_stmt_node(IntegerVariableDeclStmtNode* n) {
+  //TRY2(n->id_->accept(this));
+  if (!n->init_.empty()) {
+    TRY2(n->init_[0]->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_struct_decl_stmt_node(StructDeclStmtNode* n) {
+  //TRY2(n->id_->accept(this));
+  for (auto it = n->stmts_.begin(); it != n->stmts_.end(); ++it) {
+    TRY2((*it)->accept(this));
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_parser_state_stmt_node(ParserStateStmtNode* n) {
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_timer_decl_stmt_node(TimerDeclStmtNode* n) {
+  auto timer_delay = make_unique<IntegerVariableDeclStmtNode>(make_unique<IdentExprNode>("timer_delay"), "32");
+  scopes_->current_var()->add("timer_delay", timer_delay.get());
+  n->block_->stmts_.push_back(move(timer_delay));
+
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_state_decl_stmt_node(StateDeclStmtNode* n) {
+  if (!n->id_) {
+    return mkstatus(0);
+  }
+  auto s1 = proto_scopes_->top_state()->lookup(n->id_->name_, true);
+  if (s1) {
+    const string &name = n->id_->name_;
+    auto offset_var = make_unique<IntegerVariableDeclStmtNode>(make_unique<IdentExprNode>("$" + name), "64");
+    offset_var->init_.push_back(make_unique<AssignExprNode>(offset_var->id_->copy(), make_unique<IntegerExprNode>("0")));
+    scopes_->current_var()->add("$" + name, offset_var.get());
+    s1->subs_[0].block_->scope_->add("$" + name, offset_var.get());
+    n->init_.push_back(move(offset_var));
+
+    n->parser_ = ParserStateStmtNode::make(n->id_);
+    n->parser_->next_state_ = s1->subs_[0].block_.get();
+    n->parser_->scope_id_ = n->scope_id_;
+
+    auto p = proto_scopes_->top_struct()->lookup(n->id_->name_, true);
+    if (!p) return mkstatus_(n, "unable to find struct decl for parser state %s", n->id_->full_name().c_str());
+
+    // $proto = parsed_bytes; parsed_bytes += sizeof($proto);
+    auto asn1 = make_unique<AssignExprNode>(make_unique<IdentExprNode>("$" + n->id_->name_),
+                                            make_unique<IdentExprNode>("parsed_bytes"));
+    n->init_.push_back(make_unique<ExprStmtNode>(move(asn1)));
+    auto add_expr = make_unique<BinopExprNode>(make_unique<IdentExprNode>("parsed_bytes"), Tok::TPLUS,
+                                               make_unique<IntegerExprNode>(std::to_string(p->bit_width_ >> 3), 64));
+    auto asn2 = make_unique<AssignExprNode>(make_unique<IdentExprNode>("parsed_bytes"), move(add_expr));
+    n->init_.push_back(make_unique<ExprStmtNode>(move(asn2)));
+  }
+
+  for (auto it = n->init_.begin(); it != n->init_.end(); ++it) {
+    TRY2((*it)->accept(this));
+  }
+
+  for (auto it = n->subs_.begin(); it != n->subs_.end(); ++it) {
+    auto scope = scopes_->current_state();
+    scopes_->set_current(it->scope_);
+
+    TRY2(it->block_->accept(this));
+
+    if (s1) {
+      if (it->id_->name_ == "") {
+        it->parser_ = ParserStateStmtNode::make(it->id_);
+        it->parser_->next_state_ = s1->subs_[0].block_.get();
+        it->parser_->scope_id_ = n->scope_id_ + n->id_->name_ + "_";
+      } else if (auto s2 = proto_scopes_->top_state()->lookup(it->id_->name_, true)) {
+        it->parser_ = ParserStateStmtNode::make(it->id_);
+        it->parser_->next_state_ = s2->subs_[0].block_.get();
+        it->parser_->scope_id_ = n->scope_id_ + n->id_->name_ + "_";
+      }
+
+      if (it->parser_) {
+        TRY2(it->parser_->accept(this));
+      }
+    }
+
+    scopes_->set_current(scope);
+  }
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_match_decl_stmt_node(MatchDeclStmtNode* n) {
+  //TRY2(n->id_->accept(this));
+  for (auto it = n->formals_.begin(); it != n->formals_.end(); ++it) {
+    TRY2((*it)->accept(this));
+  }
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_miss_decl_stmt_node(MissDeclStmtNode* n) {
+  //TRY2(n->id_->accept(this));
+  for (auto it = n->formals_.begin(); it != n->formals_.end(); ++it) {
+    TRY2((*it)->accept(this));
+  }
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_failure_decl_stmt_node(FailureDeclStmtNode* n) {
+  //TRY2(n->id_->accept(this));
+  for (auto it = n->formals_.begin(); it != n->formals_.end(); ++it) {
+    TRY2((*it)->accept(this));
+  }
+  TRY2(n->block_->accept(this));
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit_table_decl_stmt_node(TableDeclStmtNode* n) {
+  auto key_type = scopes_->top_struct()->lookup(n->key_id()->name_, true);
+  if (!key_type)
+    return mkstatus_(n, "Table key type %s undefined", n->key_id()->c_str());
+  n->key_id()->bit_width_ = key_type->bit_width_;
+  auto leaf_type = scopes_->top_struct()->lookup(n->leaf_id()->name_, true);
+  if (!leaf_type)
+    return mkstatus_(n, "Table leaf type %s undefined", n->leaf_id()->c_str());
+  n->leaf_id()->bit_width_ = leaf_type->bit_width_;
+  return mkstatus(0);
+}
+
+StatusTuple TypeCheck::visit(Node* root) {
+  BlockStmtNode* b = static_cast<BlockStmtNode*>(root);
+
+  if (pragmas_.count("name") == 0)
+    return mkstatus(-1, "#name <PackageName> must be defined");
+
+  scopes_->set_current(scopes_->top_state());
+  scopes_->set_current(scopes_->top_var());
+
+  // add builtin types and identifiers
+  if (scopes_->top_struct()->lookup("_Packet", true)) {
+    return mkstatus(-1, "_Packet already defined");
+  }
+  auto pkt_type = make_unique<StructDeclStmtNode>(make_unique<IdentExprNode>("_Packet"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("port_id"), "8"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("length"), "16"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("parsed_bytes"), "16"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("vlan_tag"), "16"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg8"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg7"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg6"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg5"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg4"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg3"), "32")); // originator_plum
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg2"), "32")); // gid
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("arg1"), "32")); // from_fabric
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("tun_key.crypto_proto"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("tun_key.crypto_hr"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("tun_key.crypto_mark"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("tun_key.crypto_spi"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("tun_key.src_ip"), "32"));
+  pkt_type->stmts_.push_back(make_unique<IntegerVariableDeclStmtNode>(
+          make_unique<IdentExprNode>("tun_key.dst_ip"), "32"));
+  scopes_->top_struct()->add("_Packet", pkt_type.get());
+  b->stmts_.push_back(move(pkt_type));
+
+  if (scopes_->current_var()->lookup("pkt", true)) {
+    return mkstatus(-1, "pkt already defined");
+  }
+  auto pkt = make_unique<StructVariableDeclStmtNode>(make_unique<IdentExprNode>("_Packet"),
+                                                     make_unique<IdentExprNode>("pkt"));
+  pkt->storage_type_ = VariableDeclStmtNode::STRUCT_REFERENCE;
+  scopes_->current_var()->add("pkt", pkt.get());
+  b->stmts_.push_back(move(pkt));
+
+  // packet data in bpf socket
+  if (scopes_->top_struct()->lookup("_skbuff", true)) {
+    return mkstatus(-1, "_skbuff already defined");
+  }
+  auto skb_type = make_unique<StructDeclStmtNode>(make_unique<IdentExprNode>("_skbuff"));
+  scopes_->top_struct()->add("_skbuff", skb_type.get());
+  b->stmts_.push_back(move(skb_type));
+
+  if (scopes_->current_var()->lookup("skb", true)) {
+    return mkstatus(-1, "skb already defined");
+  }
+  auto skb = make_unique<StructVariableDeclStmtNode>(make_unique<IdentExprNode>("_skbuff"),
+                                                     make_unique<IdentExprNode>("skb"));
+  skb->storage_type_ = VariableDeclStmtNode::STRUCT_REFERENCE;
+  scopes_->current_var()->add("skb", skb.get());
+  b->stmts_.push_back(move(skb));
+
+  // offset counter
+  auto parsed_bytes = make_unique<IntegerVariableDeclStmtNode>(
+                        make_unique<IdentExprNode>("parsed_bytes"), "64");
+  parsed_bytes->init_.push_back(make_unique<AssignExprNode>(parsed_bytes->id_->copy(), make_unique<IntegerExprNode>("0")));
+  scopes_->current_var()->add("parsed_bytes", parsed_bytes.get());
+  b->stmts_.push_back(move(parsed_bytes));
+
+  TRY2(b->accept(this));
+
+  if (!errors_.empty()) {
+    for (auto it = errors_.begin(); it != errors_.end(); ++it) {
+      fprintf(stderr, "%s\n", it->c_str());
+    }
+    return mkstatus(-1, errors_.begin()->c_str());
+  }
+  return mkstatus(0);
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/type_check.h b/jit/src/cc/type_check.h
new file mode 100644
index 0000000..7818d2d
--- /dev/null
+++ b/jit/src/cc/type_check.h
@@ -0,0 +1,52 @@
+/*
+ * =====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * =====================================================================
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+#include "cc/node.h"
+#include "cc/scope.h"
+
+namespace ebpf {
+namespace cc {
+
+class TypeCheck : public Visitor {
+ public:
+  TypeCheck(Scopes *scopes, Scopes *proto_scopes, const std::map<std::string, std::string>& pragmas)
+      : scopes_(scopes), proto_scopes_(proto_scopes), pragmas_(pragmas) {}
+
+  virtual STATUS_RETURN visit(Node* n);
+  STATUS_RETURN expect_method_arg(MethodCallExprNode* n, size_t num, size_t num_def_args);
+  STATUS_RETURN check_lookup_method(MethodCallExprNode* n);
+  STATUS_RETURN check_update_method(MethodCallExprNode* n);
+  STATUS_RETURN check_delete_method(MethodCallExprNode* n);
+
+#define VISIT(type, func) virtual STATUS_RETURN visit_##func(type* n);
+  EXPAND_NODES(VISIT)
+#undef VISIT
+
+ private:
+  Scopes *scopes_;
+  Scopes *proto_scopes_;
+  vector<string> errors_;
+  const std::map<std::string, std::string> &pragmas_;
+};
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/cc/type_helper.h b/jit/src/cc/type_helper.h
new file mode 100644
index 0000000..ed66193
--- /dev/null
+++ b/jit/src/cc/type_helper.h
@@ -0,0 +1,115 @@
+/*
+ * ====================================================================
+ * Copyright (c) 2012, PLUMgrid, http://plumgrid.com
+ *
+ * This source is subject to the PLUMgrid License.
+ * All rights reserved.
+ *
+ * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * PLUMgrid confidential information, delete if you are not the
+ * intended recipient.
+ *
+ * ====================================================================
+ */
+
+#pragma once
+
+namespace ebpf {
+namespace cc {
+
+// Represent the numeric type of a protocol field
+enum FieldType {
+  INVALID = 0,
+  UINT8_T,
+  UINT16_T,
+  UINT32_T,
+  UINT64_T,
+  UINT128_T,
+  VOID
+};
+
+static inline size_t enum_to_size(const FieldType t) {
+  switch (t) {
+    case UINT8_T: return sizeof(uint8_t);
+    case UINT16_T: return sizeof(uint16_t);
+    case UINT32_T: return sizeof(uint32_t);
+    case UINT64_T: return sizeof(uint64_t);
+    case UINT128_T: return sizeof(__uint128_t);
+    default:
+      return 0;
+  }
+}
+
+/// Convert a bit size to the next highest power of 2
+static inline int next_base2(int v) {
+  --v;
+  v |= v >> 1;
+  v |= v >> 2;
+  v |= v >> 4;
+  v |= v >> 8;
+  v |= v >> 16;
+  ++v;
+  return v;
+}
+
+static inline const char* bits_to_uint(int v) {
+  v = next_base2(v);
+  if (v <= 8) {
+    return "uint8_t";
+  } else if (v == 16) {
+    return "uint16_t";
+  } else if (v == 32) {
+    return "uint32_t";
+  } else if (v == 64) {
+    return "uint64_t";
+  } else if (v >= 128) {
+    /* in plumlet 128-bit integers should be 8-byte aligned,
+     * all other ints should have natural alignment */
+    return "unsigned __int128 __attribute__((packed, aligned(8)))";
+  }
+  return "void";
+}
+
+static inline FieldType bits_to_enum(int v) {
+  v = next_base2(v);
+  if (v <= 8) {
+    return UINT8_T;
+  } else if (v == 16) {
+    return UINT16_T;
+  } else if (v == 32) {
+    return UINT32_T;
+  } else if (v == 64) {
+    return UINT64_T;
+  } else if (v >= 128) {
+    return UINT128_T;
+  }
+  return VOID;
+}
+
+static inline size_t bits_to_size(int v) {
+  return enum_to_size(bits_to_enum(v));
+}
+
+static inline size_t align_offset(size_t offset, FieldType ft) {
+  switch (ft) {
+    case UINT8_T:
+      return offset % 8 > 0 ? offset + (8 - offset % 8) : offset;
+    case UINT16_T:
+      return offset % 16 > 0 ? offset + (16 - offset % 16) : offset;
+    case UINT32_T:
+      return offset % 32 > 0 ? offset + (32 - offset % 32) : offset;
+    case UINT64_T:
+    case UINT128_T:
+      return offset % 64 > 0 ? offset + (64 - offset % 64) : offset;
+    default:
+      ;
+  }
+  return offset;
+}
+
+}  // namespace cc
+}  // namespace ebpf
diff --git a/jit/src/libbpf.h b/jit/src/libbpf.h
new file mode 100644
index 0000000..1eeab11
--- /dev/null
+++ b/jit/src/libbpf.h
@@ -0,0 +1,196 @@
+/* eBPF mini library */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct bpf_insn;
+enum bpf_map_type;
+enum bpf_prog_type;
+
+int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
+		   int max_entries);
+int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags);
+int bpf_lookup_elem(int fd, void *key, void *value);
+int bpf_delete_elem(int fd, void *key);
+int bpf_get_next_key(int fd, void *key, void *next_key);
+
+int bpf_prog_load(enum bpf_prog_type prog_type,
+		  const struct bpf_insn *insns, int insn_len,
+		  const char *license);
+int bpf_attach_socket(int sockfd, int progfd);
+int bpf_attach_filter(int progfd, const char *prog_name, uint32_t ifindex,
+                      uint8_t prio, uint32_t classid);
+
+/* create RAW socket and bind to interface 'name' */
+int bpf_open_raw_sock(const char *name);
+
+#define LOG_BUF_SIZE 65536
+extern char bpf_log_buf[LOG_BUF_SIZE];
+
+/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */
+
+#define BPF_ALU64_REG(OP, DST, SRC)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_OP(OP) | BPF_X,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+#define BPF_ALU32_REG(OP, DST, SRC)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU | BPF_OP(OP) | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */
+
+#define BPF_ALU64_IMM(OP, DST, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_OP(OP) | BPF_K,	\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+#define BPF_ALU32_IMM(OP, DST, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU | BPF_OP(OP) | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Short form of mov, dst_reg = src_reg */
+
+#define BPF_MOV64_REG(DST, SRC)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_MOV | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/* Short form of mov, dst_reg = imm32 */
+
+#define BPF_MOV64_IMM(DST, IMM)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_MOV | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */
+#define BPF_LD_IMM64(DST, IMM)					\
+	BPF_LD_IMM64_RAW(DST, 0, IMM)
+
+#define BPF_LD_IMM64_RAW(DST, SRC, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_LD | BPF_DW | BPF_IMM,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = (__u32) (IMM) }),			\
+	((struct bpf_insn) {					\
+		.code  = 0, /* zero is reserved opcode */	\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = ((__u64) (IMM)) >> 32 })
+
+#define BPF_PSEUDO_MAP_FD	1
+
+/* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */
+#define BPF_LD_MAP_FD(DST, MAP_FD)				\
+	BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
+
+
+/* Direct packet access, R0 = *(uint *) (skb->data + imm32) */
+
+#define BPF_LD_ABS(SIZE, IMM)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS,	\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Memory load, dst_reg = *(uint *) (src_reg + off16) */
+
+#define BPF_LDX_MEM(SIZE, DST, SRC, OFF)			\
+	((struct bpf_insn) {					\
+		.code  = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Memory store, *(uint *) (dst_reg + off16) = src_reg */
+
+#define BPF_STX_MEM(SIZE, DST, SRC, OFF)			\
+	((struct bpf_insn) {					\
+		.code  = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Memory store, *(uint *) (dst_reg + off16) = imm32 */
+
+#define BPF_ST_MEM(SIZE, DST, OFF, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Conditional jumps against registers, if (dst_reg 'op' src_reg) goto pc + off16 */
+
+#define BPF_JMP_REG(OP, DST, SRC, OFF)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_JMP | BPF_OP(OP) | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */
+
+#define BPF_JMP_IMM(OP, DST, IMM, OFF)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_JMP | BPF_OP(OP) | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Raw code statement block */
+
+#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM)			\
+	((struct bpf_insn) {					\
+		.code  = CODE,					\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Program exit */
+
+#define BPF_EXIT_INSN()						\
+	((struct bpf_insn) {					\
+		.code  = BPF_JMP | BPF_EXIT,			\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tests/jit/loader.py b/tests/jit/loader.py
new file mode 100755
index 0000000..adf4fcb
--- /dev/null
+++ b/tests/jit/loader.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+import sys
+from src.bpf import BPF
+prog = BPF(sys.argv[1], sys.argv[2], sys.argv[3],
+        prog_type=int(sys.argv[4]), debug=int(sys.argv[5]))
+
diff --git a/tests/jit/proto.dph b/tests/jit/proto.dph
new file mode 100644
index 0000000..e1bd1f0
--- /dev/null
+++ b/tests/jit/proto.dph
@@ -0,0 +1,155 @@
+
+@1.0.0
+#packed 'true'
+
+struct ethernet {
+  dst:48
+  src:48
+  type:16
+}
+
+state ethernet {
+  switch $ethernet.type {
+    case 0x0800 {
+      next proto::ip
+    }
+    case 0x8100 {
+      next proto::dot1q
+    }
+    case * {
+      goto EOP
+    }
+  }
+}
+
+
+struct dot1q {
+  pri:3
+  cfi:1
+  vlanid:12
+  type:16
+}
+
+state dot1q {
+  switch $dot1q.type {
+    case 0x0800 {
+      next proto::ip
+    }
+    case * {
+      goto EOP
+    }
+  }
+}
+
+
+struct ip {
+  ver:4
+  hlen:4
+  tos:8
+  tlen:16
+  identification:16
+  ffo_unused:1
+  df:1
+  mf:1
+  foffset:13
+  ttl:8
+  nextp:8
+  hchecksum:16
+  src:32
+  dst:32
+}
+
+state ip {
+  switch $ip.nextp {
+    case 6 {
+      next proto::tcp
+    }
+    case 17 {
+      next proto::udp
+    }
+    case 47 {
+      next proto::gre
+    }
+    case * {
+      goto EOP
+    }
+  }
+}
+
+
+struct udp {
+  sport:16
+  dport:16
+  length:16
+  crc:16
+}
+
+state udp {
+  switch $udp.dport {
+    case 8472 {
+      next proto::vxlan
+    }
+    case * {
+      goto EOP
+    }
+  }
+}
+
+struct tcp {
+  src_port:16
+  dst_port:16
+  seq_num:32
+  ack_num:32
+  offset:4
+  reserved:4
+  flag_cwr:1
+  flag_ece:1
+  flag_urg:1
+  flag_ack:1
+  flag_psh:1
+  flag_rst:1
+  flag_syn:1
+  flag_fin:1
+  rcv_wnd:16
+  cksum:16
+  urg_ptr:16
+}
+
+state tcp {
+  goto EOP
+}
+
+struct vxlan {
+  rsv1:4
+  iflag:1
+  rsv2:3
+  rsv3:24
+  key:24
+  rsv4:8
+}
+
+state vxlan {
+  goto EOP
+}
+
+
+struct gre {
+  cflag:1
+  rflag:1
+  kflag:1
+  snflag:1
+  srflag:1
+  recurflag:3
+  reserved:5
+  vflag:3
+  protocol:16
+  key:32
+}
+
+state gre {
+  switch $gre.protocol {
+    case * {
+      goto EOP
+    }
+  }
+}
diff --git a/tests/jit/proto_simple.dph b/tests/jit/proto_simple.dph
new file mode 100644
index 0000000..086b02f
--- /dev/null
+++ b/tests/jit/proto_simple.dph
@@ -0,0 +1,17 @@
+
+@1.0.0
+#packed 'true'
+
+struct ethernet {
+  dst:48
+  src:48
+  type:16
+}
+
+state ethernet {
+  switch $ethernet.type {
+    case * {
+      goto EOP
+    }
+  }
+}
diff --git a/tests/jit/test1.dp b/tests/jit/test1.dp
new file mode 100644
index 0000000..952b232
--- /dev/null
+++ b/tests/jit/test1.dp
@@ -0,0 +1,45 @@
+
+@1.0.0
+#name test1
+struct SomeKey {
+  foo:3
+  foo1:9
+  foo2:63
+}
+struct SomeLeaf {
+  bar:64
+}
+Table<SomeKey, SomeLeaf, FIXED_MATCH, LRU> stats(8)
+
+state INIT {
+  goto proto::ethernet
+}
+
+var ret:32 = 0
+
+state proto::ethernet {
+  var SomeKey sk2{foo = 2}
+  stats.lookup(sk2) {
+    on_match stats (var SomeLeaf *l21) {
+      atomic_add(l21.bar, 1)
+    }
+    on_miss stats () {
+      ret = 1
+    }
+  }
+  var SomeKey sk3{foo = 3}
+  stats.lookup(sk3) {
+  }
+  var SomeKey sk4{foo = 4}
+  var SomeLeaf sl4{bar = 1}
+  stats.update(sk4, sl4) { }
+  var SomeKey sk5{foo = 5}
+  var SomeLeaf sl5{bar = 1}
+  stats.update(sk5, sl5) {
+    on_failure stats (var SomeKey *k21) {}
+  }
+}
+
+state EOP {
+  return ret
+}
diff --git a/tests/jit/test2.dp b/tests/jit/test2.dp
new file mode 100644
index 0000000..2ad4768
--- /dev/null
+++ b/tests/jit/test2.dp
@@ -0,0 +1,67 @@
+
+@1.0.0
+#name 'socket1'
+#packed 'false'
+
+struct IPKey {
+  dip:32
+  sip:32
+}
+struct IPLeaf {
+  rx_pkts:64
+  tx_pkts:64
+}
+Table<IPKey, IPLeaf, FIXED_MATCH, LRU> stats(1024)
+
+var ret:32 = 0
+
+state INIT {
+  goto proto::ethernet
+}
+
+state proto::ethernet {
+}
+
+state proto::dot1q {
+}
+
+state proto::ip {
+  var rx:32 = 0
+  var tx:32 = 0
+  var IPKey key
+  if $ip.dst > $ip.src {
+    key.dip = $ip.dst
+    key.sip = $ip.src
+    rx = 1
+    if false {
+      return 3
+    }
+  } else {
+    key.dip = $ip.src
+    key.sip = $ip.dst
+    tx = 1
+    ret = 1
+  }
+  var IPLeaf *leaf
+  stats.lookup(key, leaf) {}
+  on_valid(leaf) {
+    atomic_add(leaf.rx_pkts, rx)
+    atomic_add(leaf.tx_pkts, tx)
+  } else {
+    var IPLeaf newleaf{rx_pkts = rx, tx_pkts = tx}
+    stats.update(key, newleaf) {}
+  }
+}
+
+state proto::udp {
+}
+
+state proto::vxlan {
+}
+
+state proto::gre {
+}
+
+state EOP {
+  return ret
+}
diff --git a/tests/jit/test2.py b/tests/jit/test2.py
new file mode 100644
index 0000000..2bbed54
--- /dev/null
+++ b/tests/jit/test2.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+# test program to count the packets sent to a device in a .5
+# second period
+
+import time
+import netaddr
+from ctypes import *
+from src.bpf import BPF
+
+prog = BPF("socket1", "tests/test2.dp", "tests/proto.dph")
+
+class Key(Structure):
+    _fields_ = [("dip", c_uint),
+                ("sip", c_uint)]
+class Leaf(Structure):
+    _fields_ = [("rx_pkts", c_ulong),
+                ("tx_pkts", c_ulong)]
+
+prog.attach("eth0")
+stats = prog.table("stats", Key, Leaf)
+
+time.sleep(0.5)
+
+for key in stats.iter():
+    leaf = stats.get(key)
+    print(netaddr.IPAddress(key.sip), "=>", netaddr.IPAddress(key.dip),
+          "rx", leaf.rx_pkts, "tx", leaf.tx_pkts)
diff --git a/tests/jit/test3.dp b/tests/jit/test3.dp
new file mode 100644
index 0000000..564f17c
--- /dev/null
+++ b/tests/jit/test3.dp
@@ -0,0 +1,70 @@
+// test for packet modification
+
+@1.0.0
+#name 'classifier'
+#packed 'false'
+
+struct IPKey {
+  dip:32
+  sip:32
+}
+struct IPLeaf {
+  xdip:32
+  xsip:32
+  xlated_pkts:64
+}
+Table<IPKey, IPLeaf, FIXED_MATCH, LRU> xlate(1024)
+
+var ret:32 = 1
+
+var orig_dip:32 = 0
+var orig_sip:32 = 0
+var IPLeaf *xleaf;
+
+state INIT {
+  goto proto::ethernet
+}
+
+state proto::ethernet {
+}
+
+state proto::dot1q {
+}
+
+state proto::ip {
+  orig_dip = $ip.dst
+  orig_sip = $ip.src
+  var IPKey key{dip=orig_dip, sip=orig_sip}
+  xlate.lookup(key, xleaf) {}
+  on_valid(xleaf) {
+    incr_cksum(@ip.hchecksum, orig_dip, xleaf.xdip)
+    incr_cksum(@ip.hchecksum, orig_sip, xleaf.xsip)
+    pkt.rewrite_field($ip.dst, xleaf.xdip)
+    pkt.rewrite_field($ip.src, xleaf.xsip)
+    atomic_add(xleaf.xlated_pkts, 1)
+  }
+}
+
+state proto::udp {
+  on_valid(xleaf) {
+    incr_cksum(@udp.crc, orig_dip, xleaf.xdip, 1)
+    incr_cksum(@udp.crc, orig_sip, xleaf.xsip, 1)
+  }
+}
+
+state proto::tcp {
+  on_valid(xleaf) {
+    incr_cksum(@tcp.cksum, orig_dip, xleaf.xdip, 1)
+    incr_cksum(@tcp.cksum, orig_sip, xleaf.xsip, 1)
+  }
+}
+
+state proto::vxlan {
+}
+
+state proto::gre {
+}
+
+state EOP {
+  return ret
+}
diff --git a/tests/jit/test3.py b/tests/jit/test3.py
new file mode 100644
index 0000000..a114a26
--- /dev/null
+++ b/tests/jit/test3.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+import time
+from netaddr import IPAddress
+from ctypes import *
+from src.bpf import BPF
+
+prog = BPF("classifier", "tests/test3.dp", "tests/proto.dph",
+        BPF.BPF_PROG_TYPE_SCHED_CLS, debug=1)
+
+class Key(Structure):
+    _fields_ = [("dip", c_uint),
+                ("sip", c_uint)]
+class Leaf(Structure):
+    _fields_ = [("xdip", c_uint),
+                ("xsip", c_uint),
+                ("xlated_pkts", c_ulonglong)]
+
+prog.attach_filter(4, 10, 1)
+xlate = prog.table("xlate", Key, Leaf)
+xlate.put(Key(IPAddress("172.16.2.1").value, IPAddress("172.16.2.2").value),
+        Leaf(IPAddress("192.168.1.1").value, IPAddress("192.168.1.2").value, 0))
+while True:
+    print("==============================")
+    for key in xlate.iter():
+        leaf = xlate.get(key)
+        print(IPAddress(key.sip), "=>", IPAddress(key.dip),
+              "xlated_pkts", leaf.xlated_pkts)
+    time.sleep(1)