
#include "Python.h"

int main(int argc, char** argv);

static PyObject* run_model_checker(PyObject *self, PyObject *args) {
    Py_ssize_t tupleSize = PyTuple_Size(args);
    Py_ssize_t argc = tupleSize + 1;
    char **argv = malloc(argc * sizeof(char *));
    argv[0] = "charm";
    for (Py_ssize_t i = 0; i < tupleSize; ++i) {
        PyObject *a = PyTuple_GetItem(args, i);        
        char *s;
        if (!PyArg_Parse(a, "s", &s)) {
            return NULL;
        }
        argv[i + 1] = s;
    }
    PyObject *r = PyLong_FromLong(main(argc, argv));
    free(argv);
    return r;
}

static char module_docstring[] =
    "This module provides an interface for running the Harmony model checker.";
static char run_model_checker_sub_docstring[] =
    "Perform model check.";

static PyMethodDef module_methods[] = {
    {"run_model_checker", run_model_checker, METH_VARARGS, run_model_checker_sub_docstring},
    {NULL, NULL, 0, NULL}
};

static PyModuleDef mod_def = {
    PyModuleDef_HEAD_INIT,
    "charm",
    module_docstring,
    -1,
    module_methods,
};

PyObject* PyInit_charm(void)
{
    return PyModule_Create(&mod_def);
};
// Redefined in the rest of charm.c
#undef _GNU_SOURCE
#define _GNU_SOURCE
#define HARMONY_COMBINE
#ifdef _WIN32
#ifndef __MINGW32__
#define CHARM_WINDOWS
#endif
#endif

#ifdef _WIN32
#include <windows.h>
#endif

#ifdef CHARM_WINDOWS
typedef HANDLE *mutex_t;

typedef struct {
    CRITICAL_SECTION mutex;
    CONDITION_VARIABLE cond;
    unsigned int threads_required;
    unsigned int threads_left;
    unsigned int cycle;
} barrier_t;

#else // pthreads

#include <pthread.h>
#include <unistd.h>

#ifdef __APPLE__
#include <sys/param.h>
#include <sys/sysctl.h>
#endif

typedef pthread_mutex_t mutex_t;

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    unsigned int threads_required;
    unsigned int threads_left;
    unsigned int cycle;
} barrier_t;

#endif

#include <time.h>

void thread_create(void (*f)(void *arg), void *arg);
void mutex_init(mutex_t *mutex);
void mutex_acquire(mutex_t *mutex);
void mutex_release(mutex_t *mutex);
void mutex_destroy(mutex_t *mutex);
void barrier_init(barrier_t *barrier, unsigned int count);
void barrier_wait(barrier_t *barrier);
void barrier_destroy(barrier_t *barrier);
int getNumCores();
//
// Created by William Ma on 10/12/21.
//

#ifndef SRC_STACK_H
#define SRC_STACK_H

#include <assert.h>
#include <stdlib.h>

struct stack_t {
    void **arr;
    int len;
    int alloc_len;
};

struct stack_t *stack_init(int alloc_len) {
    assert(alloc_len > 0);

    struct stack_t *stack = malloc(sizeof(struct stack_t));
    stack->len = 0;
    stack->alloc_len = alloc_len;
    stack->arr = malloc(stack->alloc_len * sizeof(void *));
    return stack;
}

void stack_deinit(struct stack_t *stack) {
    free(stack->arr);
    free(stack);
}

void stack_push(struct stack_t *stack, void *elem) {
    stack->len++;
    if (stack->len > stack->alloc_len) {
        stack->alloc_len = 2 * stack->len;
        stack->arr = realloc(stack->arr, stack->alloc_len * sizeof(void *));
    }

    stack->arr[stack->len - 1] = elem;
}

void *stack_pop(struct stack_t *stack) {
    assert(stack->len > 0);
    stack->len--;
    return stack->arr[stack->len];
}

int stack_len(struct stack_t *stack) {
    return stack->len;
}

#endif //SRC_STACK_H
// Originally downloaded from https://github.com/exebook/hashdict.c

#ifndef HASHDICTC
#define HASHDICTC
#include <stdlib.h> /* malloc/calloc */
#include <stdint.h> /* uint32_t */
#include <string.h> /* memcpy/memcmp */

#define HASHDICT_VALUE_TYPE void*

typedef void (*enumFunc)(void *env, const void *key, unsigned int key_size,
                                HASHDICT_VALUE_TYPE value);

struct keynode {
	struct keynode *next;
	char *key;
	unsigned int len;
	HASHDICT_VALUE_TYPE value;
};

struct dict_bucket {
    struct keynode *stable;
    struct keynode *unstable;
    mutex_t lock;
	int count;
};
		
struct dict {
	struct dict_bucket *table;
	int length, count;
	double growth_treshold;
	double growth_factor;
    int concurrent;         // 0 = not concurrent
};

struct dict *dict_new(int initial_size);
void dict_delete(struct dict *dict);
void *dict_lookup(struct dict *dict, const void *key, unsigned int keylen);
void **dict_insert(struct dict *dict, const void *key, unsigned int keylen);
void *dict_find(struct dict *dict, const void *key, unsigned int keylen);
void *dict_retrieve(const void *p, int *psize);
void dict_iter(struct dict *dict, enumFunc f, void *user);
void dict_set_concurrent(struct dict *dict, int concurrent);
#endif
#ifndef SRC_HASHSET_H
#define SRC_HASHSET_H

#include <stdbool.h>
#include <assert.h>

#ifndef HARMONY_COMBINE
#include "hashdict.h"
#endif

struct hashset_t {
    struct dict *dict;  // a dict from values to DUMMY
};

struct hashset_t hashset_new(int initial_size);

// returns true iff key was in the set before
bool hashset_insert(struct hashset_t set, const void *key, unsigned int keylen);

// returns true iff key was in the set before
bool hashset_remove(struct hashset_t set, const void *key, unsigned int keylen);

bool hashset_contains(struct hashset_t set, const void *key, unsigned int keylen);

void hashset_delete(struct hashset_t set);

#endif //SRC_HASHSET_H
#ifndef JSON_H
#define JSON_H

typedef struct json_buf {
    char *base;
    unsigned int len;
} json_buf_t;

struct json_value { 
	enum { JV_ATOM, JV_MAP, JV_LIST } type;
	union {
		json_buf_t atom;
		struct dict *map;		// maps atoms to json_values
		struct {
			struct json_value **vals;
			unsigned int nvals;
		} list;
	} u;
};

struct json_value *json_parse_value(json_buf_t *buf);
struct json_value *json_string(char *s, unsigned int len);
void json_value_free(struct json_value *jv);
void json_dump(struct json_value *jv);
void json_list_append(struct json_value *list, struct json_value *jv);
void json_map_append(struct json_value *map, json_buf_t key, struct json_value *jv);
char *json_lookup_string(struct dict *map, char *key);
struct json_value *json_lookup_map(struct dict *map, char *key);
struct json_value *json_lookup_value(struct dict *map, char *key);
char *json_escape(const char *s, unsigned int len);

#endif /* JSON_H */
#ifndef _MINHEAP_H
#define _MINHEAP_H

#include <stdbool.h>

struct minheap *minheap_create();
void *minheap_getmin(struct minheap *);
void minheap_insert(struct minheap *, void *);
void minheap_decrease(struct minheap *, void *);
int  minheap_size(struct minheap *);
bool minheap_empty(struct minheap *mh);
void minheap_move(struct minheap *mh1, struct minheap *mh2);
void minheap_destroy(struct minheap *);
bool minheap_check(struct minheap *hm, void *key);

#endif
struct queue {
	struct element *first, **last;
	int nelts;
};

void queue_init(struct queue *q);
void queue_insert(struct queue *q, void *item);
void queue_append(struct queue *q, void *, char *file, int line);
unsigned int queue_size(struct queue *q);
void queue_add(struct queue *q, void *);
void queue_add_uint(struct queue *q, uint64_t);
void *queue_get(struct queue *q);
bool queue_tget(struct queue *q, void **item);
bool queue_get_uint(struct queue *q, uint64_t *);
bool queue_empty(struct queue *q);
void queue_release(struct queue *q);
#ifndef SRC_DOT_H
#define SRC_DOT_H

#include <stdio.h>
#include <stdbool.h>

struct dot_node_t {
    const char *name;   // null-terminated string
    bool terminating;
    bool initial;
    bool choosing;
    int choosing_atomic_level;
    int *fwd;           // forward edges
    int fwd_len;        // number forward edges
};

struct dot_graph_t {
    struct dot_node_t **nodes;
    int len;
    int _alloc_len;
};

struct dot_graph_t *dot_graph_init(int alloc_len);
void dot_graph_deinit(struct dot_graph_t *graph);
struct dot_node_t *dot_graph_new_node(struct dot_graph_t *graph);
void dot_graph_add_edge(struct dot_graph_t *graph, int from_idx, int to_idx);
void dot_graph_fprint(struct dot_graph_t *graph, FILE *f);

#endif //SRC_DOT_H
struct strbuf {
    char *buf;
    unsigned int len, allocated;
};

void strbuf_init(struct strbuf *sb);
void strbuf_append(struct strbuf *sb, const char *str, unsigned int len);
void strbuf_vprintf(struct strbuf *sb, const char *fmt, va_list args);
void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
char *strbuf_getstr(struct strbuf *sb);
unsigned int strbuf_getlen(struct strbuf *sb);
void strbuf_deinit(struct strbuf *sb);
char *strbuf_convert(struct strbuf *sb);
#include <stdint.h>
#include <stdbool.h>

#ifndef HARMONY_COMBINE
#include "hashdict.h"
#include "json.h"
#include "minheap.h"
#include "code.h"
#include "value.h"
#include "graph.h"
#endif

#define new_alloc(t)	(t *) calloc(1, sizeof(t))

#define CALLTYPE_PROCESS       1
#define CALLTYPE_NORMAL        2
#define CALLTYPE_INTERRUPT     3

//void *mcopy(void *p, unsigned int size);
//char *scopy(char *s);
//void mfree(void *p);

#define PRI_HVAL            PRIx64
typedef uint64_t            hvalue_t;

void panic(char *s);
unsigned long to_ulong(const char *p, int len);
double gettime();
#ifndef SRC_VALUE_H
#define SRC_VALUE_H

#include <stdbool.h>

struct state {
    hvalue_t vars;        // shared variables
    hvalue_t seqs;        // sequential variables
    hvalue_t choosing;    // context that is choosing if non-zero
    hvalue_t ctxbag;      // bag of running contexts
    hvalue_t stopbag;     // bag of stopped contexts
    hvalue_t termbag;     // bag of terminated contexts
    hvalue_t invariants;  // set of invariants that must hold
    hvalue_t dfa_state;
};

struct context {          // context value
    hvalue_t name;        // name of method
    hvalue_t entry;       // entry point of main method
    hvalue_t arg;         // argument provided to spawn
    hvalue_t this;        // thread-local state
    hvalue_t vars;        // method-local variables
    hvalue_t trap_pc;     // trap program counter
    hvalue_t trap_arg;    // trap argument
    hvalue_t failure;     // atom value describing failure, or 0 if no failure
    int pc;               // program counter
    int fp;               // frame pointer
    int readonly;         // readonly counter
    int atomic;           // atomic counter
    bool atomicFlag;      // to implement lazy atomicity
    bool interruptlevel;  // interrupt level
    bool stopped;         // context is stopped
    bool terminated;      // context has terminated
    bool eternal;         // context runs indefinitely
    int sp;               // stack size
    hvalue_t stack[0];    // growing stack
};

struct combined {           // combination of current state and current context
    struct state state;
    struct context context;
};

struct values_t {
    struct dict *atoms;
    struct dict *dicts;
    struct dict *sets;
    struct dict *addresses;
    struct dict *contexts;
};

void value_init(struct values_t *values);
void value_set_concurrent(struct values_t *values, int concurrent);
hvalue_t value_from_json(struct values_t *values, struct dict *map);
int value_cmp(hvalue_t v1, hvalue_t v2);
void *value_get(hvalue_t v, int *size);
void *value_copy(hvalue_t v, int *size);
hvalue_t value_put_atom(struct values_t *values, const void *p, int size);
hvalue_t value_put_set(struct values_t *values, void *p, int size);
hvalue_t value_put_dict(struct values_t *values, void *p, int size);
hvalue_t value_put_address(struct values_t *values, void *p, int size);
hvalue_t value_put_context(struct values_t *values, struct context *ctx);
char *value_string(hvalue_t v);
char *indices_string(const hvalue_t *vec, int size);
char *value_json(hvalue_t v);

void strbuf_value_string(struct strbuf *sb, hvalue_t v);
void strbuf_value_json(struct strbuf *sb, hvalue_t v);

#define VALUE_BITS      3
#define VALUE_MASK      ((hvalue_t) ((1 << VALUE_BITS) - 1))

#define VALUE_BOOL      0
#define VALUE_INT       1
#define VALUE_ATOM      2
#define VALUE_PC        3
#define VALUE_DICT      4
#define VALUE_SET       5
#define VALUE_ADDRESS   6
#define VALUE_CONTEXT   7

#define VALUE_FALSE     VALUE_BOOL
#define VALUE_TRUE      ((1 << VALUE_BITS) | VALUE_BOOL)

#define VALUE_MAX   ((int64_t) ((~(hvalue_t)0) >> (VALUE_BITS + 1)))
#define VALUE_MIN   ((int64_t) ((~(hvalue_t)0) << (64 - (VALUE_BITS + 1))))

hvalue_t value_dict_store(struct values_t *values, hvalue_t dict, hvalue_t key, hvalue_t value);
hvalue_t value_dict_load(hvalue_t dict, hvalue_t key);
bool value_dict_tryload(struct values_t *values, hvalue_t dict, hvalue_t key, hvalue_t *result);
hvalue_t value_dict_remove(struct values_t *values, hvalue_t dict, hvalue_t key);
hvalue_t value_bag_add(struct values_t *values, hvalue_t bag, hvalue_t v, int multiplicity);
void value_ctx_push(struct context **pctx, hvalue_t v);
hvalue_t value_ctx_pop(struct context **pctx);
hvalue_t value_ctx_failure(struct context *ctx, struct values_t *values, char *fmt, ...);
bool value_ctx_all_eternal(hvalue_t ctxbag);


#endif //SRC_VALUE_H
#ifndef SRC_CODE_H
#define SRC_CODE_H

#ifndef HARMONY_COMBINE
#include "json.h"
#include "value.h"
#endif

struct instr_t {
    struct op_info *oi;
    const void *env;
    bool choose, load, store, del, retop, print, breakable;
};

struct code_t {
    struct instr_t *instrs;
    int len;
    struct dict *code_map;       // maps pc to file:line
};

struct code_t code_init_parse(struct values_t *values, struct json_value *json_code);

#endif //SRC_CODE_H
#ifndef SRC_GRAPH_H
#define SRC_GRAPH_H

#include <stdint.h>

#ifndef HARMONY_COMBINE
#include "value.h"
#include "minheap.h"
#endif

struct component {
    bool good;              // terminating or out-going edge
    int size;               // #states
    struct node *rep;       // lowest numbered state in the component
    bool all_same;          // shared state in component is the same
    bool final;             // all states in this component are final
};

struct access_info {
    struct access_info *next; // linked list maintenance
    hvalue_t *indices;        // address of load/store
    int n;                    // length of address
    bool load;                // store or del if false
    int pc;                   // for debugging
    int multiplicity;         // #identical contexts
    int atomic;               // atomic counter
};

struct edge {
    struct edge *next;       // linked list maintenance
    hvalue_t ctx, choice;    // ctx that made the microstep, choice if any
    bool interrupt;          // set if state change is an interrupt
    struct node *node;       // resulting node (state)
    hvalue_t after;          // resulting context
    int weight;              // 1 if context switch; 0 otherwise
    struct access_info *ai;  // to detect data races
    hvalue_t *log;           // print history
    int nlog;                // size of print history
};

enum fail_type {
    FAIL_NONE,
    FAIL_SAFETY,
    FAIL_BEHAVIOR,
    FAIL_INVARIANT,
    FAIL_TERMINATION,
    FAIL_BUSYWAIT,
    FAIL_RACE
};

struct node {
    // Information about state
    struct state *state;    // state corresponding to this node
    int id;                 // nodes are numbered starting from 0
    struct edge *fwd;       // forward edges
    struct edge *bwd;       // backward edges
    enum fail_type ftype;    // failure if any

    // How to get here from parent node
    struct node *parent;    // shortest path to initial state
    int len;                // length of path to initial state
    int steps;              // #microsteps from root
    hvalue_t before;        // context before state change
    hvalue_t after;         // context after state change (current context)
    hvalue_t choice;        // choice made if any
    bool interrupt;         // set if gotten here by interrupt
    int weight;             // 1 if context switch; 0 otherwise
    struct access_info *ai; // to detect data races
    bool final;             // only eternal threads left
    hvalue_t *log;          // history
    int nlog;               // size of history

    // SCC
    bool visited;           // for Kosaraju algorithm
    unsigned int component; // strongly connected component id

    // NFA compression
    bool reachable;
};

struct failure {
    enum fail_type type;
    struct node *node;      // failed state
    struct node *parent;    // if NULL, use node->parent
    hvalue_t choice;        // choice if any
    bool interrupt;         // interrupt transition
    hvalue_t address;       // in case of data race
};

struct graph_t {
    struct node **nodes;         // vector of all nodes
    int size;                    // to create node identifiers
    int alloc_size;              // size allocated
};

void graph_init(struct graph_t *graph, int initial_size);

struct access_info *graph_ai_alloc(int multiplicity, int atomic, int pc);

void graph_check_for_data_race(
    struct node *node,
    struct minheap *warnings,
    struct values_t *values
);
void graph_add(struct graph_t *graph, struct node *node);
int graph_find_scc(struct graph_t *graph);

#endif //SRC_GRAPH_H
#ifndef SRC_CHARM_H
#define SRC_CHARM_H

#ifndef HARMONY_COMBINE
#include "minheap.h"
#include "code.h"
#include "value.h"
#include "graph.h"
#endif

#define CHUNKSIZE   (1 << 12)

struct global_t {
    struct code_t code;
    struct values_t values;
    struct graph_t graph;
    struct minheap *failures;    // queue of "struct failure"  (TODO: make part of struct node "issues")
    hvalue_t *processes;         // list of contexts of processes
    int nprocesses;              // the number of processes in the list
    double lasttime;             // since last report printed
    int enqueued;                // #states enqueued
    int dequeued;                // #states dequeued
    bool dumpfirst;              // for json dumping
    struct dfa *dfa;             // for tracking correct behaviors
};

#endif //SRC_CHARM_H
struct dfa *dfa_read(struct values_t *values, char *fname);
int dfa_initial(struct dfa *dfa);
bool dfa_is_final(struct dfa *dfa, int state);
int dfa_step(struct dfa *dfa, int current, hvalue_t symbol);
int dfa_ntransitions(struct dfa *dfa);
void dfa_check_trie(struct global_t *global);
#ifndef SRC_OPS_H
#define SRC_OPS_H

#include <inttypes.h>

#ifndef HARMONY_COMBINE
#include "charm.h"
#include "value.h"
#include "global.h"
#endif

void ops_init(struct global_t* global);
struct op_info *ops_get(char *opname, int size);

struct step {
    struct context *ctx;
    struct access_info *ai;
    hvalue_t *log;
    int nlog;
    struct dfa_trie *dfa_trie;
};

struct op_info {
    const char *name;
    void *(*init)(struct dict *, struct values_t *values);
    void (*op)(const void *env, struct state *state, struct step *step, struct global_t *global);
};

struct env_Cut {
    hvalue_t set;
    struct var_tree *key, *value;
};

struct env_Del {
    hvalue_t *indices;
    int n;
};

struct env_DelVar {
    hvalue_t name;
};

struct env_Frame {
    hvalue_t name;
    struct var_tree *args;
};

struct env_AtomicInc {
    bool lazy;
};

struct env_IncVar {
    hvalue_t name;
};

struct env_Invariant {
    int end;
};

struct env_Jump {
    int pc;
};

struct env_JumpCond {
    hvalue_t cond;
    int pc;
};

struct env_Load {
    hvalue_t *indices;
    int n;
};

struct env_LoadVar {
    hvalue_t name;
};

struct env_Move {
    int offset;
};

struct env_Nary {
    int arity;
    struct f_info *fi;
};

struct env_Push {
    hvalue_t value;
};

struct env_Spawn {
    bool eternal;
};

struct env_Split {
    int count;
};

struct env_Stop {
    hvalue_t *indices;
    int n;
};

struct env_Store {
    hvalue_t *indices;
    int n;
};

struct env_StoreVar {
    struct var_tree *args;
};

void interrupt_invoke(struct step *step);

#endif //SRC_OPS_H
#ifndef SRC_IFACE_H
#define SRC_IFACE_H

#ifndef HARMONY_COMBINE
#include "ops.h"
#include "charm.h"
#include "graph.h"
#include "dot.h"
#endif

void iface_write_spec_graph_to_file(struct global_t *global, const char* filename);
void iface_write_spec_graph_to_json_file(struct global_t *global, const char* filename);

#endif //SRC_IFACE_H
//
// Created by William Ma on 10/12/21.
//

#ifndef SRC_IFACE_GRAPH_H
#define SRC_IFACE_GRAPH_H

#include <stdint.h>
#include <stdbool.h>

#ifndef HARMONY_COMBINE
#include "value.h"
#endif

struct iface_node_t {
    int idx;
    struct node *node;

    uint64_t value;
    bool initial;
    bool terminated;
    bool choosing;

    struct state *state;

    struct iface_edge_t *fwd;
    struct iface_edge_t *bwd;

    int _tag;
};

struct iface_edge_t {
    struct iface_edge_t *next;

    /**
     * is_fwd iff this edge \in src.fwd
     * !is_fwd iff this edge \in dst.bwd
     */
    bool is_fwd;
    struct iface_node_t *src;
    struct iface_node_t *dst;
};

struct iface_graph_t {
    struct iface_node_t **nodes;
    int nodes_len;
    int _nodes_alloc_len;

    struct iface_edge_t **edges; // all edges, including fwd and bwd
    int edges_len;
    int _edges_alloc_len;
};

void iface_graph_print(struct iface_graph_t *graph);
struct iface_graph_t *iface_graph_init(int initial_size);
void iface_graph_deinit(struct iface_graph_t *graph);
struct iface_node_t *iface_graph_add_node(struct iface_graph_t *graph);
void iface_graph_add_edge(struct iface_graph_t *graph, int src_idx, int dst_idx);
struct iface_graph_t *iface_graph_destutter(struct iface_graph_t *graph);

#endif //SRC_IFACE_GRAPH_H
//
// Created by William Ma on 10/12/21.
//

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

#ifndef HARMONY_COMBINE
#include "iface_graph.h"
#include "hashset.h"
#include "stack.h"
#endif

void iface_graph_print(struct iface_graph_t *graph) {
    printf("graph: %p\n", (void*) graph);
    printf("nodes: %d alloc %d\n", graph->nodes_len, graph->_nodes_alloc_len);
    printf("edges: %d alloc %d\n", graph->edges_len, graph->_edges_alloc_len);

    for (int i = 0; i < graph->edges_len; i++) {
        printf("(%d, %d)\n", graph->edges[i]->src->idx, graph->edges[i]->dst->idx);
    }
}

static void iface_graph_check_invariants(struct iface_graph_t *graph) {
#ifndef NDEBUG
    assert(0 <= graph->nodes_len);
    assert(graph->nodes_len <= graph->_nodes_alloc_len);

    int num_edges = 0;
    for (int i = 0; i < graph->nodes_len; i++) {
        struct iface_node_t *node = graph->nodes[i];
        assert(node->idx == i);

        for (struct iface_edge_t *edge = node->fwd; edge != NULL; edge = edge->next) {
            assert(0 <= edge->dst->idx && edge->dst->idx < graph->nodes_len);
            num_edges += 1;
        }

        for (struct iface_edge_t *edge = node->bwd; edge != NULL; edge = edge->next) {
            assert(0 <= edge->dst->idx && edge->dst->idx < graph->nodes_len);
            num_edges += 1;
        }
    }

    assert(graph->edges_len == num_edges);
#endif
}

struct iface_graph_t *iface_graph_init(int initial_size) {
    assert(initial_size >= 1);

    struct iface_graph_t *graph = malloc(sizeof(struct iface_graph_t));

    graph->nodes_len = 0;
    graph->_nodes_alloc_len = initial_size;
    graph->nodes = malloc(graph->_nodes_alloc_len * sizeof(struct iface_node_t *));

    graph->edges_len = 0;
    graph->_edges_alloc_len = initial_size;
    graph->edges = malloc(graph->_edges_alloc_len * sizeof(struct iface_edge_t *));

    iface_graph_check_invariants(graph);
    return graph;
}

void iface_graph_deinit(struct iface_graph_t *graph) {
    iface_graph_check_invariants(graph);

    for (int i = 0; i < graph->nodes_len; i++) {
        free(graph->nodes[i]);
    }
    free(graph->nodes);

    for (int i = 0; i < graph->edges_len; i++) {
        free(graph->edges[i]);
    }
    free(graph->edges);

    free(graph);
}

struct iface_node_t *iface_graph_add_node(struct iface_graph_t *graph) {
    iface_graph_check_invariants(graph);

    graph->nodes_len++;
    if (graph->nodes_len > graph->_nodes_alloc_len) {
        graph->_nodes_alloc_len = 2 * graph->nodes_len;
        graph->nodes = realloc(graph->nodes, graph->_nodes_alloc_len * sizeof(struct iface_node_t *));
    }

    struct iface_node_t *node = malloc(sizeof(struct iface_node_t));
    node->value = 0;
    node->initial = false;
    node->terminated = false;
    node->choosing = false;
    node->state = NULL;
    node->fwd = NULL;
    node->bwd = NULL;
    node->idx = graph->nodes_len - 1;
    graph->nodes[node->idx] = node;

    iface_graph_check_invariants(graph);

    return graph->nodes[node->idx];
}

void iface_graph_add_edge(struct iface_graph_t *graph, int src_idx, int dst_idx) {
    iface_graph_check_invariants(graph);

    assert(0 <= src_idx && src_idx < graph->nodes_len);
    assert(0 <= dst_idx && dst_idx < graph->nodes_len);

    graph->edges_len += 2;
    if (graph->edges_len > graph->_edges_alloc_len) {
        graph->_edges_alloc_len = 2 * graph->edges_len;
        graph->edges = realloc(graph->edges, graph->_edges_alloc_len * sizeof(struct iface_edge_t *));
    }

    struct iface_edge_t *fwd_edge = malloc(sizeof(struct iface_edge_t));
    fwd_edge->src = graph->nodes[src_idx];
    fwd_edge->dst = graph->nodes[dst_idx];
    fwd_edge->next = graph->nodes[src_idx]->fwd;
    fwd_edge->is_fwd = true;
    graph->nodes[src_idx]->fwd = fwd_edge;
    graph->edges[graph->edges_len - 2] = fwd_edge;

    struct iface_edge_t *bwd_edge = malloc(sizeof(struct iface_edge_t));
    bwd_edge->src = graph->nodes[dst_idx];
    bwd_edge->dst = graph->nodes[src_idx];
    bwd_edge->next = graph->nodes[dst_idx]->bwd;
    bwd_edge->is_fwd = false;
    graph->nodes[dst_idx]->bwd = bwd_edge;
    graph->edges[graph->edges_len - 1] = bwd_edge;

    iface_graph_check_invariants(graph);
}

void iface_graph_add_edge_unique(struct iface_graph_t *graph, int src_idx, int dst_idx, bool is_fwd) {
    for (int i = 0; i < graph->edges_len; i++) {
        struct iface_edge_t *edge = graph->edges[i];
        if (edge->src->idx == src_idx && edge->dst->idx == dst_idx && edge->is_fwd == is_fwd) {
            return;
        }
    }

    iface_graph_add_edge(graph, src_idx, dst_idx);
}

bool iface_node_is_equal(struct iface_node_t *lhs, struct iface_node_t *rhs) {
    return lhs->value == rhs->value
        && lhs->initial == rhs->initial
        && lhs->terminated == rhs->terminated
        && lhs->choosing == rhs->choosing;
}

/**
 * Performs a DFS starting at `node_in_region` and tags all nodes equal to
 * `node_in_region` and touching `nodes_in_region` with `tag`.
 */
static void mark_region_with_tag(struct iface_node_t *node_in_region, const int tag) {
    struct hashset_t visited = hashset_new(0);
    struct stack_t *stack = stack_init(1);

    node_in_region->_tag = tag;
    stack_push(stack, node_in_region);

    while (stack_len(stack) > 0) {
        struct iface_node_t *node = stack_pop(stack);
        if (hashset_contains(visited, &node, sizeof(struct iface_node_t *))) {
            continue;
        }
        assert(iface_node_is_equal(node, node_in_region));

#ifdef true
        bool belongs_in_same_region = true;
        for (struct iface_edge_t *edge = node->fwd; edge != NULL; edge = edge->next) {
            if (!iface_node_is_equal(edge->dst, node_in_region)) {
                belongs_in_same_region = false;
                break;
            }
        }

        if (!belongs_in_same_region) {
            continue;
        }
#endif

        assert(node->_tag == -1 || node->_tag == tag);
        hashset_insert(visited, &node, sizeof(struct iface_node_t *));
        node->_tag = tag;

        for (struct iface_edge_t *edge = node->fwd; edge != NULL; edge = edge->next) {
            if (iface_node_is_equal(edge->dst, node_in_region)) {
                stack_push(stack, edge->dst);
            }
        }

        for (struct iface_edge_t *edge = node->bwd; edge != NULL; edge = edge->next) {
            if (iface_node_is_equal(edge->dst, node_in_region)) {
                stack_push(stack, edge->dst);
            }
        }
    }

    hashset_delete(visited);
    stack_deinit(stack);
}

struct iface_graph_t *iface_graph_destutter(struct iface_graph_t *graph) {
    iface_graph_check_invariants(graph);

    for (int i = 0; i < graph->nodes_len; i++) {
        graph->nodes[i]->_tag = -1;
    }

    int num_tags = 0;
    for (int i = 0; i < graph->nodes_len; i++) {
        struct iface_node_t *node = graph->nodes[i];
        if (node->_tag == -1) {
            mark_region_with_tag(node, num_tags);
            num_tags++;
        }
    }

    struct iface_graph_t *normalized = iface_graph_init(num_tags);
    for (int i = 0; i < num_tags; i++) {
        iface_graph_add_node(normalized);
    }

    for (int i = 0; i < graph->nodes_len; i++) {
        struct iface_node_t *n = graph->nodes[i];
        assert(0 <= n->_tag && n->_tag < num_tags);

        struct iface_node_t *node = normalized->nodes[n->_tag];

        node->value = n->value;
        node->initial = n->initial;
        node->terminated = n->terminated;
        node->choosing = n->choosing;
        node->state = n->state;
    }

    for (int i = 0; i < graph->edges_len; i++) {
        struct iface_edge_t *e = graph->edges[i];
        if (e->is_fwd && e->src->_tag != e->dst->_tag) {
            iface_graph_add_edge_unique(normalized, e->src->_tag, e->dst->_tag, e->is_fwd);
        }
    }

    iface_graph_check_invariants(normalized);

    return normalized;
}

#ifdef CHARM_COMBINE
#include "thread.h"
#endif

#ifdef CHARM_WINDOWS

void thread_create(void (*f)(void *arg), void *arg){
    (void) CreateThread( 
         NULL,         // default security attributes
         0,            // default stack size
         (LPTHREAD_START_ROUTINE) f, arg,
         0,            // default creation flags
         NULL); 	   // receive thread identifier
}

void mutex_init(mutex_t *mutex){
    *mutex = CreateMutex(NULL, FALSE, NULL);
    assert(*mutex != NULL);
}

void mutex_acquire(mutex_t *mutex){
    DWORD r = WaitForSingleObject(*mutex, INFINITE);
    assert(r == WAIT_OBJECT_0);
}

void mutex_release(mutex_t *mutex){
    ReleaseMutex(*mutex);
}

void mutex_destroy(mutex_t *mutex){
    CloseHandle(*mutex);
    *mutex = NULL;
}

void barrier_init(barrier_t *barrier, unsigned int count){
    barrier->threads_required = barrier->threads_left = count;
    barrier->cycle = 0;
    InitializeCriticalSection(&barrier->mutex);
    InitializeConditionVariable(&barrier->cond);
}

void barrier_wait(barrier_t *barrier){
    EnterCriticalSection(&barrier->mutex);

    if (--barrier->threads_left == 0) {
        barrier->cycle++;
        barrier->threads_left = barrier->threads_required;
        WakeAllConditionVariable(&barrier->cond);
    }
    else {
        unsigned int cycle = barrier->cycle;
        while (cycle == barrier->cycle)
            SleepConditionVariableCS(&barrier->cond,
                        &barrier->mutex, INFINITE);
    }

    LeaveCriticalSection(&barrier->mutex);
}

void barrier_destroy(barrier_t *barrier){
    DeleteCriticalSection(&barrier->mutex);
}

#else // pthreads

void thread_create(void (*f)(void *arg), void *arg){
    pthread_t tid;
    pthread_create(&tid, NULL, (void *(*)(void *)) f, arg);
}

void mutex_init(mutex_t *mutex){
    pthread_mutex_init(mutex, NULL);
}

void mutex_acquire(mutex_t *mutex){
    pthread_mutex_lock(mutex);
}

void mutex_release(mutex_t *mutex){
    pthread_mutex_unlock(mutex);
}

void mutex_destroy(mutex_t *mutex){
    pthread_mutex_destroy(mutex);
}

void barrier_init(barrier_t *barrier, unsigned int count){
    barrier->threads_required = barrier->threads_left = count;
    barrier->cycle = 0;
    pthread_mutex_init(&barrier->mutex, NULL);
    pthread_cond_init(&barrier->cond, NULL);
}

void barrier_wait(barrier_t *barrier){
    pthread_mutex_lock(&barrier->mutex);

    if (--barrier->threads_left == 0) {
        barrier->cycle++;
        barrier->threads_left = barrier->threads_required;
        pthread_cond_broadcast(&barrier->cond);
    }
    else {
        unsigned int cycle = barrier->cycle;
        while (cycle == barrier->cycle)
            pthread_cond_wait(&barrier->cond, &barrier->mutex);
    }
    pthread_mutex_unlock(&barrier->mutex);
}

void barrier_destroy(barrier_t *barrier){
    pthread_cond_destroy(&barrier->cond);
    pthread_mutex_destroy(&barrier->mutex);
}

#endif

int getNumCores(){
#ifdef _WIN32
    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);
    return sysinfo.dwNumberOfProcessors;
#elif defined(_SC_NPROCESSORS_ONLN)
    return sysconf(_SC_NPROCESSORS_ONLN);
#else
    int nm[2];
    size_t len = 4;
    uint32_t count;

    nm[0] = CTL_HW;
    nm[1] = HW_AVAILCPU;
    sysctl(nm, 2, &count, &len, NULL, 0);
    if (count < 1) {
        nm[1] = HW_NCPU;
        sysctl(nm, 2, &count, &len, NULL, 0);
        if (count < 1) {
            count = 1;
        }
    }
    return count;
#endif
}
struct dfa_transition {
    struct dfa_transition *next; // linked list maintenance
    hvalue_t symbol;             // transition symbol
    int dst;                     // destination state
};

struct dfa_state {
    struct dfa_state *next;      // linked list maintenance
    int idx;                     // name of state
    bool final;                  // terminal state
    struct dfa_transition *transitions;     // transition map

    // TODO.  Maybe should make transitions a dict
};

struct dfa {
    int nstates;
    int initial;
    struct dfa_state *states;
};

static int int_parse(char *p, int len){
    char *copy = malloc(len + 1);
    memcpy(copy, p, len);
    copy[len] = 0;
    int v = atoi(copy);
    free(copy);
    return v;
}

// read a DFA from a json file
struct dfa *dfa_read(struct values_t *values, char *fname){
    // open the HFA file
    FILE *fp = fopen(fname, "r");
    if (fp == NULL) {
        fprintf(stderr, "charm: can't open %s\n", fname);
        return NULL;
    }

    // read the entire file
    json_buf_t buf;
    buf.base = malloc(CHUNKSIZE);
    buf.len = 0;
    int n;
    while ((n = fread(&buf.base[buf.len], 1, CHUNKSIZE, fp)) > 0) {
        buf.len += n;
        buf.base = realloc(buf.base, buf.len + CHUNKSIZE);
    }
    fclose(fp);

    // parse the contents
    struct json_value *jv = json_parse_value(&buf);
    assert(jv->type == JV_MAP);

    struct dfa *dfa = new_alloc(struct dfa);

    // get the initial state
    struct json_value *initial = dict_lookup(jv->u.map, "initial", 7);
    assert(initial->type == JV_ATOM);
    dfa->initial = int_parse(initial->u.atom.base, initial->u.atom.len);
    // printf("INITIAL %d\n", dfa->initial);

    // read the list of states
    struct json_value *nodes = dict_lookup(jv->u.map, "nodes", 5);
    assert(nodes->type == JV_LIST);
    int max_idx = 0;
    struct dfa_state *states = NULL;
    for (unsigned int i = 0; i < nodes->u.list.nvals; i++) {
        struct json_value *node = nodes->u.list.vals[i];
        assert(node->type == JV_MAP);

        struct dfa_state *ds = new_alloc(struct dfa_state);

        struct json_value *idx = dict_lookup(node->u.map, "idx", 3);
        assert(idx->type == JV_ATOM);
        ds->idx = int_parse(idx->u.atom.base, idx->u.atom.len);
        if (ds->idx > max_idx) {
            max_idx = ds->idx;
        }

        struct json_value *type = dict_lookup(node->u.map, "type", 4);
        assert(type->type == JV_ATOM);
        assert(type->u.atom.len > 0);
        ds->final = type->u.atom.base[0] == 'f';

        // printf("IDX %d %d\n", ds->idx, ds->final);
        ds->next = states;
        states = ds;
    }

    dfa->nstates = max_idx + 1;
    dfa->states = calloc(sizeof(dfa->states[0]), dfa->nstates);
    for (int i = 0; i < dfa->nstates; i++) {
        dfa->states[i].idx = -1;            // some may not be used
    }
    struct dfa_state *ds;
    while ((ds = states) != NULL) {
        states = ds->next;
        dfa->states[ds->idx] = *ds;
    }

    // read the list of edges
    struct json_value *edges = dict_lookup(jv->u.map, "edges", 5);
    assert(edges->type == JV_LIST);
    for (unsigned int i = 0; i < edges->u.list.nvals; i++) {
        struct json_value *edge = edges->u.list.vals[i];
        assert(edge->type == JV_MAP);

        struct dfa_transition *dt = new_alloc(struct dfa_transition);

        struct json_value *src = dict_lookup(edge->u.map, "src", 3);
        assert(src->type == JV_ATOM);
        int src_state = int_parse(src->u.atom.base, src->u.atom.len);
        assert(src_state < dfa->nstates);

        struct json_value *dst = dict_lookup(edge->u.map, "dst", 3);
        assert(dst->type == JV_ATOM);
        dt->dst = int_parse(dst->u.atom.base, dst->u.atom.len);
        assert(dt->dst < dfa->nstates);

        struct json_value *symbol = dict_lookup(edge->u.map, "symbol", 6);
        assert(symbol->type == JV_MAP);
        dt->symbol = value_from_json(values, symbol->u.map);

        dt->next = dfa->states[src_state].transitions;
        dfa->states[src_state].transitions = dt;

        // printf("EDGE %d %d %s\n", src_state, dt->dst, value_string(dt->symbol));
    }

    json_value_free(jv);
    return dfa;
}

// get the initial state
int dfa_initial(struct dfa *dfa){
    return dfa->initial;
}

// check if the state is terminal
bool dfa_is_final(struct dfa *dfa, int state){
    return dfa->states[state].final;
}

// make a step.  Return -1 upon error.  Record transition in
// transitions if requested
int dfa_step(struct dfa *dfa, int current, hvalue_t symbol){
    struct dfa_state *ds = &dfa->states[current];

    // TODO.  Maybe make symbol lookup a hashmap
    for (struct dfa_transition *dt = ds->transitions; dt != NULL; dt = dt->next) {
        if (dt->symbol == symbol) {
            return dt->dst;
        }
    }
    return -1;
}
#ifdef WIN32
#include <windows.h>
#else
#include <sys/param.h>
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <assert.h>
#include <time.h>

#ifndef HARMONY_COMBINE
#include "global.h"
#include "thread.h"
#include "charm.h"
#include "ops.h"
#include "dot.h"
#include "strbuf.h"
#include "iface/iface.h"
#endif

// One of these per worker thread
struct worker {
    struct global_t *global;     // global state
    double timeout;
    barrier_t *start_barrier, *end_barrier;

    int index;                   // index of worker
    struct minheap *todo;        // set of states to evaluate
    int timecnt;                 // to reduce gettime() overhead
    struct step inv_step;        // for evaluating invariants

    struct minheap *results[2]; // out-queues for weights 0 and 1
};

bool invariant_check(struct global_t *global, struct state *state, struct step *step, int end){
    assert(step->ctx->sp == 0);
    assert(step->ctx->failure == 0);
    step->ctx->pc++;
    while (step->ctx->pc != end) {
        struct op_info *oi = global->code.instrs[step->ctx->pc].oi;
        int oldpc = step->ctx->pc;
        (*oi->op)(global->code.instrs[oldpc].env, state, step, global);
        if (step->ctx->failure != 0) {
            step->ctx->sp = 0;
            return false;
        }
        assert(step->ctx->pc != oldpc);
        assert(!step->ctx->terminated);
    }
    assert(step->ctx->sp == 1);
    step->ctx->sp = 0;
    assert(step->ctx->fp == 0);
    hvalue_t b = step->ctx->stack[0];
    assert((b & VALUE_MASK) == VALUE_BOOL);
    return b >> VALUE_BITS;
}

void check_invariants(struct worker *w, struct node *node, struct step *step){
    struct global_t *global = w->global;
    struct state *state = node->state;
    extern int invariant_cnt(const void *env);

    assert((state->invariants & VALUE_MASK) == VALUE_SET);
    assert(step->ctx->sp == 0);
    int size;
    hvalue_t *vals = value_get(state->invariants, &size);
    size /= sizeof(hvalue_t);
    for (int i = 0; i < size; i++) {
        assert((vals[i] & VALUE_MASK) == VALUE_PC);
        step->ctx->pc = vals[i] >> VALUE_BITS;
        assert(strcmp(global->code.instrs[step->ctx->pc].oi->name, "Invariant") == 0);
        int end = invariant_cnt(global->code.instrs[step->ctx->pc].env);
        bool b = invariant_check(global, state, step, end);
        if (step->ctx->failure != 0) {
            printf("Invariant failed: %s\n", value_string(step->ctx->failure));
            b = false;
        }
        if (!b) {
            node->ftype = FAIL_INVARIANT;
            break;
        }
    }
}

static bool onestep(
    struct worker *w,       // thread info
    struct node *node,      // starting node
    struct state *sc,       // actual state
    hvalue_t ctx,           // context identifier
    struct step *step,      // step info
    hvalue_t choice,        // if about to make a choice, which choice?
    bool interrupt,         // start with invoking interrupt handler
    bool infloop_detect,    // try to detect infloop from the start
    int multiplicity        // #contexts that are in the current state
) {
    assert(!step->ctx->terminated);
    assert(step->ctx->failure == 0);

    struct global_t *global = w->global;

    // See if we should also try an interrupt.
    if (interrupt) {
		assert(step->ctx->trap_pc != 0);
        interrupt_invoke(step);
    }

    // Copy the choice
    hvalue_t choice_copy = choice;

    bool choosing = false, infinite_loop = false;
    struct dict *infloop = NULL;        // infinite loop detector
    int loopcnt = 0;
    for (;; loopcnt++) {
        int pc = step->ctx->pc;

        // If I'm phread 0 and it's time, print some stats
        if (w->index == 0 && w->timecnt-- == 0) {
            double now = gettime();
            if (now - global->lasttime > 1) {
                if (global->lasttime != 0) {
                    char *p = value_string(step->ctx->name);
                    fprintf(stderr, "%s pc=%d diameter=%d states=%d queue=%d\n",
                            p, step->ctx->pc, node->len, global->enqueued, global->enqueued - global->dequeued);
                    free(p);
                }
                global->lasttime = now;
                if (now > w->timeout) {
                    fprintf(stderr, "charm: timeout exceeded\n");
                    exit(1);
                }
            }
            w->timecnt = 100;
        }

        struct instr_t *instrs = global->code.instrs;
        struct op_info *oi = instrs[pc].oi;
        if (instrs[pc].choose) {
            assert(step->ctx->sp > 0);
            step->ctx->stack[step->ctx->sp - 1] = choice;
            step->ctx->pc++;
        }
        else {
            if (instrs[pc].load || instrs[pc].store || instrs[pc].del) {
                step->ai = graph_ai_alloc(multiplicity, step->ctx->atomic, pc);
            }
            (*oi->op)(instrs[pc].env, sc, step, global);
        }
		assert(step->ctx->pc >= 0);
		assert(step->ctx->pc < global->code.len);

        if (!step->ctx->terminated && step->ctx->failure == 0 && (infloop_detect || loopcnt > 1000)) {
            if (infloop == NULL) {
                infloop = dict_new(0);
            }

            int stacksize = step->ctx->sp * sizeof(hvalue_t);
            int combosize = sizeof(struct combined) + stacksize;
            struct combined *combo = calloc(1, combosize);
            combo->state = *sc;
            memcpy(&combo->context, step->ctx, sizeof(*step->ctx) + stacksize);
            void **p = dict_insert(infloop, combo, combosize);
            free(combo);
            if (*p == (void *) 0) {
                *p = (void *) 1;
            }
            else if (infloop_detect) {
                step->ctx->failure = value_put_atom(&global->values, "infinite loop", 13);
                infinite_loop = true;
            }
            else {
                // start over, as twostep does not have loopcnt optimization
                return false;
            }
        }

        if (step->ctx->terminated || step->ctx->failure != 0 || step->ctx->stopped) {
            break;
        }
        if (step->ctx->pc == pc) {
            fprintf(stderr, ">>> %s\n", oi->name);
        }
        assert(step->ctx->pc != pc);
		assert(step->ctx->pc >= 0);
		assert(step->ctx->pc < global->code.len);

        /* Peek at the next instruction.
         */
        oi = global->code.instrs[step->ctx->pc].oi;
        if (global->code.instrs[step->ctx->pc].choose) {
            assert(step->ctx->sp > 0);
#ifdef TODO
            if (0 && step->ctx->readonly > 0) {    // TODO
                value_ctx_failure(step->ctx, &global->values, "can't choose in assertion or invariant");
                break;
            }
#endif
            hvalue_t s = step->ctx->stack[step->ctx->sp - 1];
            if ((s & VALUE_MASK) != VALUE_SET) {
                value_ctx_failure(step->ctx, &global->values, "choose operation requires a set");
                break;
            }
            int size;
            hvalue_t *vals = value_get(s, &size);
            size /= sizeof(hvalue_t);
            if (size == 0) {
                value_ctx_failure(step->ctx, &global->values, "choose operation requires a non-empty set");
                break;
            }
            if (size == 1) {            // TODO.  This optimization is probably not worth it
                choice = vals[0];
            }
            else {
                choosing = true;
                break;
            }
        }

        // See if we need to break out of this step.  If the atomicFlag is
        // set, then definitely not.  If it is not set, then it gets
        // complicated.  If the atomic count > 0, then we may have delayed
        // breaking until strictly necessary (lazy atomic), in the hopes
        // of not having to at all (because breaking causes an expensive
        // context switch).  If the instruction is not "breakable" (Load,
        // Store, Del, Print, eager AtomicInc), then there's no need to
        // break yet.  Otherwise, if the atomic count > 0, we should set
        // the atomicFlag and break.  Otherwise  if it's a breakable
        // instruction, we should just break.
        if (!step->ctx->atomicFlag) {
            struct instr_t *next_instr = &global->code.instrs[step->ctx->pc];
            bool breakable = next_instr->breakable;
            if (next_instr->retop && step->ctx->trap_pc != 0 &&
                                            !step->ctx->interruptlevel) {
                hvalue_t ct = step->ctx->stack[step->ctx->sp - 4];
                assert((ct & VALUE_MASK) == VALUE_INT);
                if ((ct >> VALUE_BITS) == CALLTYPE_PROCESS) {
                    breakable = true;
                }
            }
            if (breakable) {
                if (step->ctx->atomic > 0) {
                    step->ctx->atomicFlag = true;
                }
                break;
            }
        }
    }

    if (infloop != NULL) {
        dict_delete(infloop);
    }

    // Remove old context from the bag
    hvalue_t count = value_dict_load(sc->ctxbag, ctx);
    assert((count & VALUE_MASK) == VALUE_INT);
    count -= 1 << VALUE_BITS;
    if (count == VALUE_INT) {
        sc->ctxbag = value_dict_remove(&global->values, sc->ctxbag, ctx);
    }
    else {
        sc->ctxbag = value_dict_store(&global->values, sc->ctxbag, ctx, count);
    }

    // Store new context in value directory.  Must be immutable now.
    hvalue_t after = value_put_context(&global->values, step->ctx);

    // If choosing, save in state
    if (choosing) {
        assert(!step->ctx->terminated);
        sc->choosing = after;
    }

    // Add new context to state unless it's terminated or stopped
    if (step->ctx->stopped) {
        sc->stopbag = value_bag_add(&global->values, sc->stopbag, after, 1);
    }
    else if (!step->ctx->terminated) {
        sc->ctxbag = value_bag_add(&global->values, sc->ctxbag, after, 1);
    }

    // Weight of this step
    int weight = ctx == node->after ? 0 : 1;

    struct node *next = new_alloc(struct node);
    next->parent = node;
    next->state = sc;
    next->before = ctx;
    next->choice = choice_copy;
    next->interrupt = interrupt;
    next->after = after;
    next->len = node->len + weight;
    next->steps = node->steps + loopcnt;
    next->weight = weight;

    next->ai = step->ai;
    step->ai = NULL;
    next->log = step->log;
    next->nlog = step->nlog;
    step->log = NULL;
    step->nlog = 0;

    if (step->ctx->failure != 0) {
        next->ftype = infinite_loop ? FAIL_TERMINATION : FAIL_SAFETY;
    }
    else if (sc->choosing == 0 && sc->invariants != VALUE_SET) {
        check_invariants(w, next, &w->inv_step);
    }

    minheap_insert(w->results[weight], next);
    return true;
}

static void make_step(
    struct worker *w,
    struct node *node,
    hvalue_t ctx,
    hvalue_t choice,       // if about to make a choice, which choice?
    int multiplicity       // #contexts that are in the current state
) {
    struct step step;
    memset(&step, 0, sizeof(step));

    // Make a copy of the state
    struct state *sc = new_alloc(struct state);
    memcpy(sc, node->state, sizeof(*sc));

    // Make a copy of the context
    step.ctx = value_copy(ctx, NULL);

    // See if we need to interrupt
    if (sc->choosing == 0 && step.ctx->trap_pc != 0 && !step.ctx->interruptlevel) {
        bool succ = onestep(w, node, sc, ctx, &step, choice, true, false, multiplicity);
        if (!succ) {        // ran into an infinite loop
            (void) onestep(w, node, sc, ctx, &step, choice, true, true, multiplicity);
        }

        // Allocate another state
        sc = new_alloc(struct state);
        memcpy(sc, node->state, sizeof(*sc));
        free(step.ctx);
        step.ctx = value_copy(ctx, NULL);
    }

    sc->choosing = 0;
    bool succ = onestep(w, node, sc, ctx, &step, choice, false, false, multiplicity);
    if (!succ) {        // ran into an infinite loop
        (void) onestep(w, node, sc, ctx, &step, choice, false, true, multiplicity);
    }

    free(step.ctx);
}

void print_vars(FILE *file, hvalue_t v){
    assert((v & VALUE_MASK) == VALUE_DICT);
    int size;
    hvalue_t *vars = value_get(v, &size);
    size /= sizeof(hvalue_t);
    fprintf(file, "{");
    for (int i = 0; i < size; i += 2) {
        if (i > 0) {
            fprintf(file, ",");
        }
        char *k = value_string(vars[i]);
		int len = strlen(k);
        char *v = value_json(vars[i+1]);
        fprintf(file, " \"%.*s\": %s", len - 2, k + 1, v);
        free(k);
        free(v);
    }
    fprintf(file, " }");
}

char *json_escape_value(hvalue_t v){
    char *s = value_string(v);
    char *r = json_escape(s, strlen(s));
    free(s);
    return r;
}

bool print_trace(
    struct global_t *global,
    FILE *file,
    struct context *ctx,
    int pc,
    int fp,
    hvalue_t vars
) {
    if (fp == 0) {
        return false;
    }
    assert(fp >= 4);

	int level = 0, orig_pc = pc;
    if (strcmp(global->code.instrs[pc].oi->name, "Frame") == 0) {
        hvalue_t ct = ctx->stack[ctx->sp - 2];
        assert((ct & VALUE_MASK) == VALUE_INT);
        switch (ct >> VALUE_BITS) {
        case CALLTYPE_PROCESS:
            pc++;
            break;
        case CALLTYPE_INTERRUPT:
        case CALLTYPE_NORMAL:
            {
                hvalue_t retaddr = ctx->stack[ctx->sp - 3];
                assert((retaddr & VALUE_MASK) == VALUE_PC);
                pc = retaddr >> VALUE_BITS;
            }
            break;
        default:
            fprintf(stderr, "call type: %"PRI_HVAL" %d %d %d\n", ct, ctx->sp, ctx->fp, ctx->pc);
            // panic("print_trace: bad call type 1");
        }
    }
    while (--pc >= 0) {
        const char *name = global->code.instrs[pc].oi->name;

        if (strcmp(name, "Return") == 0) {
			level++;
		}
        else if (strcmp(name, "Frame") == 0) {
			if (level == 0) {
				if (fp >= 5) {
                    assert((ctx->stack[fp - 5] & VALUE_MASK) == VALUE_PC);
					int npc = ctx->stack[fp - 5] >> VALUE_BITS;
					hvalue_t nvars = ctx->stack[fp - 2];
					int nfp = ctx->stack[fp - 1] >> VALUE_BITS;
					if (print_trace(global, file, ctx, npc, nfp, nvars)) {
                        fprintf(file, ",\n");
                    }
				}
				fprintf(file, "            {\n");
				fprintf(file, "              \"pc\": \"%d\",\n", orig_pc);
				fprintf(file, "              \"xpc\": \"%d\",\n", pc);

				const struct env_Frame *ef = global->code.instrs[pc].env;
				char *s = value_string(ef->name), *a = NULL;
				int len = strlen(s);
                a = json_escape_value(ctx->stack[fp - 3]);
				if (*a == '(') {
					fprintf(file, "              \"method\": \"%.*s%s\",\n", len - 2, s + 1, a);
				}
				else {
					fprintf(file, "              \"method\": \"%.*s(%s)\",\n", len - 2, s + 1, a);
				}

                hvalue_t ct = ctx->stack[fp - 4];
                assert((ct & VALUE_MASK) == VALUE_INT);
                switch (ct >> VALUE_BITS) {
                case CALLTYPE_PROCESS:
                    fprintf(file, "              \"calltype\": \"process\",\n");
                    break;
                case CALLTYPE_NORMAL:
                    fprintf(file, "              \"calltype\": \"normal\",\n");
                    break;
                case CALLTYPE_INTERRUPT:
                    fprintf(file, "              \"calltype\": \"interrupt\",\n");
                    break;
                default:
                    panic("print_trace: bad call type 2");
                }

				free(s);
				free(a);
				fprintf(file, "              \"vars\": ");
				print_vars(file, vars);
				fprintf(file, "\n");
				fprintf(file, "            }");
				return true;
			}
            else {
                assert(level > 0);
                level--;
            }
        }
    }
    return false;
}

char *ctx_status(struct node *node, hvalue_t ctx) {
    if (node->state->choosing == ctx) {
        return "choosing";
    }
    while (node->state->choosing != 0) {
        node = node->parent;
    }
    struct edge *edge;
    for (edge = node->fwd; edge != NULL; edge = edge->next) {
        if (edge->ctx == ctx) {
            break;
        }
    };
    if (edge != NULL && edge->node == node) {
        return "blocked";
    }
    return "runnable";
}

void print_context(
    struct global_t *global,
    FILE *file,
    hvalue_t ctx,
    int tid,
    struct node *node
) {
    char *s, *a;

    fprintf(file, "        {\n");
    fprintf(file, "          \"tid\": \"%d\",\n", tid);
    fprintf(file, "          \"yhash\": \"%"PRI_HVAL"\",\n", ctx);

    struct context *c = value_get(ctx, NULL);

    s = value_string(c->name);
	int len = strlen(s);
    a = json_escape_value(c->arg);
    if (*a == '(') {
        fprintf(file, "          \"name\": \"%.*s%s\",\n", len - 2, s + 1, a);
    }
    else {
        fprintf(file, "          \"name\": \"%.*s(%s)\",\n", len - 2, s + 1, a);
    }
    free(s);
    free(a);

    // assert((c->entry & VALUE_MASK) == VALUE_PC);   TODO
    fprintf(file, "          \"entry\": \"%d\",\n", (int) (c->entry >> VALUE_BITS));

    fprintf(file, "          \"pc\": \"%d\",\n", c->pc);
    fprintf(file, "          \"fp\": \"%d\",\n", c->fp);

#ifdef notdef
    {
        fprintf(file, "STACK %d:\n", c->fp);
        for (int x = 0; x < c->sp; x++) {
            fprintf(file, "    %d: %s\n", x, value_string(c->stack[x]));
        }
    }
#endif

    fprintf(file, "          \"trace\": [\n");
    print_trace(global, file, c, c->pc, c->fp, c->vars);
    fprintf(file, "\n");
    fprintf(file, "          ],\n");

    if (c->failure != 0) {
        s = value_string(c->failure);
        fprintf(file, "          \"failure\": %s,\n", s);
        free(s);
    }

    if (c->trap_pc != 0) {
        s = value_string(c->trap_pc);
        a = value_string(c->trap_arg);
        if (*a == '(') {
            fprintf(file, "          \"trap\": \"%s%s\",\n", s, a);
        }
        else {
            fprintf(file, "          \"trap\": \"%s(%s)\",\n", s, a);
        }
        free(s);
    }

    if (c->interruptlevel) {
        fprintf(file, "          \"interruptlevel\": \"1\",\n");
    }

    if (c->atomic != 0) {
        fprintf(file, "          \"atomic\": \"%d\",\n", c->atomic);
    }
    if (c->readonly != 0) {
        fprintf(file, "          \"readonly\": \"%d\",\n", c->readonly);
    }

    if (c->terminated) {
        fprintf(file, "          \"mode\": \"terminated\",\n");
    }
    else if (c->failure != 0) {
        fprintf(file, "          \"mode\": \"failed\",\n");
    }
    else if (c->stopped) {
        fprintf(file, "          \"mode\": \"stopped\",\n");
    }
    else {
        fprintf(file, "          \"mode\": \"%s\",\n", ctx_status(node, ctx));
    }

#ifdef notdef
    fprintf(file, "          \"stack\": [\n");
    for (int i = 0; i < c->sp; i++) {
        s = value_string(c->stack[i]);
        if (i < c->sp - 1) {
            fprintf(file, "            \"%s\",\n", s);
        }
        else {
            fprintf(file, "            \"%s\"\n", s);
        }
        free(s);
    }
    fprintf(file, "          ],\n");
#endif

    s = value_json(c->this);
    fprintf(file, "          \"this\": %s\n", s);
    free(s);

    fprintf(file, "        }");
}

void print_state(
    struct global_t *global,
    FILE *file,
    struct node *node
) {

#ifdef notdef
    fprintf(file, "      \"shared\": ");
    print_vars(file, node->state->vars);
    fprintf(file, ",\n");
#endif

    struct state *state = node->state;
    extern int invariant_cnt(const void *env);
    struct step inv_step;
    memset(&inv_step, 0, sizeof(inv_step));
    inv_step.ctx = new_alloc(struct context);

    // hvalue_t inv_nv = value_put_atom("name", 4);
    // hvalue_t inv_tv = value_put_atom("tag", 3);
    inv_step.ctx->name = value_put_atom(&global->values, "__invariant__", 13);
    inv_step.ctx->arg = VALUE_DICT;
    inv_step.ctx->this = VALUE_DICT;
    inv_step.ctx->vars = VALUE_DICT;
    inv_step.ctx->atomic = inv_step.ctx->readonly = 1;
    inv_step.ctx->interruptlevel = false;

    fprintf(file, "      \"invfails\": [");
    assert((state->invariants & VALUE_MASK) == VALUE_SET);
    int size;
    hvalue_t *vals = value_get(state->invariants, &size);
    size /= sizeof(hvalue_t);
    int nfailures = 0;
    for (int i = 0; i < size; i++) {
        assert((vals[i] & VALUE_MASK) == VALUE_PC);
        inv_step.ctx->pc = vals[i] >> VALUE_BITS;
        assert(strcmp(global->code.instrs[inv_step.ctx->pc].oi->name, "Invariant") == 0);
        int end = invariant_cnt(global->code.instrs[inv_step.ctx->pc].env);
        bool b = invariant_check(global, state, &inv_step, end);
        if (inv_step.ctx->failure != 0) {
            b = false;
        }
        if (!b) {
            if (nfailures != 0) {
                fprintf(file, ",");
            }
            fprintf(file, "\n        {\n");
            fprintf(file, "          \"pc\": \"%u\",\n", (unsigned int) (vals[i] >> VALUE_BITS));
            if (inv_step.ctx->failure == 0) {
                fprintf(file, "          \"reason\": \"invariant violated\"\n");
            }
            else {
                char *val = value_string(inv_step.ctx->failure);
				int len = strlen(val);
                fprintf(file, "          \"reason\": \"%.*s\"\n", len - 2, val + 1);
                free(val);
            }
            nfailures++;
            fprintf(file, "        }");
        }
    }
    fprintf(file, "\n      ],\n");
    free(inv_step.ctx);

    fprintf(file, "      \"contexts\": [\n");
    for (int i = 0; i < global->nprocesses; i++) {
        print_context(global, file, global->processes[i], i, node);
        if (i < global->nprocesses - 1) {
            fprintf(file, ",");
        }
        fprintf(file, "\n");
    }
    fprintf(file, "      ]\n");
}

void diff_state(
    struct global_t *global,
    FILE *file,
    struct state *oldstate,
    struct state *newstate,
    struct context *oldctx,
    struct context *newctx,
    bool interrupt,
    bool choose,
    hvalue_t choice,
    char *print
) {
    if (global->dumpfirst) {
        global->dumpfirst = false;
    }
    else {
        fprintf(file, ",");
    }
    fprintf(file, "\n        {\n");
    if (newstate->vars != oldstate->vars) {
        fprintf(file, "          \"shared\": ");
        print_vars(file, newstate->vars);
        fprintf(file, ",\n");
    }
    if (interrupt) {
        fprintf(file, "          \"interrupt\": \"True\",\n");
    }
    if (choose) {
        char *val = value_json(choice);
        fprintf(file, "          \"choose\": %s,\n", val);
        free(val);
    }
    if (print != NULL) {
        fprintf(file, "          \"print\": %s,\n", print);
    }
    fprintf(file, "          \"npc\": \"%d\",\n", newctx->pc);
    if (newctx->fp != oldctx->fp) {
        fprintf(file, "          \"fp\": \"%d\",\n", newctx->fp);
        fprintf(file, "          \"trace\": [\n");
        print_trace(global, file, newctx, newctx->pc, newctx->fp, newctx->vars);
        fprintf(file, "\n");
        fprintf(file, "          ],\n");
    }
    if (newctx->this != oldctx->this) {
        char *val = value_json(newctx->this);
        fprintf(file, "          \"this\": %s,\n", val);
        free(val);
    }
    if (newctx->vars != oldctx->vars) {
        fprintf(file, "          \"local\": ");
        print_vars(file, newctx->vars);
        fprintf(file, ",\n");
    }
    if (newctx->atomic != oldctx->atomic) {
        fprintf(file, "          \"atomic\": \"%d\",\n", newctx->atomic);
    }
    if (newctx->readonly != oldctx->readonly) {
        fprintf(file, "          \"readonly\": \"%d\",\n", newctx->readonly);
    }
    if (newctx->interruptlevel != oldctx->interruptlevel) {
        fprintf(file, "          \"interruptlevel\": \"%d\",\n", newctx->interruptlevel ? 1 : 0);
    }
    if (newctx->failure != 0) {
        char *val = value_string(newctx->failure);
        fprintf(file, "          \"failure\": %s,\n", val);
        fprintf(file, "          \"mode\": \"failed\",\n");
        free(val);
    }
    else if (newctx->terminated) {
        fprintf(file, "          \"mode\": \"terminated\",\n");
    }

    int common;
    for (common = 0; common < newctx->sp && common < oldctx->sp; common++) {
        if (newctx->stack[common] != oldctx->stack[common]) {
            break;
        }
    }
    if (common < oldctx->sp) {
        fprintf(file, "          \"pop\": \"%d\",\n", oldctx->sp - common);
    }
    fprintf(file, "          \"push\": [");
    for (int i = common; i < newctx->sp; i++) {
        if (i > common) {
            fprintf(file, ",");
        }
        char *val = value_json(newctx->stack[i]);
        fprintf(file, " %s", val);
        free(val);
    }
    fprintf(file, " ],\n");

    fprintf(file, "          \"pc\": \"%d\"\n", oldctx->pc);

    fprintf(file, "        }");
}

void diff_dump(
    struct global_t *global,
    FILE *file,
    struct state *oldstate,
    struct state *newstate,
    struct context **oldctx,
    struct context *newctx,
    bool interrupt,
    bool choose,
    hvalue_t choice,
    char *print
) {
    int newsize = sizeof(*newctx) + (newctx->sp * sizeof(hvalue_t));

    if (memcmp(oldstate, newstate, sizeof(struct state)) == 0 &&
            (*oldctx)->sp == newctx->sp &&
            memcmp(*oldctx, newctx, newsize) == 0) {
        return;
    }

    // Keep track of old state and context for taking diffs
    diff_state(global, file, oldstate, newstate, *oldctx, newctx, interrupt, choose, choice, print);
    *oldstate = *newstate;
    free(*oldctx);
    *oldctx = malloc(newsize);
    memcpy(*oldctx, newctx, newsize);
}

// similar to onestep.  TODO.  Use flag to onestep?
hvalue_t twostep(
    struct global_t *global,
    FILE *file,
    struct node *node,
    hvalue_t ctx,
    hvalue_t choice,
    bool interrupt,
    struct state *oldstate,
    struct context **oldctx,
    hvalue_t nextvars
){
    // Make a copy of the state
    struct state *sc = new_alloc(struct state);
    memcpy(sc, node->state, sizeof(*sc));
    sc->choosing = 0;

    struct step step;
    memset(&step, 0, sizeof(step));
    step.ctx = value_copy(ctx, NULL);
    if (step.ctx->terminated || step.ctx->failure != 0) {
        free(step.ctx);
        return ctx;
    }

    if (interrupt) {
		assert(step.ctx->trap_pc != 0);
        interrupt_invoke(&step);
        diff_dump(global, file, oldstate, sc, oldctx, step.ctx, true, false, 0, NULL);
    }

    struct dict *infloop = NULL;        // infinite loop detector
    for (int loopcnt = 0;; loopcnt++) {
        int pc = step.ctx->pc;

        char *print = NULL;
        struct instr_t *instrs = global->code.instrs;
        struct op_info *oi = instrs[pc].oi;
        if (instrs[pc].choose) {
            step.ctx->stack[step.ctx->sp - 1] = choice;
            step.ctx->pc++;
        }
        else if (instrs[pc].print) {
            print = value_json(step.ctx->stack[step.ctx->sp - 1]);
            (*oi->op)(instrs[pc].env, sc, &step, global);
        }
        else {
            (*oi->op)(instrs[pc].env, sc, &step, global);
        }

        // Infinite loop detection
        if (!step.ctx->terminated && step.ctx->failure == 0) {
            if (infloop == NULL) {
                infloop = dict_new(0);
            }

            int stacksize = step.ctx->sp * sizeof(hvalue_t);
            int combosize = sizeof(struct combined) + stacksize;
            struct combined *combo = calloc(1, combosize);
            combo->state = *sc;
            memcpy(&combo->context, step.ctx, sizeof(*step.ctx) + stacksize);
            void **p = dict_insert(infloop, combo, combosize);
            free(combo);
            if (*p == (void *) 0) {
                *p = (void *) 1;
            }
            else {
                step.ctx->failure = value_put_atom(&global->values, "infinite loop", 13);
            }
        }

        diff_dump(global, file, oldstate, sc, oldctx, step.ctx, false, global->code.instrs[pc].choose, choice, print);
        free(print);
        if (step.ctx->terminated || step.ctx->failure != 0 || step.ctx->stopped) {
            break;
        }
        if (step.ctx->pc == pc) {
            fprintf(stderr, ">>> %s\n", oi->name);
        }
        assert(step.ctx->pc != pc);

        /* Peek at the next instruction.
         */
        oi = global->code.instrs[step.ctx->pc].oi;
        if (global->code.instrs[step.ctx->pc].choose) {
            assert(step.ctx->sp > 0);
#ifdef TODO
            if (0 && step.ctx->readonly > 0) {    // TODO
                value_ctx_failure(step.ctx, &global->values, "can't choose in assertion or invariant");
                diff_dump(global, file, oldstate, sc, oldctx, step.ctx, false, global->code.instrs[pc].choose, choice, NULL);
                break;
            }
#endif
            hvalue_t s = step.ctx->stack[step.ctx->sp - 1];
            if ((s & VALUE_MASK) != VALUE_SET) {
                value_ctx_failure(step.ctx, &global->values, "choose operation requires a set");
                diff_dump(global, file, oldstate, sc, oldctx, step.ctx, false, global->code.instrs[pc].choose, choice, NULL);
                break;
            }
            int size;
            hvalue_t *vals = value_get(s, &size);
            size /= sizeof(hvalue_t);
            if (size == 0) {
                value_ctx_failure(step.ctx, &global->values, "choose operation requires a non-empty set");
                diff_dump(global, file, oldstate, sc, oldctx, step.ctx, false, global->code.instrs[pc].choose, choice, NULL);
                break;
            }
            if (size == 1) {
                choice = vals[0];
            }
            else {
                break;
            }
        }

        if (!step.ctx->atomicFlag) {
            struct instr_t *next_instr = &global->code.instrs[step.ctx->pc];
            bool breakable = next_instr->breakable;
            if (next_instr->retop && step.ctx->trap_pc != 0 &&
                                            !step.ctx->interruptlevel) {
                hvalue_t ct = step.ctx->stack[step.ctx->sp - 4];
                assert((ct & VALUE_MASK) == VALUE_INT);
                if ((ct >> VALUE_BITS) == CALLTYPE_PROCESS) {
                    breakable = true;
                }
            }
            if (breakable) {
                if (step.ctx->atomic > 0) {
                    step.ctx->atomicFlag = true;
                }
                break;
            }
        }
    }

    // Remove old context from the bag
    hvalue_t count = value_dict_load(sc->ctxbag, ctx);
    assert((count & VALUE_MASK) == VALUE_INT);
    count -= 1 << VALUE_BITS;
    if (count == VALUE_INT) {
        sc->ctxbag = value_dict_remove(&global->values, sc->ctxbag, ctx);
    }
    else {
        sc->ctxbag = value_dict_store(&global->values, sc->ctxbag, ctx, count);
    }

    hvalue_t after = value_put_context(&global->values, step.ctx);

    // Add new context to state unless it's terminated or stopped
    if (step.ctx->stopped) {
        sc->stopbag = value_bag_add(&global->values, sc->stopbag, after, 1);
    }
    else if (!step.ctx->terminated) {
        sc->ctxbag = value_bag_add(&global->values, sc->ctxbag, after, 1);
    }

    // assert(sc->vars == nextvars);
    ctx = value_put_context(&global->values, step.ctx);

    free(sc);
    free(step.ctx);
    free(step.log);

    return ctx;
}

void path_dump(
    struct global_t *global,
    FILE *file,
    struct node *last,
    struct node *parent,
    hvalue_t choice,
    struct state *oldstate,
    struct context **oldctx,
    bool interrupt
) {
    struct node *node = last;

    last = parent == NULL ? last->parent : parent;
    if (last->parent == NULL) {
        fprintf(file, "\n");
    }
    else {
        path_dump(global, file, last, last->parent, last->choice, oldstate, oldctx, last->interrupt);
        fprintf(file, ",\n");
    }

    fprintf(file, "    {\n");
    fprintf(file, "      \"id\": \"%d\",\n", node->id);

    /* Find the starting context in the list of processes.
     */
    hvalue_t ctx = node->before;
    int pid;
    for (pid = 0; pid < global->nprocesses; pid++) {
        if (global->processes[pid] == ctx) {
            break;
        }
    }

    struct context *context = value_get(ctx, NULL);
    assert(!context->terminated);
    char *name = value_string(context->name);
	int len = strlen(name);
    char *arg = json_escape_value(context->arg);
    // char *c = value_string(choice);
    fprintf(file, "      \"tid\": \"%d\",\n", pid);
    fprintf(file, "      \"xhash\": \"%"PRI_HVAL"\",\n", ctx);
    if (*arg == '(') {
        fprintf(file, "      \"name\": \"%.*s%s\",\n", len - 2, name + 1, arg);
    }
    else {
        fprintf(file, "      \"name\": \"%.*s(%s)\",\n", len - 2, name + 1, arg);
    }
    // fprintf(file, "      \"choice\": \"%s\",\n", c);
    global->dumpfirst = true;
    fprintf(file, "      \"microsteps\": [");
    free(name);
    free(arg);
    // free(c);
    memset(*oldctx, 0, sizeof(**oldctx));
    (*oldctx)->pc = context->pc;

    // Recreate the steps
    assert(pid < global->nprocesses);
    global->processes[pid] = twostep(
        global,
        file,
        last,
        ctx,
        choice,
        interrupt,
        oldstate,
        oldctx,
        node->state->vars
    );
    fprintf(file, "\n      ],\n");

    /* Match each context to a process.
     */
    bool *matched = calloc(global->nprocesses, sizeof(bool));
    int nctxs;
    hvalue_t *ctxs = value_get(node->state->ctxbag, &nctxs);
    nctxs /= sizeof(hvalue_t);
    for (int i = 0; i < nctxs; i += 2) {
        assert((ctxs[i] & VALUE_MASK) == VALUE_CONTEXT);
        assert((ctxs[i+1] & VALUE_MASK) == VALUE_INT);
        int cnt = ctxs[i+1] >> VALUE_BITS;
        for (int j = 0; j < cnt; j++) {
            int k;
            for (k = 0; k < global->nprocesses; k++) {
                if (!matched[k] && global->processes[k] == ctxs[i]) {
                    matched[k] = true;
                    break;
                }
            }
            if (k == global->nprocesses) {
                global->processes = realloc(global->processes, (global->nprocesses + 1) * sizeof(hvalue_t));
                matched = realloc(matched, (global->nprocesses + 1) * sizeof(bool));
                global->processes[global->nprocesses] = ctxs[i];
                matched[global->nprocesses] = true;
                global->nprocesses++;
            }
        }
    }
    free(matched);
  
    print_state(global, file, node);
    fprintf(file, "    }");
}

static char *json_string_encode(char *s, int len){
    char *result = malloc(4 * len), *p = result;

    while (len > 0) {
        switch (*s) {
        case '\r':
            *p++ = '\\'; *p++ = 'r';
            break;
        case '\n':
            *p++ = '\\'; *p++ = 'n';
            break;
        case '\f':
            *p++ = '\\'; *p++ = 'f';
            break;
        case '\t':
            *p++ = '\\'; *p++ = 't';
            break;
        case '"':
            *p++ = '\\'; *p++ = '"';
            break;
        case '\\':
            *p++ = '\\'; *p++ = '\\';
            break;
        default:
            *p++ = *s;
        }
        s++;
        len--;
    }
    *p++ = 0;
    return result;
}

struct enum_loc_env_t {
    FILE *out;
    struct dict *code_map;
};

static void enum_loc(
    void *env,
    const void *key,
    unsigned int key_size,
    HASHDICT_VALUE_TYPE value
) {
    static bool notfirst = false;
    struct enum_loc_env_t *enum_loc_env = env;
    FILE *out = enum_loc_env->out;
    struct dict *code_map = enum_loc_env->code_map;

    if (notfirst) {
        fprintf(out, ",\n");
    }
    else {
        notfirst = true;
        fprintf(out, "\n");
    }

    // Get program counter
    char *pcc = malloc(key_size + 1);
    memcpy(pcc, key, key_size);
    pcc[key_size] = 0;
    int pc = atoi(pcc);
    free(pcc);

    fprintf(out, "    \"%.*s\": { ", key_size, (char *) key);

    struct json_value *jv = value;
    assert(jv->type == JV_MAP);

    struct json_value *file = dict_lookup(jv->u.map, "file", 4);
    assert(file->type == JV_ATOM);
    fprintf(out, "\"file\": \"%s\", ", json_string_encode(file->u.atom.base, file->u.atom.len));

    struct json_value *line = dict_lookup(jv->u.map, "line", 4);
    assert(line->type == JV_ATOM);
    fprintf(out, "\"line\": \"%.*s\", ", line->u.atom.len, line->u.atom.base);

    void **p = dict_insert(code_map, &pc, sizeof(pc));
    struct strbuf sb;
    strbuf_init(&sb);
    strbuf_printf(&sb, "%.*s:%.*s", file->u.atom.len, file->u.atom.base, line->u.atom.len, line->u.atom.base);
    *p = strbuf_convert(&sb);

    struct json_value *code = dict_lookup(jv->u.map, "code", 4);
    assert(code->type == JV_ATOM);
    fprintf(out, "\"code\": \"%s\"", json_string_encode(code->u.atom.base, code->u.atom.len));
    fprintf(out, " }");
}

enum busywait { BW_ESCAPE, BW_RETURN, BW_VISITED };
static enum busywait is_stuck(
    struct node *start,
    struct node *node,
    hvalue_t ctx,
    bool change
) {
	if (node->component != start->component) {
		return BW_ESCAPE;
	}
	if (node->visited) {
		return BW_VISITED;
	}
    change = change || (node->state->vars != start->state->vars);
	node->visited = true;
	enum busywait result = BW_ESCAPE;
    for (struct edge *edge = node->fwd; edge != NULL; edge = edge->next) {
        if (edge->ctx == ctx) {
			if (edge->node == node) {
				node->visited = false;
				return BW_ESCAPE;
			}
			if (edge->node == start) {
				if (!change) {
					node->visited = false;
					return BW_ESCAPE;
				}
				result = BW_RETURN;
			}
			else {
				enum busywait bw = is_stuck(start, edge->node, edge->after, change);
				switch (bw) {
				case BW_ESCAPE:
					node->visited = false;
					return BW_ESCAPE;
				case BW_RETURN:
					result = BW_RETURN;
					break;
				case BW_VISITED:
					break;
				default:
					assert(false);
				}
			}
        }
    }
	node->visited = false;
    return result;
}

static void detect_busywait(struct minheap *failures, struct node *node){
	// Get the contexts
	int size;
	hvalue_t *ctxs = value_get(node->state->ctxbag, &size);
	size /= sizeof(hvalue_t);

	for (int i = 0; i < size; i += 2) {
		if (is_stuck(node, node, ctxs[i], false) == BW_RETURN) {
			struct failure *f = new_alloc(struct failure);
			f->type = FAIL_BUSYWAIT;
			f->choice = node->choice;
			f->node = node;
			minheap_insert(failures, f);
			break;
		}
	}
}

static int node_cmp(void *n1, void *n2){
    struct node *node1 = n1, *node2 = n2;

    if (node1->len != node2->len) {
        return node1->len - node2->len;
    }
    if (node1->steps != node2->steps) {
        return node1->steps - node2->steps;
    }
    return node1->id - node2->id;
}

static int fail_cmp(void *f1, void *f2){
    struct failure *fail1 = f1, *fail2 = f2;

    return node_cmp(fail1->node, fail2->node);
}

static void do_work(struct worker *w){
	while (!minheap_empty(w->todo)) {
		struct node *node = minheap_getmin(w->todo);
		struct state *state = node->state;
		w->global->dequeued++; // TODO race condition

		if (state->choosing != 0) {
			assert((state->choosing & VALUE_MASK) == VALUE_CONTEXT);

			struct context *cc = value_get(state->choosing, NULL);
			assert(cc != NULL);
			assert(cc->sp > 0);
			hvalue_t s = cc->stack[cc->sp - 1];
			assert((s & VALUE_MASK) == VALUE_SET);
			int size;
			hvalue_t *vals = value_get(s, &size);
			size /= sizeof(hvalue_t);
			assert(size > 0);
			for (int i = 0; i < size; i++) {
				make_step(
					w,
					node,
					state->choosing,
					vals[i],
					1
				);
			}
		}
		else {
			int size;
			hvalue_t *ctxs = value_get(state->ctxbag, &size);
			size /= sizeof(hvalue_t);
			assert(size >= 0);
			for (int i = 0; i < size; i += 2) {
				assert((ctxs[i] & VALUE_MASK) == VALUE_CONTEXT);
				assert((ctxs[i+1] & VALUE_MASK) == VALUE_INT);
				make_step(
					w,
					node,
					ctxs[i],
					0,
					ctxs[i+1] >> VALUE_BITS
				);
			}
		}
	}
}

static void worker(void *arg){
    struct worker *w = arg;

    for (int epoch = 0;; epoch++) {
        barrier_wait(w->start_barrier);
		// printf("WORKER %d starting\n", w->index);
		do_work(w);
		// printf("WORKER %d finished\n", w->index);
        barrier_wait(w->end_barrier);
    }
}

void process_results(
    struct global_t *global,
    struct dict *visited,
    struct minheap *todo[2],
    struct minheap *results
) {
    while (!minheap_empty(results)) {
        struct node *node = minheap_getmin(results);
        struct node *parent = node->parent, *next;
        bool must_free;     // whether node must be freed or not

        /* See if we already visited this node.
         */
        void **p = dict_insert(visited, node->state, sizeof(struct state));
        if ((next = *p) == NULL) {
            next = *p = node;
            graph_add(&global->graph, node);   // sets node->id
            assert(node->id != 0);
            minheap_insert(todo[0], node);
            global->enqueued++;
            must_free = false;
        }
        else {
            next = *p;
            must_free = true;
        }

        if (node->ftype != FAIL_NONE) {
            struct failure *f = new_alloc(struct failure);
            f->type = node->ftype;
            f->choice = node->choice;
            f->interrupt = node->interrupt;
            f->parent = node->parent;
            f->node = next;
            minheap_insert(global->failures, f);
        }

        // Add a forward edge from parent
        struct edge *fwd = new_alloc(struct edge);
        fwd->ctx = node->before;
        fwd->choice = node->choice;
        fwd->interrupt = node->interrupt;
        fwd->node = next;
        fwd->weight = node->weight;
        fwd->after = node->after;
        fwd->ai = node->ai;
        fwd->log = node->log;
        fwd->nlog = node->nlog;
        fwd->next = parent->fwd;
        parent->fwd = fwd;

        // Add a backward edge from node to parent.
        struct edge *bwd = new_alloc(struct edge);
        bwd->ctx = node->before;
        bwd->choice = node->choice;
        bwd->interrupt = node->interrupt;
        bwd->node = parent;
        bwd->weight = node->weight;
        bwd->after = node->after;
        bwd->ai = node->ai;
        bwd->log = node->log;
        bwd->nlog = node->nlog;
        bwd->next = next->bwd;
        next->bwd = bwd;

        if (must_free) {
            free(node);
        }
    }
}

char *state_string(struct state *state){
    struct strbuf sb;
    strbuf_init(&sb);

    char *v;
    strbuf_printf(&sb, "{");
    v = value_string(state->vars);
    strbuf_printf(&sb, "%s", v); free(v);
    v = value_string(state->seqs);
    strbuf_printf(&sb, ",%s", v); free(v);
    v = value_string(state->choosing);
    strbuf_printf(&sb, ",%s", v); free(v);
    v = value_string(state->ctxbag);
    strbuf_printf(&sb, ",%s", v); free(v);
    v = value_string(state->stopbag);
    strbuf_printf(&sb, ",%s", v); free(v);
    v = value_string(state->termbag);
    strbuf_printf(&sb, ",%s", v); free(v);
    v = value_string(state->invariants);
    strbuf_printf(&sb, ",%s}", v); free(v);
    return strbuf_convert(&sb);
}

// This routine removes all node that have a single incoming edge and it's
// an "epsilon" edge (empty print log).  These are essentially useless nodes.
// Typically about half of the nodes can be removed this way.
static void destutter1(struct graph_t *graph){
    for (int i = 0; i < graph->size; i++) {
        struct node *n = graph->nodes[i];

        if (n->bwd != NULL && n->bwd->next == NULL && n->bwd->nlog == 0) {
            struct node *parent = n->bwd->node;

            if (n->final) {
                parent->final = true;
            }

            // Remove the edge from the parent
            struct edge **pe, *e;
            for (pe = &parent->fwd; (e = *pe) != NULL; pe = &e->next) {
                if (e->node == n && e->nlog == 0) {
                    *pe = e->next;
                    free(e);
                    break;
                }
            }

            struct edge *next;
            for (struct edge *e = n->fwd; e != NULL; e = next) {
                // Move the outgoing edge to the parent.
                next = e->next;
                e->next = parent->fwd;
                parent->fwd = e;

                // Fix the corresponding backwards edge
                for (struct edge *f = e->node->bwd; f != NULL; f = f->next) {
                    if (f->node == n && f->nlog == e->nlog &&
                            memcmp(f->log, e->log, f->nlog * sizeof(*f->log)) == 0) {
                        f->node = parent;
                        break;
                    }
                }
            }
            n->reachable = false;
        }
        else {
            n->reachable = true;
        }
    }
}

static struct dict *collect_symbols(struct graph_t *graph){
    struct dict *symbols = dict_new(0);
    hvalue_t symbol_id = 0;

    for (int i = 0; i < graph->size; i++) {
        struct node *n = graph->nodes[i];
        if (!n->reachable) {
            continue;
        }
        for (struct edge *e = n->fwd; e != NULL; e = e->next) {
            for (int j = 0; j < e->nlog; j++) {
                void **p = dict_insert(symbols, &e->log[j], sizeof(e->log[j]));
                if (*p == NULL) {
                    *p = (void *) ++symbol_id;
                }
            }
        }
    }
    return symbols;
}

struct symbol_env {
    FILE *out;
    bool first;
};

static void print_symbol(void *env, const void *key, unsigned int key_size, void *value){
    struct symbol_env *se = env;
    const hvalue_t *symbol = key;

    assert(key_size == sizeof(*symbol));
    char *p = value_json(*symbol);
    if (se->first) {
        se->first = false;
    }
    else {
        fprintf(se->out, ",\n");
    }
    fprintf(se->out, "     \"%"PRIu64"\": %s", (uint64_t) value, p);
    free(p);
}

struct print_trans_env {
    FILE *out;
    bool first;
    struct dict *symbols;
};

static void print_trans_upcall(void *env, const void *key, unsigned int key_size, void *value){
    struct print_trans_env *pte = env;
    const hvalue_t *log = key;
    unsigned int nkeys = key_size / sizeof(hvalue_t);
    struct strbuf *sb = value;

    if (pte->first) {
        pte->first = false;
    }
    else {
        fprintf(pte->out, ",\n");
    }
    fprintf(pte->out, "        [[");
    for (unsigned int i = 0; i < nkeys; i++) {
        void *p = dict_lookup(pte->symbols, &log[i], sizeof(log[i]));
        assert(p != NULL);
        if (i != 0) {
            fprintf(pte->out, ",");
        }
        fprintf(pte->out, "%"PRIu64, (uint64_t) p);
    }
    fprintf(pte->out, "],[%s]]", strbuf_getstr(sb));
    strbuf_deinit(sb);
    free(sb);
}

static void print_transitions(FILE *out, struct dict *symbols, struct edge *edges){
    struct dict *d = dict_new(0);

    fprintf(out, "      \"transitions\": [\n");
    for (struct edge *e = edges; e != NULL; e = e->next) {
        void **p = dict_insert(d, e->log, e->nlog * sizeof(*e->log));
        struct strbuf *sb = *p;
        if (sb == NULL) {
            *p = sb = malloc(sizeof(struct strbuf));
            strbuf_init(sb);
            strbuf_printf(sb, "%d", e->node->id);
        }
        else {
            strbuf_printf(sb, ",%d", e->node->id);
        }
    }
    struct print_trans_env pte = {
        .out = out, .first = true, .symbols = symbols
    };
    dict_iter(d, print_trans_upcall, &pte);
    fprintf(out, "\n");
    fprintf(out, "      ],\n");
    dict_delete(d);
}

#ifdef OBSOLETE
static void pr_state(struct global_t *global, FILE *fp, struct state *state, int index){
    char *v = state_string(state);
    fprintf(fp, "%s\n", v);
    free(v);
}
#endif

static void usage(char *prog){
    fprintf(stderr, "Usage: %s [-c] [-t<maxtime>] [-B<dfafile>] -o<outfile> file.json\n", prog);
    exit(1);
}

int main(int argc, char **argv){
    bool cflag = false;
    int i, maxtime = 300000000 /* about 10 years */;
    char *outfile = NULL, *dfafile = NULL;
    for (i = 1; i < argc; i++) {
        if (*argv[i] != '-') {
            break;
        }
        switch (argv[i][1]) {
        case 'c':
            cflag = true;
            break;
        case 't':
            maxtime = atoi(&argv[i][2]);
            if (maxtime <= 0) {
                fprintf(stderr, "%s: negative timeout\n", argv[0]);
                exit(1);
            }
            break;
        case 'B':
            dfafile = &argv[i][2];
            break;
        case 'o':
            outfile = &argv[i][2];
            break;
        case 'x':
            printf("Charm model checker working\n");
            return 0;
        default:
            usage(argv[0]);
        }
    }
    if (argc - i != 1 || outfile == NULL) {
        usage(argv[0]);
    }
    char *fname = argv[i];
    double timeout = gettime() + maxtime;

    // initialize modules
    struct global_t *global = new_alloc(struct global_t);
    value_init(&global->values);
    ops_init(global);
    graph_init(&global->graph, 1024);
    global->failures = minheap_create(fail_cmp);
    global->processes = NULL;
    global->nprocesses = 0;
    global->lasttime = 0;
    global->enqueued = 0;
    global->dequeued = 0;
    global->dumpfirst = false;

    // First read and parse the DFA if any
    if (dfafile != NULL) {
        global->dfa = dfa_read(&global->values, dfafile);
        if (global->dfa == NULL) {
            exit(1);
        }
    }

    // open the HVM file
    FILE *fp = fopen(fname, "r");
    if (fp == NULL) {
        fprintf(stderr, "%s: can't open %s\n", argv[0], fname);
        exit(1);
    }

    // read the file
    json_buf_t buf;
    buf.base = malloc(CHUNKSIZE);
    buf.len = 0;
    int n;
    while ((n = fread(&buf.base[buf.len], 1, CHUNKSIZE, fp)) > 0) {
        buf.len += n;
        buf.base = realloc(buf.base, buf.len + CHUNKSIZE);
    }
    fclose(fp);

    // parse the contents
    struct json_value *jv = json_parse_value(&buf);
    assert(jv->type == JV_MAP);

    // travel through the json code contents to create the code array
    struct json_value *jc = dict_lookup(jv->u.map, "code", 4);
    assert(jc->type == JV_LIST);
    global->code = code_init_parse(&global->values, jc);

    // Create an initial state
    struct context *init_ctx = new_alloc(struct context);
    init_ctx->name = value_put_atom(&global->values, "__init__", 8);
    init_ctx->arg = VALUE_DICT;
    init_ctx->this = VALUE_DICT;
    init_ctx->vars = VALUE_DICT;
    init_ctx->atomic = 1;
    init_ctx->atomicFlag = true;
    value_ctx_push(&init_ctx, (CALLTYPE_PROCESS << VALUE_BITS) | VALUE_INT);
    value_ctx_push(&init_ctx, VALUE_DICT);
    struct state *state = new_alloc(struct state);
    state->vars = VALUE_DICT;
    state->seqs = VALUE_SET;
    hvalue_t ictx = value_put_context(&global->values, init_ctx);
    state->ctxbag = value_dict_store(&global->values, VALUE_DICT, ictx, (1 << VALUE_BITS) | VALUE_INT);
    state->stopbag = VALUE_DICT;
    state->invariants = VALUE_SET;
    state->dfa_state = global->dfa == NULL ? 0 : dfa_initial(global->dfa);
    global->processes = new_alloc(hvalue_t);
    *global->processes = ictx;
    global->nprocesses = 1;

    // Put the initial state in the visited map
    struct dict *visited = dict_new(0);
    struct node *node = new_alloc(struct node);
    node->state = state;
    node->after = ictx;
    graph_add(&global->graph, node);
    void **p = dict_insert(visited, state, sizeof(*state));
    assert(*p == NULL);
    *p = node;

    struct minheap *todo[2];
    todo[0] = minheap_create(node_cmp);
    todo[1] = minheap_create(node_cmp);

    struct minheap *results[2];
    results[0] = minheap_create(node_cmp);
    results[1] = minheap_create(node_cmp);

    // Determine how many worker threads to use
    int nworkers = getNumCores();
	printf("nworkers = %d\n", nworkers);
    barrier_t start_barrier, end_barrier;
    barrier_init(&start_barrier, nworkers + 1);
    barrier_init(&end_barrier, nworkers + 1);

    // Allocate space for worker info
    struct worker *workers = calloc(nworkers, sizeof(*workers));
    for (int i = 0; i < nworkers; i++) {
        struct worker *w = &workers[i];
        w->global = global;
        w->timeout = timeout;
        w->start_barrier = &start_barrier;
        w->end_barrier = &end_barrier;
        w->index = i;
        w->todo = minheap_create(node_cmp);
        w->results[0] = minheap_create(node_cmp);
        w->results[1] = minheap_create(node_cmp);

        // Create a context for evaluating invariants
        w->inv_step.ctx = new_alloc(struct context);
        w->inv_step.ctx->name = value_put_atom(&global->values, "__invariant__", 13);
        w->inv_step.ctx->arg = VALUE_DICT;
        w->inv_step.ctx->this = VALUE_DICT;
        w->inv_step.ctx->vars = VALUE_DICT;
        w->inv_step.ctx->atomic = w->inv_step.ctx->readonly = 1;
        w->inv_step.ctx->interruptlevel = false;
    }

    // Start the workers, who'll wait on the start barrier
    for (int i = 0; i < nworkers; i++) {
        thread_create(worker, &workers[i]);
    }

    // Give the initial state to worker 0
    minheap_insert(workers[0].todo, node);
    global->enqueued++;

    double before = gettime(), postproc = 0;
    while (minheap_empty(global->failures)) {
        // Put the value dictionaries in concurrent mode
        value_set_concurrent(&global->values, 1);

        // make the threads work
        barrier_wait(&start_barrier);
        barrier_wait(&end_barrier);

        double before_postproc = gettime();

        // Deal with the unstable values
        value_set_concurrent(&global->values, 0);

        // Collect the results of all the workers
        for (int i = 0; i < nworkers; i++) {
            struct worker *w = &workers[i];
            assert(minheap_empty(w->todo));

            for (int weight = 0; weight <= 1; weight++) {
                while (!minheap_empty(w->results[weight])) {
                    struct node *node = minheap_getmin(w->results[weight]);
                    minheap_insert(results[weight], node);
                }
            }
        }

        process_results(global, visited, todo, results[0]);

        if (minheap_empty(todo[0])) {
            struct minheap *tmp = results[0];
            results[0] = results[1];
            results[1] = tmp;
            process_results(global, visited, todo, results[0]);
        }

#ifdef XXX
        if (!minheap_empty(global->failures)) {
            break;
        }
#endif

        if (minheap_empty(todo[0])) {
            break;
        }

        // Distribute the work evenly among the workers
        assert(!minheap_empty(todo[0]));
        int distr = 0;
        while (!minheap_empty(todo[0])) {
            struct node *node = minheap_getmin(todo[0]);
            minheap_insert(workers[distr].todo, node);
            if (++distr == nworkers) {
                distr = 0;
            }
        }

        postproc += gettime() - before_postproc;
    }

    printf("#states %d (time %.3lf+%.3lf=%.3lf)\n", global->graph.size, gettime() - before - postproc, postproc, gettime() - before);

    printf("Phase 3: analysis\n");
    if (minheap_empty(global->failures)) {
        // find the strongly connected components
        int ncomponents = graph_find_scc(&global->graph);
        printf("%d components\n", ncomponents);

        // mark the components that are "good" because they have a way out
        struct component *components = calloc(ncomponents, sizeof(*components));
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
			assert(node->component < ncomponents);
            struct component *comp = &components[node->component];
            if (comp->size == 0) {
                comp->rep = node;
                comp->all_same = value_ctx_all_eternal(node->state->ctxbag)
                    && value_ctx_all_eternal(node->state->stopbag);
            }
            else if (node->state->vars != comp->rep->state->vars ||
                        !value_ctx_all_eternal(node->state->ctxbag) ||
                        !value_ctx_all_eternal(node->state->stopbag)) {
                comp->all_same = false;
            }
            comp->size++;
            if (comp->good) {
                continue;
            }
            // if this component has a way out, it is good
            for (struct edge *edge = node->fwd;
                            edge != NULL && !comp->good; edge = edge->next) {
                if (edge->node->component != node->component) {
                    comp->good = true;
                    break;
                }
            }
        }

        // components that have only one shared state and only eternal
        // threads are good because it means all its threads are blocked
        for (int i = 0; i < ncomponents; i++) {
            struct component *comp = &components[i];
            assert(comp->size > 0);
            if (!comp->good && comp->all_same) {
                comp->good = true;
                comp->final = true;
            }
        }

        // Look for states in final components
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
			assert(node->component < ncomponents);
            struct component *comp = &components[node->component];
            if (comp->final) {
                node->final = true;
                if (global->dfa != NULL &&
						!dfa_is_final(global->dfa, node->state->dfa_state)) {
                    struct failure *f = new_alloc(struct failure);
                    f->type = FAIL_BEHAVIOR;
                    f->choice = node->choice;
                    f->node = node;
                    minheap_insert(global->failures, f);
                    break;
                }
            }
        }

        if (minheap_empty(global->failures)) {
            // now count the nodes that are in bad components
            int nbad = 0;
            for (int i = 0; i < global->graph.size; i++) {
                struct node *node = global->graph.nodes[i];
                if (!components[node->component].good) {
                    nbad++;
                    struct failure *f = new_alloc(struct failure);
                    f->type = FAIL_TERMINATION;
                    f->choice = node->choice;
                    f->node = node;
                    minheap_insert(global->failures, f);
                    break;
                }
            }

            if (nbad == 0 && !cflag) {
                for (int i = 0; i < global->graph.size; i++) {
                    global->graph.nodes[i]->visited = false;
                }
                for (int i = 0; i < global->graph.size; i++) {
                    struct node *node = global->graph.nodes[i];
                    if (components[node->component].size > 1) {
                        detect_busywait(global->failures, node);
                    }
                }
            }
        }
    }

#ifdef OBSOLETE
    if (false) {
        FILE *df = fopen("charm.dump", "w");
        assert(df != NULL);
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
            assert(node->id == i);
            fprintf(df, "\nNode %d:\n", node->id);
            fprintf(df, "    component: %d\n", node->component);
            if (node->parent != NULL) {
                fprintf(df, "    parent: %d\n", node->parent->id);
            }
            fprintf(df, "    vars: %s\n", value_string(node->state->vars));
            fprintf(df, "    fwd:\n");
            int eno = 0;
            for (struct edge *edge = node->fwd; edge != NULL; edge = edge->next, eno++) {
                fprintf(df, "        %d:\n", eno);
                struct context *ctx = value_get(edge->ctx, NULL);
                fprintf(df, "            context: %s %s %d\n", value_string(ctx->name), value_string(ctx->arg), ctx->pc);
                fprintf(df, "            choice: %s\n", value_string(edge->choice));
                fprintf(df, "            node: %d (%d)\n", edge->node->id, edge->node->component);
                fprintf(df, "            log:");
                for (int j = 0; j < edge->nlog; j++) {
                    char *p = value_string(edge->log[j]);
                    fprintf(df, " %s", p);
                    free(p);
                }
                fprintf(df, "\n");
            }
            fprintf(df, "    bwd:\n");
            eno = 0;
            for (struct edge *edge = node->bwd; edge != NULL; edge = edge->next, eno++) {
                fprintf(df, "        %d:\n", eno);
                struct context *ctx = value_get(edge->ctx, NULL);
                fprintf(df, "            context: %s %s %d\n", value_string(ctx->name), value_string(ctx->arg), ctx->pc);
                fprintf(df, "            choice: %s\n", value_string(edge->choice));
                fprintf(df, "            node: %d (%d)\n", edge->node->id, edge->node->component);
                fprintf(df, "            log:");
                for (int j = 0; j < edge->nlog; j++) {
                    char *p = value_string(edge->log[j]);
                    fprintf(df, " %s", p);
                    free(p);
                }
                fprintf(df, "\n");
            }
        }
        fclose(df);
    }

    if (false) {
        FILE *df = fopen("charm.dump", "w");
        assert(df != NULL);
        char **table = malloc(global->graph.size * sizeof(char*));
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
            table[i] = state_string(node->state);
            fprintf(df, "%s\n", table[i]);
        }
        fclose(df);
    }
#endif // OBSOLETE

    // Look for data races
    // TODO.  Can easily be parallelized
	// TODO.  Don't need failures/warnings distinction any more
    struct minheap *warnings = minheap_create(fail_cmp);
    if (minheap_empty(global->failures)) {
        printf("Check for data races\n");
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
            graph_check_for_data_race(node, warnings, &global->values);
            if (!minheap_empty(warnings)) {
                break;
            }
        }
    }

    bool no_issues = minheap_empty(global->failures) && minheap_empty(warnings);
    if (no_issues) {
        printf("No issues\n");
    }

    FILE *out = fopen(outfile, "w");
    if (out == NULL) {
        fprintf(stderr, "charm: can't create %s\n", outfile);
        exit(1);
    }

    printf("Phase 4: write results to %s\n", outfile);

    fprintf(out, "{\n");

    if (no_issues) {
        fprintf(out, "  \"issue\": \"No issues\",\n");

        destutter1(&global->graph);

        // Output the symbols;
        struct dict *symbols = collect_symbols(&global->graph);
        fprintf(out, "  \"symbols\": {\n");
        struct symbol_env se = { .out = out, .first = true };
        dict_iter(symbols, print_symbol, &se);
        fprintf(out, "\n");
        fprintf(out, "  },\n");

        fprintf(out, "  \"nodes\": [\n");
        bool first = true;
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
            assert(node->id == i);
            if (node->reachable) {
                if (first) {
                    first = false;
                }
                else {
                    fprintf(out, ",\n");
                }
                fprintf(out, "    {\n");
                fprintf(out, "      \"idx\": %d,\n", node->id);
                fprintf(out, "      \"component\": %d,\n", node->component);
#ifdef notdef
                if (node->parent != NULL) {
                    fprintf(out, "      \"parent\": %d,\n", node->parent->id);
                }
                char *val = json_escape_value(node->state->vars);
                fprintf(out, "      \"value\": \"%s:%d\",\n", val, node->state->choosing != 0);
                free(val);
#endif
                print_transitions(out, symbols, node->fwd);
                if (i == 0) {
                    fprintf(out, "      \"type\": \"initial\"\n");
                }
                else if (node->final) {
                    fprintf(out, "      \"type\": \"terminal\"\n");
                }
                else {
                    fprintf(out, "      \"type\": \"normal\"\n");
                }
                fprintf(out, "    }");
            }
        }
        fprintf(out, "\n");
        fprintf(out, "  ],\n");
#ifdef notdef
        fprintf(out, "  \"edges\": [\n");
        first = true;
        bool first_log;
        for (int i = 0; i < global->graph.size; i++) {
            struct node *node = global->graph.nodes[i];
            if (node->reachable) {
                for (struct edge *edge = node->fwd; edge != NULL; edge = edge->next) {
                    assert(edge->node->reachable);
                    if (first) {
                        first = false;
                    }
                    else {
                        fprintf(out, ",\n");
                    }
                    fprintf(out, "    {\n");
                    fprintf(out, "      \"src\": %d,\n", node->id);
                    fprintf(out, "      \"dst\": %d,\n", edge->node->id);
                    fprintf(out, "      \"print\": [");
                    first_log = true;
                    for (int j = 0; j < edge->nlog; j++) {
                        if (first_log) {
                            first_log = false;
                            fprintf(out, "\n");
                        }
                        else {
                            fprintf(out, ",\n");
                        }
                        char *p = value_json(edge->log[j]);
                        fprintf(out, "        %s", p);
                        free(p);
                    }
                    fprintf(out, "\n");
                    fprintf(out, "      ]\n");
                    fprintf(out, "    }");
                }
            }
        }
        fprintf(out, "\n");
        fprintf(out, "  ],\n");
#endif // notdef
    }
    else {
        // Find shortest "bad" path
        struct failure *bad = NULL;
        if (minheap_empty(global->failures)) {
            bad = minheap_getmin(warnings);
        }
        else {
            bad = minheap_getmin(global->failures);
        }

        switch (bad->type) {
        case FAIL_SAFETY:
            printf("Safety Violation\n");
            fprintf(out, "  \"issue\": \"Safety violation\",\n");
            break;
        case FAIL_INVARIANT:
            printf("Invariant Violation\n");
            fprintf(out, "  \"issue\": \"Invariant violation\",\n");
            break;
        case FAIL_BEHAVIOR:
            printf("Behavior Violation: terminal state not final\n");
            fprintf(out, "  \"issue\": \"Behavior violation: terminal state not final\",\n");
            break;
        case FAIL_TERMINATION:
            printf("Non-terminating state\n");
            fprintf(out, "  \"issue\": \"Non-terminating state\",\n");
            break;
        case FAIL_BUSYWAIT:
            printf("Active busy waiting\n");
            fprintf(out, "  \"issue\": \"Active busy waiting\",\n");
            break;
        case FAIL_RACE:
            assert(bad->address != VALUE_ADDRESS);
            char *addr = value_string(bad->address);
            char *json = json_string_encode(addr, strlen(addr));
            printf("Data race (%s)\n", json);
            fprintf(out, "  \"issue\": \"Data race (%s)\",\n", json);
            free(json);
            free(addr);
            break;
        default:
            panic("main: bad fail type");
        }

        fprintf(out, "  \"macrosteps\": [");
        struct state oldstate;
        memset(&oldstate, 0, sizeof(oldstate));
        struct context *oldctx = calloc(1, sizeof(*oldctx));
        global->dumpfirst = true;
        path_dump(global, out, bad->node, bad->parent, bad->choice, &oldstate, &oldctx, bad->interrupt);
        fprintf(out, "\n");
        free(oldctx);
        fprintf(out, "  ],\n");
    }

    fprintf(out, "  \"code\": [\n");
    jc = dict_lookup(jv->u.map, "pretty", 6);
    assert(jc->type == JV_LIST);
    for (unsigned int i = 0; i < jc->u.list.nvals; i++) {
        struct json_value *next = jc->u.list.vals[i];
        assert(next->type == JV_LIST);
        assert(next->u.list.nvals == 2);
        struct json_value *codestr = next->u.list.vals[0];
        assert(codestr->type == JV_ATOM);
		char *v = json_escape(codestr->u.atom.base, codestr->u.atom.len);
        fprintf(out, "    \"%s\"", v);
		free(v);
        if (i < jc->u.list.nvals - 1) {
            fprintf(out, ",");
        }
        fprintf(out, "\n");
    }
    fprintf(out, "  ],\n");

    fprintf(out, "  \"explain\": [\n");
    for (unsigned int i = 0; i < jc->u.list.nvals; i++) {
        struct json_value *next = jc->u.list.vals[i];
        assert(next->type == JV_LIST);
        assert(next->u.list.nvals == 2);
        struct json_value *codestr = next->u.list.vals[1];
        assert(codestr->type == JV_ATOM);
		char *v = json_escape(codestr->u.atom.base, codestr->u.atom.len);
        fprintf(out, "    \"%s\"", v);
		free(v);
        if (i < jc->u.list.nvals - 1) {
            fprintf(out, ",");
        }
        fprintf(out, "\n");
    }
    fprintf(out, "  ],\n");

    fprintf(out, "  \"locations\": {");
    jc = dict_lookup(jv->u.map, "locations", 9);
    assert(jc->type == JV_MAP);
    struct enum_loc_env_t enum_loc_env;
    enum_loc_env.out = out;
    enum_loc_env.code_map = global->code.code_map;
    dict_iter(jc->u.map, enum_loc, &enum_loc_env);
    fprintf(out, "\n  }\n");

    fprintf(out, "}\n");
	fclose(out);

    iface_write_spec_graph_to_file(global, "iface.gv");
    iface_write_spec_graph_to_json_file(global, "iface.json");

    free(global);
    return 0;
}
#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <time.h>

#ifndef TIME_UTC
#include <sys/time.h>
#endif

#ifndef HARMONY_COMBINE
#include "global.h"
#endif

#define CHUNK_SIZE	4096

double gettime(){
#if defined(TIME_UTC) && !defined(__APPLE__)
    struct timespec ts;
    timespec_get(&ts, TIME_UTC);
    return ts.tv_sec + (double) ts.tv_nsec / 1000000000;
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + (double) tv.tv_usec / 1000000;
#endif
}

unsigned long to_ulong(const char *p, int len){
	unsigned long r = 0;

	while (len > 0) {
		assert(isdigit(*p));
		r *= 10;
		r += *p - '0';
		len--;
		p++;
	}
	return r;
}

void panic(char *s){
    fprintf(stderr, "Panic: %s\n", s);
    exit(1);
}

#ifdef notdef
bool file_read(char *filename, /* OUT */ json_buf_t *buf){
	FILE *fp;

	if ((fp = fopen(filename, "r")) == NULL) {
		perror(filename);
		return false;
	}

	*buf = uv_buf_init(0, 0);
	int n;
	for (;;) {
		buf->base = realloc(buf->base, buf->len + CHUNK_SIZE);
		n = fread(buf->base + buf->len, 1, CHUNK_SIZE, fp);
		if (n < CHUNK_SIZE) {
			if (ferror(fp)) {
				perror(filename);
				fclose(fp);
				return false;
			}
			assert(feof(fp));
			assert(n >= 0);
			fclose(fp);
			buf->len += n;
			break;
		}
		assert(n == CHUNK_SIZE);
		buf->len += CHUNK_SIZE;
	}
	return true;
}
#endif // notdef
#include <assert.h>

#ifndef HARMONY_COMBINE
#include "hashdict.h"
#include "thread.h"
#endif

#define hash_func meiyan

static inline uint32_t meiyan(const char *key, int count) {
	typedef uint32_t *P;
	uint32_t h = 0x811c9dc5;
	while (count >= 8) {
		h = (h ^ ((((*(P)key) << 5) | ((*(P)key) >> 27)) ^ *(P)(key + 4))) * 0xad3e7;
		count -= 8;
		key += 8;
	}
	#define tmp h = (h ^ *(uint16_t*)key) * 0xad3e7; key += 2;
	if (count & 4) { tmp tmp }
	if (count & 2) { tmp }
	if (count & 1) { h = (h ^ *key) * 0xad3e7; }
	#undef tmp
	return h ^ (h >> 16);
}

struct keynode *keynode_new(char*k, int l) {
	struct keynode *node = malloc(sizeof(struct keynode));
	node->len = l;
	node->key = malloc(l);
	memcpy(node->key, k, l);
	node->next = 0;
	node->value = 0;
	return node;
}

// TODO.  Make iterative rather than recursive
void keynode_delete(struct keynode *node) {
	free(node->key);
	if (node->next) keynode_delete(node->next);
	free(node);
}

struct dict *dict_new(int initial_size) {
	struct dict *dict = malloc(sizeof(struct dict));
	if (initial_size == 0) initial_size = 1024;
	dict->length = initial_size;
	dict->count = 0;
	dict->table = calloc(sizeof(struct dict_bucket), initial_size);
	for (int i = 0; i < dict->length; i++) {
		mutex_init(&dict->table[i].lock);
	}
	dict->growth_treshold = 2.0;
	dict->growth_factor = 10;
	dict->concurrent = 0;
	return dict;
}

void dict_delete(struct dict *dict) {
	for (int i = 0; i < dict->length; i++) {
		if (dict->table[i].stable != NULL)
			keynode_delete(dict->table[i].stable);
		if (dict->table[i].unstable != NULL)
			keynode_delete(dict->table[i].unstable);
		mutex_destroy(&dict->table[i].lock);
	}
	free(dict->table);
	free(dict);
}

static void dict_reinsert_when_resizing(struct dict *dict, struct keynode *k2) {
	int n = hash_func(k2->key, k2->len) % dict->length;
	struct keynode *k = dict->table[n].stable;
	k2->next = k;
	dict->table[n].stable = k2;
}

static void dict_resize(struct dict *dict, int newsize) {
	int o = dict->length;
	struct dict_bucket *old = dict->table;
	dict->table = calloc(sizeof(struct dict_bucket), newsize);
	dict->length = newsize;
	for (int i = 0; i < newsize; i++) {
		mutex_init(&dict->table[i].lock);
	}
	for (int i = 0; i < o; i++) {
		struct dict_bucket *b = &old[i];
        assert(b->unstable == NULL);
        struct keynode *k = b->stable;
		while (k != NULL) {
			struct keynode *next = k->next;
			dict_reinsert_when_resizing(dict, k);
			k = next;
		}
		mutex_destroy(&b->lock);
	}
	free(old);
}

void *dict_find(struct dict *dict, const void *key, unsigned int keyn) {
	// assert(keyn > 0);
	int n = hash_func((const char*)key, keyn) % dict->length;
    struct dict_bucket *db = &dict->table[n];

    // First see if the item is in the stable list, which does not require
    // a lock
	struct keynode *k = db->stable;
	while (k != NULL) {
		if (k->len == keyn && memcmp(k->key, key, keyn) == 0) {
			return k;
		}
		k = k->next;
	}

    if (dict->concurrent) {
        mutex_acquire(&db->lock);

        // See if the item is in the unstable list
        k = db->unstable;
        while (k != NULL) {
            if (k->len == keyn && memcmp(k->key, key, keyn) == 0) {
                mutex_release(&db->lock);
                return k;
            }
            k = k->next;
        }
    }

    // If not concurrent may have to grow the table now
	if (!dict->concurrent && db->stable == NULL) {
		double f = (double)dict->count / (double)dict->length;
		if (f > dict->growth_treshold) {
			dict_resize(dict, dict->length * dict->growth_factor);
			return dict_find(dict, key, keyn);
		}
	}

    k = keynode_new((char*)key, keyn);
    if (dict->concurrent) {
            struct keynode *k2;
            for (k2 = db->unstable; k2 != NULL; k2 = k2->next) {
                if (k2->len == k->len && memcmp(k2->key, k->key, k->len) == 0) {
                    fprintf(stderr, "DUPLICATE 3\n");
                    exit(1);
                }
            }
        k->next = db->unstable;
        db->unstable = k;
		db->count++;
        mutex_release(&db->lock);
    }
    else {
        k->next = db->stable;
        db->stable = k;
		dict->count++;
    }
	return k;
}

void **dict_insert(struct dict *dict, const void *key, unsigned int keyn){
    struct keynode *k = dict_find(dict, key, keyn);
    return &k->value;
}

void *dict_retrieve(const void *p, int *psize){
    const struct keynode *k = p;
    if (psize != NULL) {
        *psize = k->len;
    }
    return k->key;
}

void *dict_lookup(struct dict *dict, const void *key, unsigned int keyn) {
	int n = hash_func((const char*)key, keyn) % dict->length;
    struct dict_bucket *db = &dict->table[n];
	// __builtin_prefetch(db);

    // First look in the stable list, which does not require a lock
	struct keynode *k = db->stable;
	while (k != NULL) {
		if (k->len == keyn && !memcmp(k->key, key, keyn)) {
			return k->value;
		}
		k = k->next;
	}

    // Look in the unstable list
    if (dict->concurrent) {
        mutex_acquire(&db->lock);
        k = db->unstable;
        while (k != NULL) {
            if (k->len == keyn && !memcmp(k->key, key, keyn)) {
                mutex_release(&db->lock);
                return k->value;
            }
            k = k->next;
        }
        mutex_release(&db->lock);
    }

	return NULL;
}

void dict_iter(struct dict *dict, enumFunc f, void *env) {
	for (int i = 0; i < dict->length; i++) {
        struct dict_bucket *db = &dict->table[i];
        struct keynode *k = db->stable;
        while (k != NULL) {
            (*f)(env, k->key, k->len, k->value);
            k = k->next;
        }
        if (dict->concurrent) {
            mutex_acquire(&db->lock);
            k = db->unstable;
            while (k != NULL) {
                (*f)(env, k->key, k->len, k->value);
                k = k->next;
            }
            mutex_release(&db->lock);
        }
	}
}

// To switch between concurrent and sequential modes
void dict_set_concurrent(struct dict *dict, int concurrent) {
	if (dict->concurrent == concurrent) {
        assert(false);      // Shouldn't normally happen
		return;
	}
    dict->concurrent = concurrent;
    if (concurrent) {
        return;
    }

    // When going from concurrent to sequential, need to move over
    // the unstable values and possibly grow the table
	dict->count = 0;
	for (int i = 0; i < dict->length; i++) {
        struct dict_bucket *db = &dict->table[i];
        struct keynode *k;
        while ((k = db->unstable) != NULL) {
            db->unstable = k->next;
            k->next = db->stable;
            db->stable = k;
        }
		dict->count += db->count;
    }
	double f = (double)dict->count / (double)dict->length;
	if (f > dict->growth_treshold) {
        int min = dict->length * dict->growth_factor;
        if (min < dict->count) {
            min = dict->count * 2;
        }
		dict_resize(dict, min);
	}
}
#ifndef HARMONY_COMBINE
#include "hashset.h"
#endif

struct hashset_t hashset_new(int initial_size) {
    struct hashset_t set;
    set.dict = dict_new(initial_size);
    return set;
}

static int DUMMY;

// returns true iff key was in the set before
bool hashset_insert(struct hashset_t set, const void *key, unsigned int keylen) {
    void **value = dict_insert(set.dict, key, keylen);
    bool result = *value != NULL;
    *value = &DUMMY;
    return result;
}

// returns true iff key was in the set before
bool hashset_remove(struct hashset_t set, const void *key, unsigned int keylen) {
    void **value = dict_insert(set.dict, key, keylen);
    bool result = *value != NULL;
    *value = NULL;
    return result;
}

bool hashset_contains(struct hashset_t set, const void *key, unsigned int keylen) {
    return dict_lookup(set.dict, key, keylen) != NULL;
}

void hashset_delete(struct hashset_t set) {
    dict_delete(set.dict);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>

#ifndef HARMONY_COMBINE
#include "hashdict.h"
#include "json.h"
#endif

#define new_alloc(t)	(t *) calloc(1, sizeof(t))

#define buf_adv(b)		do { assert((b)->len > 0); (b)->base++; (b)->len--; } while (false)

static void json_map_cleanup(void *env, const void *key, unsigned int keylen, void *val){
	json_value_free(val);
}

void json_value_free(struct json_value *jv){
	switch (jv->type) {
	case JV_ATOM:
		free(jv->u.atom.base);
		break;
	case JV_MAP:
		dict_iter(jv->u.map, json_map_cleanup, jv);
		dict_delete(jv->u.map);
		break;
	case JV_LIST:
		{
			for (unsigned int i = 0; i < jv->u.list.nvals; i++) {
				json_value_free(jv->u.list.vals[i]);
			}
			free(jv->u.list.vals);
		}
		break;
	default:
		assert(0);
	}
	free(jv);
}

static void json_string_add(json_buf_t *buf, char c){
	/* Grow efficiently, doubling at powers of 2.
	 */
	if (((buf->len + 1) & buf->len) == 0) {
		buf->base = realloc(buf->base, (buf->len + 1) * 2);
	}
	buf->base[buf->len++] = c;
}

static void json_skip_blanks(json_buf_t *buf){
	while (buf->len > 0) {
		if (!isspace(*buf->base)) {
			return;
		}
		buf_adv(buf);
	}
}

static bool is_atom_char(char c){
	return isalnum(c) || c == '.' || c == '-' || c == '_' || c == '/';
}

static void json_parse_atom(json_buf_t *buf, json_buf_t *atom){
	assert(buf->len > 0);
	assert(is_atom_char(*buf->base));
	while (buf->len > 0) {
		if (!is_atom_char(*buf->base)) {
			return;
		}
		json_string_add(atom, *buf->base);
		buf_adv(buf);
	}
}

void json_map_append(struct json_value *map, json_buf_t key, struct json_value *jv){
	assert(map->type == JV_MAP);
	void **p = dict_insert(map->u.map, key.base, key.len);
	if (*p != 0) {
		fprintf(stderr, "json_map_append: duplicate key: '%.*s'\n",
							(int) key.len, key.base);
	}
	assert(*p == 0);
	*p = jv;
}

static struct json_value *json_parse_struct(json_buf_t *buf){
	assert(buf->len > 0);
	assert(*buf->base == '{');
	buf_adv(buf);

	struct json_value *jv = new_alloc(struct json_value);
	jv->type = JV_MAP;
	jv->u.map = dict_new(0);
	for (;;) {
		json_skip_blanks(buf);
		assert(buf->len > 0);
		if (*buf->base == '}') {
			buf_adv(buf);
			return jv;
		}

		struct json_value *key = json_parse_value(buf);
		assert(key->type == JV_ATOM);
		json_skip_blanks(buf);
		assert(buf->len > 0);
		assert(*buf->base == '=' || *buf->base == ':');
		buf_adv(buf);
		struct json_value *val = json_parse_value(buf);
		json_map_append(jv, key->u.atom, val);
		json_value_free(key);

		json_skip_blanks(buf);
		assert(buf->len > 0);
		if (*buf->base == ',' || *buf->base == ';') {
			buf_adv(buf);
		}
	}
}

/* Append a value to the list.
 */
void json_list_append(struct json_value *list, struct json_value *jv){
	assert(list->type == JV_LIST);
	list->u.list.vals = realloc(list->u.list.vals,
				(list->u.list.nvals + 1) * sizeof(*list->u.list.vals));
	list->u.list.vals[list->u.list.nvals++] = jv;
}

static struct json_value *json_parse_list(json_buf_t *buf){
	assert(buf->len > 0);
	assert(*buf->base == '[');
	buf_adv(buf);

	struct json_value *jv = new_alloc(struct json_value);
	jv->type = JV_LIST;
	unsigned int index;
	for (index = 0;; index++) {
		json_skip_blanks(buf);
		assert(buf->len > 0);
		if (*buf->base == ']') {
			buf_adv(buf);
			return jv;
		}
		json_list_append(jv, json_parse_value(buf));
		json_skip_blanks(buf);
		assert(buf->len > 0);
		if (*buf->base == ',' || *buf->base == ';') {
			buf_adv(buf);
		}
	}
}

static struct json_value *json_parse_string(json_buf_t *buf){
	assert(buf->len > 0);
	assert(*buf->base == '"' || *buf->base == '\'');
	char delim = *buf->base;
	buf_adv(buf);

	struct json_value *jv = new_alloc(struct json_value);
	jv->type = JV_ATOM;

	while (buf->len > 0) {
		if (*buf->base == '\\') {
			buf_adv(buf);
			assert(buf->len > 0);
			switch (*buf->base) {
			case 0:
				json_string_add(&jv->u.atom, 0);
				break;
			case '\\':
				json_string_add(&jv->u.atom, '\\');
				break;
			case '\'':
				json_string_add(&jv->u.atom, '\'');
				break;
			case '\"':
				json_string_add(&jv->u.atom, '\"');
				break;
			case '\?':
				json_string_add(&jv->u.atom, '\?');
				break;
			case 'a':
				json_string_add(&jv->u.atom, '\a');
				break;
			case 'b':
				json_string_add(&jv->u.atom, '\b');
				break;
			case 'f':
				json_string_add(&jv->u.atom, '\f');
				break;
			case 'n':
				json_string_add(&jv->u.atom, '\n');
				break;
			case 'r':
				json_string_add(&jv->u.atom, '\r');
				break;
			case 't':
				json_string_add(&jv->u.atom, '\t');
				break;
			case 'v':
				json_string_add(&jv->u.atom, '\v');
				break;
			default:
				json_string_add(&jv->u.atom, *buf->base);
			}
		}
		else if (*buf->base == delim) {
			buf_adv(buf);
			return jv;
		}
		else {
			json_string_add(&jv->u.atom, *buf->base);
		}
		buf_adv(buf);
	}
	assert(0);
	return 0;
}

struct json_value *json_parse_value(json_buf_t *buf){
	json_skip_blanks(buf);
	assert(buf->len > 0);
	switch (*buf->base) {
	case '{':
		return json_parse_struct(buf);
	case '[':
		return json_parse_list(buf);
	case '"': case '\'':
		return json_parse_string(buf);
	default:
		if (!is_atom_char(*buf->base)) {
			fprintf(stderr, "--> '%.*s'\n", (int) buf->len, buf->base);
		}
		assert(is_atom_char(*buf->base));
		struct json_value *jv = new_alloc(struct json_value);
		jv->type = JV_ATOM;
		json_parse_atom(buf, &jv->u.atom);
		return jv;
	}
}

struct json_value *json_string(char *s, unsigned int len){
	struct json_value *jv = new_alloc(struct json_value);
	jv->type = JV_ATOM;
	jv->u.atom.len = len;
	jv->u.atom.base = malloc(len);
	memcpy(jv->u.atom.base, s, len);
	return jv;
}

static void json_indent(unsigned int ind){
	while (ind > 0) {
		putchar(' ');
		ind--;
	}
}

static void json_dump_ind(struct json_value *jv, unsigned int ind);

static void json_dump_map(void *env, const void *key, unsigned int keylen, void *val){
	unsigned int ind = (size_t) env;
	json_indent(ind);
	printf("%.*s: ", keylen, (char *) key);
	json_dump_ind(val, ind + 2);
}

static void json_dump_string(json_buf_t buf){
	unsigned int i;

	/* See if we should quote it.
	 */
	for (i = 0; i < buf.len; i++) {
		if (!is_atom_char(buf.base[i])) {
			break;
		}
	}
	if (i == buf.len) {
		printf("%.*s\n", (int) buf.len, buf.base);
		return;
	}

	putchar('"');
	for (i = 0; i < buf.len; i++) {
		switch (buf.base[i]) {
		case 0:
			printf("\\0");
			break;
		case '"':
			printf("\\\"");
			break;
		default:
			putchar(buf.base[i]);
		}
	}
	printf("\"\n");
}

static void json_dump_ind(struct json_value *jv, unsigned int ind){
	switch (jv->type) {
	case JV_ATOM:
		json_dump_string(jv->u.atom);
		break;
	case JV_MAP:
		printf("{\n");
		dict_iter(jv->u.map, json_dump_map, (void *) (size_t) (ind + 2));
		json_indent(ind); printf("}\n");
		break;
	case JV_LIST:
		printf("[\n");
		for (unsigned int i = 0; i < jv->u.list.nvals; i++) {
			json_indent(ind + 2);
			json_dump_ind(jv->u.list.vals[i], ind + 4);
		}
		json_indent(ind); printf("]\n");
		break;
	default:
		assert(0);
	}
}

void json_dump(struct json_value *jv){
	json_dump_ind(jv, 0);
}

/* Allocate and get a string out of an atom identified by string key.
 */
char *json_lookup_string(struct dict *map, char *key){
	struct json_value *jv = dict_lookup(map, key, strlen(key));
	if (jv == 0) {
		return 0;
	}
	assert(jv->type == JV_ATOM);
	char *p = malloc(jv->u.atom.len + 1);
	memcpy(p, jv->u.atom.base, jv->u.atom.len);
	p[jv->u.atom.len] = 0;
	return p;
}

/* Find a map inside the map by string key.
 */
struct json_value *json_lookup_map(struct dict *map, char *key){
	struct json_value *jv = dict_lookup(map, key, strlen(key));
	if (jv == 0) {
		return 0;
	}
	assert(jv->type == JV_MAP);
	return jv;
}

/* Find a json_value (can be either a JV_MAP or JV_ATOM) 
 * inside the map by string key.
 */
struct json_value *json_lookup_value(struct dict *map, char *key){
	struct json_value *jv = dict_lookup(map, key, strlen(key));
	if (jv == 0) {
		return 0;
	}
	return jv;
}

char *json_escape(const char *s, unsigned int len){
	struct strbuf sb;

	strbuf_init(&sb);
	while (len > 0) {
		switch (*s) {		// TODO.  More cases
		case '"':
			strbuf_append(&sb, "\\\"", 2);
			break;
		case '\\':
			strbuf_append(&sb, "\\\\", 2);
			break;
        case '\n':
			strbuf_append(&sb, "\\n", 2);
			break;
        case '\r':
			strbuf_append(&sb, "\\r", 2);
			break;
		default:
			strbuf_append(&sb, s, 1);
		}
		s++;
		len--;
	}
	return strbuf_getstr(&sb);
}
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#ifndef HARMONY_COMBINE
#include "ops.h"
#endif

#define MAX_ARITY   16

struct val_info {
    int size, index;
    hvalue_t *vals;
};

struct f_info {
    char *name;
    hvalue_t (*f)(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values);
};

struct var_tree {
    enum { VT_NAME, VT_TUPLE } type;
    union {
        hvalue_t name;
        struct {
            int n;
            struct var_tree **elements;
        } tuple;
    } u;
};

// These are initialized in ops_init and are immutable.
static struct dict *ops_map, *f_map;
static hvalue_t underscore, this_atom;

bool is_sequential(hvalue_t seqvars, hvalue_t *indices, int n){
    assert((seqvars & VALUE_MASK) == VALUE_SET);
    int size;
    hvalue_t *seqs = value_get(seqvars, &size);
    size /= sizeof(hvalue_t);

    n *= sizeof(hvalue_t);
    for (int i = 0; i < size; i++) {
        assert((seqs[i] & VALUE_MASK) == VALUE_ADDRESS);
        int sn;
        hvalue_t *inds = value_get(seqs[i], &sn);
        if (n >= sn && sn >= 0 && memcmp(indices, inds, sn) == 0) {
            return true;
        }
    }
    return false;
}

hvalue_t var_match_rec(struct context *ctx, struct var_tree *vt, struct values_t *values,
                            hvalue_t arg, hvalue_t vars){
    switch (vt->type) {
    case VT_NAME:
        if (vt->u.name == underscore) {
            return vars;
        }
        return value_dict_store(values, vars, vt->u.name, arg);
    case VT_TUPLE:
        if ((arg & VALUE_MASK) != VALUE_DICT) {
            if (vt->u.tuple.n == 0) {
                return value_ctx_failure(ctx, values, "match: expected ()");
            }
            else {
                return value_ctx_failure(ctx, values, "match: expected a tuple");
            }
        }
        if (arg == VALUE_DICT) {
            if (vt->u.tuple.n != 0) {
                return value_ctx_failure(ctx, values, "match: expected a %d-tuple",
                                                vt->u.tuple.n);
            }
            return vars;
        }
        if (vt->u.tuple.n == 0) {
            return value_ctx_failure(ctx, values, "match: expected an empty tuple");
        }
        int size;
        hvalue_t *vals = value_get(arg, &size);
        size /= 2 * sizeof(hvalue_t);
        if (vt->u.tuple.n != size) {
            return value_ctx_failure(ctx, values, "match: tuple size mismatch");
        }
        for (int i = 0; i < size; i++) {
            if (vals[2*i] != (((hvalue_t) i << VALUE_BITS) | VALUE_INT)) {
                return value_ctx_failure(ctx, values, "match: not a tuple");
            }
            vars = var_match_rec(ctx, vt->u.tuple.elements[i], values, vals[2*i+1], vars);
        }
        return vars;
    default:
        panic("var_tree_rec: bad vartree type");
        return 0;
    }
}

void var_match(struct context *ctx, struct var_tree *vt, struct values_t *values, hvalue_t arg){
    hvalue_t vars = var_match_rec(ctx, vt, values, arg, ctx->vars);
    if (ctx->failure == 0) {
        ctx->vars = vars;
    }
}

static void skip_blanks(char *s, int len, int *index){
    while (*index < len && s[*index] == ' ') {
        (*index)++;
    }
}

struct var_tree *var_parse(struct values_t *values, char *s, int len, int *index){
    assert(*index < len);
    struct var_tree *vt = new_alloc(struct var_tree);

    skip_blanks(s, len, index);
    if (s[*index] == '(') {
        vt->type = VT_TUPLE;
        (*index)++;
        skip_blanks(s, len, index);
        assert(*index < len);
        if (s[*index] == ')') {
            (*index)++;
        }
        else {
            while (true) {
                struct var_tree *elt = var_parse(values, s, len, index);
                vt->u.tuple.elements = realloc(vt->u.tuple.elements,
                        (vt->u.tuple.n + 1) * sizeof(elt));
                vt->u.tuple.elements[vt->u.tuple.n++] = elt;
                skip_blanks(s, len, index);
                assert(*index < len);
                if (s[*index] == ')') {
                    (*index)++;
                    break;
                }
                assert(s[*index] == ',');
                (*index)++;
            }
        }
    }
    else if (s[*index] == '[') {
        vt->type = VT_TUPLE;
        (*index)++;
        panic("var_parse: TODO");
    }
    else {
        vt->type = VT_NAME;
        int i = *index;
        assert(isalpha(s[i]) || s[i] == '_' || s[i] == '$');
        i++;
        while (i < len && (isalpha(s[i]) || s[i] == '_' || isdigit(s[i]))) {
            i++;
        }
        vt->u.name = value_put_atom(values, &s[*index], i - *index);
        *index = i;
    }
    return vt;
}

void interrupt_invoke(struct step *step){
    assert(!step->ctx->interruptlevel);
	assert((step->ctx->trap_pc & VALUE_MASK) == VALUE_PC);
    value_ctx_push(&step->ctx, (step->ctx->pc << VALUE_BITS) | VALUE_PC);
    value_ctx_push(&step->ctx, (CALLTYPE_INTERRUPT << VALUE_BITS) | VALUE_INT);
    value_ctx_push(&step->ctx, step->ctx->trap_arg);
    step->ctx->pc = step->ctx->trap_pc >> VALUE_BITS;
    step->ctx->trap_pc = 0;
    step->ctx->interruptlevel = true;
}

bool ind_tryload(struct values_t *values, hvalue_t dict, hvalue_t *indices, int n, hvalue_t *result){
    hvalue_t d = dict;
    for (int i = 0; i < n; i++) {
        if (!value_dict_tryload(values, d, indices[i], &d)) {
            return false;
        }
    }
    *result = d;
    return true;
}

bool ind_trystore(hvalue_t dict, hvalue_t *indices, int n, hvalue_t value, struct values_t *values, hvalue_t *result){
    assert((dict & VALUE_MASK) == VALUE_DICT);
    assert(n > 0);

    if (n == 1) {
        *result = value_dict_store(values, dict, indices[0], value);
        return true;
    }
    else {
        hvalue_t *vals;
        int size;
        if (dict == VALUE_DICT) {
            vals = NULL;
            size = 0;
        }
        else {
            vals = value_get(dict & ~VALUE_MASK, &size);
            size /= sizeof(hvalue_t);
            assert(size % 2 == 0);
        }

        int i;
        for (i = 0; i < size; i += 2) {
            if (vals[i] == indices[0]) {
                hvalue_t d = vals[i+1];
                if ((d & VALUE_MASK) != VALUE_DICT) {
                    return false;
                }
                hvalue_t nd;
                if (!ind_trystore(d, indices + 1, n - 1, value, values, &nd)) {
                    return false;
                }
                if (d == nd) {
                    *result = dict;
                    return true;
                }
                int n = size * sizeof(hvalue_t);
                hvalue_t *copy = malloc(n);
                memcpy(copy, vals, n);
                copy[i + 1] = nd;
                hvalue_t v = value_put_dict(values, copy, n);
                free(copy);
                *result = v;
                return true;
            }
            /* 
                if (value_cmp(vals[i], key) > 0) {
                    assert(false);
                }
            */
        }
    }
    return false;
}

bool ind_remove(hvalue_t dict, hvalue_t *indices, int n, struct values_t *values,
                                        hvalue_t *result) {
    assert((dict & VALUE_MASK) == VALUE_DICT);
    assert(n > 0);

    if (n == 1) {
        *result = value_dict_remove(values, dict, indices[0]);
        return true;
    }
    else {
        hvalue_t *vals;
        int size;
        if (dict == VALUE_DICT) {
            vals = NULL;
            size = 0;
        }
        else {
            vals = value_get(dict & ~VALUE_MASK, &size);
            size /= sizeof(hvalue_t);
            assert(size % 2 == 0);
        }

        int i;
        for (i = 0; i < size; i += 2) {
            if (vals[i] == indices[0]) {
                hvalue_t d = vals[i+1];
                if ((d & VALUE_MASK) != VALUE_DICT) {
                    return false;
                }
                hvalue_t nd;
                if (!ind_remove(d, indices + 1, n - 1, values, &nd)) {
                    return false;
                }
                if (d == nd) {
                    *result = dict;
                    return true;
                }
                int n = size * sizeof(hvalue_t);
                hvalue_t *copy = malloc(n);
                memcpy(copy, vals, n);
                copy[i + 1] = nd;
                hvalue_t v = value_put_dict(values, copy, n);
                free(copy);
                *result = v;
                return true;
            }
            /* 
                if (value_cmp(vals[i], key) > 0) {
                    assert(false);
                }
            */
        }
        return false;
    }
}

void op_Address(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t index = value_ctx_pop(&step->ctx);
    hvalue_t av = value_ctx_pop(&step->ctx);
    if ((av & VALUE_MASK) != VALUE_ADDRESS) {
        char *p = value_string(av);
        value_ctx_failure(step->ctx, &global->values, "%s: not an address", p);
        free(p);
        return;
    }
    if (av == VALUE_ADDRESS) {
        value_ctx_failure(step->ctx, &global->values, "None unexpected");
        return;
    }

    int size;
    hvalue_t *indices = value_copy(av, &size);
    indices = realloc(indices, size + sizeof(index));
    indices[size / sizeof(hvalue_t)] = index;
    value_ctx_push(&step->ctx, value_put_address(&global->values, indices, size + sizeof(index)));
    free(indices);
    step->ctx->pc++;
}

void op_Apply(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t e = value_ctx_pop(&step->ctx);
    hvalue_t method = value_ctx_pop(&step->ctx);

    hvalue_t type = method & VALUE_MASK;
    switch (type) {
    case VALUE_DICT:
        {
            hvalue_t v;
            if (!value_dict_tryload(&global->values, method, e, &v)) {
                char *m = value_string(method);
                char *x = value_string(e);
                value_ctx_failure(step->ctx, &global->values, "Bad index %s: not in %s", x, m);
                free(m);
                free(x);
                return;
            }
            value_ctx_push(&step->ctx, v);
            step->ctx->pc++;
        }
        return;
    case VALUE_ATOM:
        {
            if ((e & VALUE_MASK) != VALUE_INT) {
                value_ctx_failure(step->ctx, &global->values, "Bad index type for string");
                return;
            }
            e >>= VALUE_BITS;
            int size;
            char *chars = value_get(method, &size);
            if ((int) e >= size) {
                value_ctx_failure(step->ctx, &global->values, "Index out of range");
                return;
            }
            hvalue_t v = value_put_atom(&global->values, &chars[e], 1);
            value_ctx_push(&step->ctx, v);
            step->ctx->pc++;
        }
        return;
    case VALUE_PC:
        value_ctx_push(&step->ctx, ((step->ctx->pc + 1) << VALUE_BITS) | VALUE_PC);
        value_ctx_push(&step->ctx, (CALLTYPE_NORMAL << VALUE_BITS) | VALUE_INT);
        value_ctx_push(&step->ctx, e);
        assert((method >> VALUE_BITS) != step->ctx->pc);
        step->ctx->pc = method >> VALUE_BITS;
        return;
    default:
        {
            char *x = value_string(method);
            value_ctx_failure(step->ctx, &global->values, "Can only apply to methods or dictionaries, not to: %s", x);
            free(x);
        }
    }
}

void op_Assert(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t v = value_ctx_pop(&step->ctx);
    if ((v & VALUE_MASK) != VALUE_BOOL) {
        value_ctx_failure(step->ctx, &global->values, "assert can only be applied to bool values");
    }
    if (v == VALUE_FALSE) {
        value_ctx_failure(step->ctx, &global->values, "Harmony assertion failed");
    }
    else {
        step->ctx->pc++;
    }
}

void op_Assert2(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t e = value_ctx_pop(&step->ctx);
    hvalue_t v = value_ctx_pop(&step->ctx);
    if ((v & VALUE_MASK) != VALUE_BOOL) {
        value_ctx_failure(step->ctx, &global->values, "assert2 can only be applied to bool values");
    }
    if (v == VALUE_FALSE) {
        char *p = value_string(e);
        value_ctx_failure(step->ctx, &global->values, "Harmony assertion failed: %s", p);
        free(p);
    }
    else {
        step->ctx->pc++;
    }
}

void op_Print(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t symbol = value_ctx_pop(&step->ctx);
    step->log = realloc(step->log, (step->nlog + 1) * sizeof(symbol));
    step->log[step->nlog++] = symbol;
    if (global->dfa != NULL) {
        int nstate = dfa_step(global->dfa, state->dfa_state, symbol);
        if (nstate < 0) {
            char *p = value_string(symbol);
            value_ctx_failure(step->ctx, &global->values, "Behavior failure on %s", p);
            free(p);
            return;
        }
        state->dfa_state = nstate;

    }
    step->ctx->pc++;
}

void op_AtomicDec(const void *env, struct state *state, struct step *step, struct global_t *global){
    struct context *ctx = step->ctx;

    assert(ctx->atomic > 0);
    if (--ctx->atomic == 0) {
        ctx->atomicFlag = false;
    }
    ctx->pc++;
}

void op_AtomicInc(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_AtomicInc *ea = env;
    struct context *ctx = step->ctx;

    ctx->atomic++;
    if (!ea->lazy) {
        ctx->atomicFlag = true;
    }
    ctx->pc++;
}

void op_Choose(const void *env, struct state *state, struct step *step, struct global_t *global){
    panic("op_Choose: should not be called");
}

void op_Continue(const void *env, struct state *state, struct step *step, struct global_t *global){
    step->ctx->pc++;
}

void op_Cut(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Cut *ec = env;
    struct context *ctx = step->ctx;

    hvalue_t v = value_dict_load(ctx->vars, ec->set);
    if ((v & VALUE_MASK) == VALUE_SET) {
        if (ec->key != NULL) {
            value_ctx_failure(ctx, &global->values, "Can't cut set in key/value pairs");
            return;
        }
        assert(v != VALUE_SET);
        void *p = (void *) (v & ~VALUE_MASK);

        int size;
        hvalue_t *vals = dict_retrieve(p, &size);
        assert(size > 0);

        ctx->vars = value_dict_store(&global->values, ctx->vars, ec->set, value_put_set(&global->values, &vals[1], size - sizeof(hvalue_t)));
        var_match(step->ctx, ec->value, &global->values, vals[0]);
        step->ctx->pc++;
        return;
    }
    if ((v & VALUE_MASK) == VALUE_DICT) {
        assert(v != VALUE_DICT);
        void *p = (void *) (v & ~VALUE_MASK);

        int size;
        hvalue_t *vals = dict_retrieve(p, &size);
        assert(size > 0);

        ctx->vars = value_dict_store(&global->values, ctx->vars, ec->set, value_put_dict(&global->values, &vals[2], size - 2 * sizeof(hvalue_t)));
        var_match(step->ctx, ec->value, &global->values, vals[1]);
        if (ec->key != NULL) {
            var_match(step->ctx, ec->key, &global->values, vals[0]);
        }
        step->ctx->pc++;
        return;
    }
    if ((v & VALUE_MASK) == VALUE_ATOM) {
        if (ec->key != NULL) {
            value_ctx_failure(ctx, &global->values, "Can't cut string in key/value pairs");
            return;
        }
        assert(v != VALUE_ATOM);
        int size;
        char *chars = value_get(v, &size);
        assert(size > 0);

        ctx->vars = value_dict_store(&global->values, ctx->vars, ec->set, value_put_atom(&global->values, &chars[1], size - 1));
        hvalue_t e = value_put_atom(&global->values, chars, 1);
        var_match(step->ctx, ec->value, &global->values, e);
        step->ctx->pc++;
        return;
    }
    value_ctx_failure(step->ctx, &global->values, "op_Cut: not a set, dict, or string");
}

void op_Del(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Del *ed = env;

    assert((state->vars & VALUE_MASK) == VALUE_DICT);

    if (step->ctx->readonly > 0) {
        value_ctx_failure(step->ctx, &global->values, "Can't update state in assert or invariant");
        return;
    }

    if (ed == 0) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        if ((av & VALUE_MASK) != VALUE_ADDRESS) {
            char *p = value_string(av);
            value_ctx_failure(step->ctx, &global->values, "Del %s: not an address", p);
            free(p);
            return;
        }
        if (av == VALUE_ADDRESS) {
            value_ctx_failure(step->ctx, &global->values, "Del: address is None");
            return;
        }

        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);
        if (step->ai != NULL) {
            step->ai->indices = indices;
            step->ai->n = size;
            step->ai->load = false;
        }
        hvalue_t nd;
        if (!ind_remove(state->vars, indices, size, &global->values, &nd)) {
            value_ctx_failure(step->ctx, &global->values, "Del: no such variable");
        }
        else {
            state->vars = nd;
            step->ctx->pc++;
        }
    }
    else {
        if (step->ai != NULL) {
            step->ai->indices = ed->indices;
            step->ai->n = ed->n;
            step->ai->load = false;
        }
        hvalue_t nd;
        if (!ind_remove(state->vars, ed->indices, ed->n, &global->values, &nd)) {
            value_ctx_failure(step->ctx, &global->values, "Del: bad variable");
        }
        else {
            state->vars = nd;
            step->ctx->pc++;
        }
    }
}

void op_DelVar(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_DelVar *ed = env;

    assert((step->ctx->vars & VALUE_MASK) == VALUE_DICT);
    if (ed == NULL) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        assert((av & VALUE_MASK) == VALUE_ADDRESS);
        assert(av != VALUE_ADDRESS);

        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);

        bool result;
        if (indices[0] == this_atom) {
            if ((step->ctx->this & VALUE_MASK) != VALUE_DICT) {
                value_ctx_failure(step->ctx, &global->values, "DelVar: 'this' is not a dictionary");
                return;
            }
		    result = ind_remove(step->ctx->this, &indices[1], size - 1, &global->values, &step->ctx->this);
        }
        else {
		    result = ind_remove(step->ctx->vars, indices, size, &global->values, &step->ctx->vars);
        }
        if (!result) {
            char *x = indices_string(indices, size);
            value_ctx_failure(step->ctx, &global->values, "DelVar: bad address: %s", x);
            free(x);
			return;
		}
    }
	else {
        if (ed->name == this_atom) {
            value_ctx_failure(step->ctx, &global->values, "DelVar: can't del 'this'");
            return;
        }
        else {
            step->ctx->vars = value_dict_remove(&global->values, step->ctx->vars, ed->name);
        }
	}
	step->ctx->pc++;
}

void op_Dup(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t v = value_ctx_pop(&step->ctx);
    value_ctx_push(&step->ctx, v);
    value_ctx_push(&step->ctx, v);
    step->ctx->pc++;
}

void op_Frame(const void *env, struct state *state, struct step *step, struct global_t *global){
    static hvalue_t result = 0;

    if (result == 0) {
        result = value_put_atom(&global->values, "result", 6);
    }

    const struct env_Frame *ef = env;

    // peek at argument
    hvalue_t arg = value_ctx_pop(&step->ctx);
    value_ctx_push(&step->ctx, arg);

    hvalue_t oldvars = step->ctx->vars;

    // Set result to None
    step->ctx->vars = value_dict_store(&global->values, VALUE_DICT, result, VALUE_ADDRESS);

    // try to match against parameters
    var_match(step->ctx, ef->args, &global->values, arg);
    if (step->ctx->failure != 0) {
        return;
    }
 
    value_ctx_push(&step->ctx, oldvars);
    value_ctx_push(&step->ctx, (step->ctx->fp << VALUE_BITS) | VALUE_INT);

    struct context *ctx = step->ctx;
    ctx->fp = ctx->sp;
    ctx->pc += 1;
}

void op_Go(
    const void *env,
    struct state *state,
    struct step *step,
    struct global_t *global
){
    hvalue_t ctx = value_ctx_pop(&step->ctx);
    if ((ctx & VALUE_MASK) != VALUE_CONTEXT) {
        value_ctx_failure(step->ctx, &global->values, "Go: not a context");
        return;
    }

    // Remove from stopbag if it's there
    hvalue_t count;
    if (value_dict_tryload(&global->values, state->stopbag, ctx, &count)) {
        assert((count & VALUE_MASK) == VALUE_INT);
        assert(count != VALUE_INT);
        count -= 1 << VALUE_BITS;
        if (count != VALUE_INT) {
            state->stopbag = value_dict_store(&global->values, state->stopbag, ctx, count);
        }
        else {
            state->stopbag = value_dict_remove(&global->values, state->stopbag, ctx);
        }
    }

    hvalue_t result = value_ctx_pop(&step->ctx);
    struct context *copy = value_copy(ctx, NULL);
    value_ctx_push(&copy, result);
    copy->stopped = false;
    hvalue_t v = value_put_context(&global->values, copy);
    free(copy);
    state->ctxbag = value_bag_add(&global->values, state->ctxbag, v, 1);
    step->ctx->pc++;
}

void op_IncVar(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_IncVar *ei = env;
    struct context *ctx = step->ctx;

    assert((ctx->vars & VALUE_MASK) == VALUE_DICT);

    hvalue_t v = value_dict_load(ctx->vars, ei->name);
    assert((v & VALUE_MASK) == VALUE_INT);
    v += 1 << VALUE_BITS;
    ctx->vars = value_dict_store(&global->values, ctx->vars, ei->name, v);
    step->ctx->pc++;
}

void op_Invariant(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Invariant *ei = env;

    assert((state->invariants & VALUE_MASK) == VALUE_SET);
    int size;
    hvalue_t *vals;
    if (state->invariants == VALUE_SET) {
        size = 0;
        vals = NULL;
    }
    else {
        vals = value_get(state->invariants, &size);
    }
    vals = realloc(vals, size + sizeof(hvalue_t));
    * (hvalue_t *) ((char *) vals + size) = (step->ctx->pc << VALUE_BITS) | VALUE_PC;
    state->invariants = value_put_set(&global->values, vals, size + sizeof(hvalue_t));
    step->ctx->pc = ei->end + 1;
}

int invariant_cnt(const void *env){
    const struct env_Invariant *ei = env;

    return ei->end;
}

void op_Jump(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Jump *ej = env;
    step->ctx->pc = ej->pc;
}

void op_JumpCond(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_JumpCond *ej = env;

    hvalue_t v = value_ctx_pop(&step->ctx);
    if ((ej->cond == VALUE_FALSE || ej->cond == VALUE_TRUE) &&
                            !(v == VALUE_FALSE || v == VALUE_TRUE)) {
        value_ctx_failure(step->ctx, &global->values, "JumpCond: not an boolean");
    }
    else if (v == ej->cond) {
        assert(step->ctx->pc != ej->pc);
        step->ctx->pc = ej->pc;
    }
    else {
        step->ctx->pc++;
    }
}

void op_Load(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Load *el = env;

    assert((state->vars & VALUE_MASK) == VALUE_DICT);

    hvalue_t v;
    if (el == 0) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        if ((av & VALUE_MASK) != VALUE_ADDRESS) {
            char *p = value_string(av);
            value_ctx_failure(step->ctx, &global->values, "Load %s: not an address", p);
            free(p);
            return;
        }
        if (av == VALUE_ADDRESS) {
            value_ctx_failure(step->ctx, &global->values, "Load: can't load from None");
            return;
        }

        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);
        if (step->ai != NULL) {
            step->ai->indices = indices;
            step->ai->n = size;
            step->ai->load = true;
        }

        if (!ind_tryload(&global->values, state->vars, indices, size, &v)) {
            char *x = indices_string(indices, size);
            value_ctx_failure(step->ctx, &global->values, "Load: unknown address %s", x);
            free(x);
            return;
        }
        value_ctx_push(&step->ctx, v);
    }
    else {
        if (step->ai != NULL) {
            step->ai->indices = el->indices;
            step->ai->n = el->n;
            step->ai->load = true;
        }
        if (!ind_tryload(&global->values, state->vars, el->indices, el->n, &v)) {
            char *x = indices_string(el->indices, el->n);
            value_ctx_failure(step->ctx, &global->values, "Load: unknown variable %s", x+1);
            free(x);
            return;
        }
        value_ctx_push(&step->ctx, v);
    }
    step->ctx->pc++;
}

void op_LoadVar(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_LoadVar *el = env;
    assert((step->ctx->vars & VALUE_MASK) == VALUE_DICT);

    hvalue_t v;
    if (el == NULL) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        assert((av & VALUE_MASK) == VALUE_ADDRESS);
        assert(av != VALUE_ADDRESS);

        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);

        bool result;
        if (indices[0] == this_atom) {
            if ((step->ctx->this & VALUE_MASK) != VALUE_DICT) {
                value_ctx_failure(step->ctx, &global->values, "LoadVar: 'this' is not a dictionary");
                return;
            }
            result = ind_tryload(&global->values, step->ctx->this, &indices[1], size - 1, &v);
        }
        else {
            result = ind_tryload(&global->values, step->ctx->vars, indices, size, &v);
        }
        if (!result) {
            char *x = indices_string(indices, size);
            value_ctx_failure(step->ctx, &global->values, "LoadVar: bad address: %s", x);
            free(x);
            return;
        }
        value_ctx_push(&step->ctx, v);
    }
    else {
        if (el->name == this_atom) {
            value_ctx_push(&step->ctx, step->ctx->this);
        }
        else if (value_dict_tryload(&global->values, step->ctx->vars, el->name, &v)) {
            value_ctx_push(&step->ctx, v);
        }
        else {
            char *p = value_string(el->name);
            value_ctx_failure(step->ctx, &global->values, "LoadVar: unknown variable %s", p + 1);
            free(p);
            return;
        }
    }
    step->ctx->pc++;
}

void op_Move(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Move *em = env;
    struct context *ctx = step->ctx;
    int offset = ctx->sp - em->offset;

    hvalue_t v = ctx->stack[offset];
    memmove(&ctx->stack[offset], &ctx->stack[offset + 1],
                (em->offset - 1) * sizeof(hvalue_t));
    ctx->stack[ctx->sp - 1] = v;
    ctx->pc++;
}

void op_Nary(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Nary *en = env;
    hvalue_t args[MAX_ARITY];

    for (int i = 0; i < en->arity; i++) {
        args[i] = value_ctx_pop(&step->ctx);
    }
    hvalue_t result = (*en->fi->f)(state, step->ctx, args, en->arity, &global->values);
    if (step->ctx->failure == 0) {
        value_ctx_push(&step->ctx, result);
        step->ctx->pc++;
    }
}

void op_Pop(const void *env, struct state *state, struct step *step, struct global_t *global){
    assert(step->ctx->sp > 0);
    step->ctx->sp--;
    step->ctx->pc++;
}

void op_Push(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Push *ep = env;

    value_ctx_push(&step->ctx, ep->value);
    step->ctx->pc++;
}

void op_ReadonlyDec(const void *env, struct state *state, struct step *step, struct global_t *global){
    struct context *ctx = step->ctx;

    assert(ctx->readonly > 0);
    ctx->readonly--;
    ctx->pc++;
}

void op_ReadonlyInc(const void *env, struct state *state, struct step *step, struct global_t *global){
    struct context *ctx = step->ctx;

    ctx->readonly++;
    ctx->pc++;
}

// On the stack are:
//  - frame pointer
//  - saved variables
//  - saved argument for stack trace
//  - process, normal, or interrupt
//  - return address if normal or interrupt
void op_Return(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t result = value_dict_load(step->ctx->vars, value_put_atom(&global->values, "result", 6));
    hvalue_t fp = value_ctx_pop(&step->ctx);
    if ((fp & VALUE_MASK) != VALUE_INT) {
        printf("XXX %d %d %s\n", step->ctx->pc, step->ctx->sp, value_string(fp));
        value_ctx_failure(step->ctx, &global->values, "XXX");
        return;
        // exit(1);
    }
    assert((fp & VALUE_MASK) == VALUE_INT);
    step->ctx->fp = fp >> VALUE_BITS;
    hvalue_t oldvars = value_ctx_pop(&step->ctx);
    assert((oldvars & VALUE_MASK) == VALUE_DICT);
    step->ctx->vars = oldvars;
    (void) value_ctx_pop(&step->ctx);   // argument saved for stack trace
    if (step->ctx->sp == 0) {     // __init__
        step->ctx->terminated = true;
        return;
    }
    hvalue_t calltype = value_ctx_pop(&step->ctx);
    assert((calltype & VALUE_MASK) == VALUE_INT);
    switch (calltype >> VALUE_BITS) {
    case CALLTYPE_PROCESS:
        step->ctx->terminated = true;
        break;
    case CALLTYPE_NORMAL:
        {
            hvalue_t pc = value_ctx_pop(&step->ctx);
            assert((pc & VALUE_MASK) == VALUE_PC);
            pc >>= VALUE_BITS;
            assert(pc != step->ctx->pc);
            value_ctx_push(&step->ctx, result);
            step->ctx->pc = pc;
        }
        break;
    case CALLTYPE_INTERRUPT:
        step->ctx->interruptlevel = false;
        hvalue_t pc = value_ctx_pop(&step->ctx);
        assert((pc & VALUE_MASK) == VALUE_PC);
        pc >>= VALUE_BITS;
        assert(pc != step->ctx->pc);
        step->ctx->pc = pc;
        break;
    default:
        panic("op_Return: bad call type");
    }
}

void op_Sequential(const void *env, struct state *state, struct step *step, struct global_t *global){
    hvalue_t addr = value_ctx_pop(&step->ctx);
    if ((addr & VALUE_MASK) != VALUE_ADDRESS) {
        char *p = value_string(addr);
        value_ctx_failure(step->ctx, &global->values, "Sequential %s: not an address", p);
        free(p);
        return;
    }

    /* Insert in state's set of sequential variables.
     */
    int size;
    hvalue_t *seqs = value_copy(state->seqs, &size);
    size /= sizeof(hvalue_t);
    int i;
    for (i = 0; i < size; i++) {
        int k = value_cmp(seqs[i], addr);
        if (k == 0) {
            free(seqs);
            step->ctx->pc++;
            return;
        }
        if (k > 0) {
            break;
        }
    }
    seqs = realloc(seqs, (size + 1) * sizeof(hvalue_t));
    memmove(&seqs[i + 1], &seqs[i], (size - i) * sizeof(hvalue_t));
    seqs[i] = addr;
    state->seqs = value_put_set(&global->values, seqs, (size + 1) * sizeof(hvalue_t));
    free(seqs);
    step->ctx->pc++;
}

// sort two key/value pairs
static int q_kv_cmp(const void *e1, const void *e2){
    const hvalue_t *kv1 = (const hvalue_t *) e1;
    const hvalue_t *kv2 = (const hvalue_t *) e2;

    int k = value_cmp(kv1[0], kv2[0]);
    if (k != 0) {
        return k;
    }
    return value_cmp(kv1[1], kv2[1]);
}

static int q_value_cmp(const void *v1, const void *v2){
    return value_cmp(* (const hvalue_t *) v1, * (const hvalue_t *) v2);
}

// Sort the resulting set and remove duplicates
static int sort(hvalue_t *vals, int n){
    qsort(vals, n, sizeof(hvalue_t), q_value_cmp);

    hvalue_t *p = vals, *q = vals + 1;
    for (int i = 1; i < n; i++, q++) {
        if (*q != *p) {
            *++p = *q;
        }
    }
    p++;
    return p - vals;
}

void op_SetIntLevel(const void *env, struct state *state, struct step *step, struct global_t *global){
	bool oldlevel = step->ctx->interruptlevel;
	hvalue_t newlevel =  value_ctx_pop(&step->ctx);
    if ((newlevel & VALUE_MASK) != VALUE_BOOL) {
        value_ctx_failure(step->ctx, &global->values, "setintlevel can only be set to a boolean");
        return;
    }
    step->ctx->interruptlevel = newlevel >> VALUE_BITS;
	value_ctx_push(&step->ctx, (oldlevel << VALUE_BITS) | VALUE_BOOL);
    step->ctx->pc++;
}

void op_Spawn(
    const void *env,
    struct state *state,
    struct step *step,
    struct global_t *global
) {
    const struct env_Spawn *se = env;

    hvalue_t thisval = value_ctx_pop(&step->ctx);
    hvalue_t arg = value_ctx_pop(&step->ctx);

    hvalue_t pc = value_ctx_pop(&step->ctx);
    if ((pc & VALUE_MASK) != VALUE_PC) {
        value_ctx_failure(step->ctx, &global->values, "spawn: not a method");
        return;
    }
    pc >>= VALUE_BITS;

    assert(pc < global->code.len);
    assert(strcmp(global->code.instrs[pc].oi->name, "Frame") == 0);

    struct context *ctx = new_alloc(struct context);

    const struct env_Frame *ef = global->code.instrs[pc].env;
    ctx->name = ef->name;
    ctx->arg = arg;
    ctx->this = thisval;
    ctx->entry = (pc << VALUE_BITS) | VALUE_PC;
    ctx->pc = pc;
    ctx->vars = VALUE_DICT;
    ctx->interruptlevel = VALUE_FALSE;
    ctx->eternal = se->eternal;
    value_ctx_push(&ctx, (CALLTYPE_PROCESS << VALUE_BITS) | VALUE_INT);
    value_ctx_push(&ctx, arg);
    hvalue_t v = value_put_context(&global->values, ctx);
    state->ctxbag = value_bag_add(&global->values, state->ctxbag, v, 1);
    step->ctx->pc++;
}

void op_Split(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Split *es = env;

    hvalue_t v = value_ctx_pop(&step->ctx);
    hvalue_t type = v & VALUE_MASK;
    if (type != VALUE_DICT && type != VALUE_SET) {
        value_ctx_failure(step->ctx, &global->values, "Can only split tuples or sets");
        return;
    }
    if (v == VALUE_DICT || v == VALUE_SET) {
        if (es->count != 0) {
            value_ctx_failure(step->ctx, &global->values, "Split: empty set or tuple");
        }
        else {
            step->ctx->pc++;
        }
        return;
    }

    int size;
    hvalue_t *vals = value_get(v, &size);

    if (type == VALUE_DICT) {
        size /= 2 * sizeof(hvalue_t);
        if (size != es->count) {
            value_ctx_failure(step->ctx, &global->values, "Split: list of wrong size");
            return;
        }
        for (int i = 0; i < size; i++) {
            value_ctx_push(&step->ctx, vals[2*i + 1]);
        }
        step->ctx->pc++;
        return;
    }
    if (type == VALUE_SET) {
        size /= sizeof(hvalue_t);
        if (size != es->count) {
            value_ctx_failure(step->ctx, &global->values, "Split: set of wrong size");
            return;
        }
        for (int i = 0; i < size; i++) {
            value_ctx_push(&step->ctx, vals[i]);
        }
        step->ctx->pc++;
        return;
    }
    panic("op_Split: not a set or dict");
}

void op_Save(const void *env, struct state *state, struct step *step, struct global_t *global){
    assert((state->vars & VALUE_MASK) == VALUE_DICT);
    hvalue_t e = value_ctx_pop(&step->ctx);

    // Save the context
    step->ctx->stopped = true;
    step->ctx->pc++;
    hvalue_t v = value_put_context(&global->values, step->ctx);

    // Restore the stopped mode to false
    step->ctx->stopped = false;

    // Push a tuple returning e and the context
    hvalue_t d[4];
    d[0] = ((hvalue_t) 0 << VALUE_BITS) | VALUE_INT;
    d[1] = e;
    d[2] = ((hvalue_t) 1 << VALUE_BITS) | VALUE_INT;
    d[3] = v;
    hvalue_t result = value_put_dict(&global->values, d, sizeof(d));
    value_ctx_push(&step->ctx, result);
}

void op_Stop(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Stop *es = env;

    assert((state->vars & VALUE_MASK) == VALUE_DICT);

    if (step->ctx->readonly > 0) {
        value_ctx_failure(step->ctx, &global->values, "Stop: in read-only mode");
        return;
    }

    if (es == 0) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        if (av == VALUE_ADDRESS || av == VALUE_DICT) {
            step->ctx->pc++;
            step->ctx->terminated = true;
            return;
        }

        if ((av & VALUE_MASK) != VALUE_ADDRESS) {
            char *p = value_string(av);
            value_ctx_failure(step->ctx, &global->values, "Stop %s: not an address", p);
            free(p);
            return;
        }
        step->ctx->pc++;

        step->ctx->stopped = true;
        hvalue_t v = value_put_context(&global->values, step->ctx);
        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);
        if (!ind_trystore(state->vars, indices, size, v, &global->values, &state->vars)) {
            char *x = indices_string(indices, size);
            value_ctx_failure(step->ctx, &global->values, "Stop: bad address: %s", x);
            free(x);
        }
    }
    else {
        step->ctx->stopped = true;
        step->ctx->pc++;
        hvalue_t v = value_put_context(&global->values, step->ctx);
        if (!ind_trystore(state->vars, es->indices, es->n, v, &global->values, &state->vars)) {
            value_ctx_failure(step->ctx, &global->values, "Store: bad variable");
        }
    }
}

void op_Store(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_Store *es = env;

    assert((state->vars & VALUE_MASK) == VALUE_DICT);

    if (step->ctx->readonly > 0) {
        value_ctx_failure(step->ctx, &global->values, "Can't update state in assert or invariant (including acquiring locks)");
        return;
    }

    hvalue_t v = value_ctx_pop(&step->ctx);

    if (es == 0) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        if ((av & VALUE_MASK) != VALUE_ADDRESS) {
            char *p = value_string(av);
            value_ctx_failure(step->ctx, &global->values, "Store %s: not an address", p);
            free(p);
            return;
        }
        if (av == VALUE_ADDRESS) {
            value_ctx_failure(step->ctx, &global->values, "Store: address is None");
            return;
        }

        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);
        if (step->ai != NULL) {
            step->ai->indices = indices;
            step->ai->n = size;
            step->ai->load = is_sequential(state->seqs, step->ai->indices, step->ai->n);
        }

        if (!ind_trystore(state->vars, indices, size, v, &global->values, &state->vars)) {
            char *x = indices_string(indices, size);
            value_ctx_failure(step->ctx, &global->values, "Store: bad address: %s", x);
            free(x);
            return;
        }
    }
    else {
        if (step->ai != NULL) {
            step->ai->indices = es->indices;
            step->ai->n = es->n;
            step->ai->load = is_sequential(state->seqs, step->ai->indices, step->ai->n);
        }
        if (!ind_trystore(state->vars, es->indices, es->n, v, &global->values, &state->vars)) {
            value_ctx_failure(step->ctx, &global->values, "Store: bad variable");
            return;
        }
    }
    step->ctx->pc++;
}

void op_StoreVar(const void *env, struct state *state, struct step *step, struct global_t *global){
    const struct env_StoreVar *es = env;
    hvalue_t v = value_ctx_pop(&step->ctx);

    assert((step->ctx->vars & VALUE_MASK) == VALUE_DICT);
    if (es == NULL) {
        hvalue_t av = value_ctx_pop(&step->ctx);
        assert((av & VALUE_MASK) == VALUE_ADDRESS);
        assert(av != VALUE_ADDRESS);

        int size;
        hvalue_t *indices = value_get(av, &size);
        size /= sizeof(hvalue_t);

        bool result;
        if (indices[0] == this_atom) {
            if ((step->ctx->this & VALUE_MASK) != VALUE_DICT) {
                value_ctx_failure(step->ctx, &global->values, "StoreVar: 'this' is not a dictionary");
                return;
            }
            result = ind_trystore(step->ctx->this, &indices[1], size - 1, v, &global->values, &step->ctx->this);
        }

        else {
            result = ind_trystore(step->ctx->vars, indices, size, v, &global->values, &step->ctx->vars);
        }
        if (!result) {
            char *x = indices_string(indices, size);
            value_ctx_failure(step->ctx, &global->values, "StoreVar: bad address: %s", x);
            free(x);
            return;
        }
        step->ctx->pc++;
    }
    else {
        if (es->args->type == VT_NAME && es->args->u.name == this_atom) {
            step->ctx->this = v;
            step->ctx->pc++;
        }
        else {
            var_match(step->ctx, es->args, &global->values, v);
            if (step->ctx->failure == 0) {
                step->ctx->pc++;
            }
        }
    }
}

void op_Trap(const void *env, struct state *state, struct step *step, struct global_t *global){
    step->ctx->trap_pc = value_ctx_pop(&step->ctx);
    if ((step->ctx->trap_pc & VALUE_MASK) != VALUE_PC) {
        value_ctx_failure(step->ctx, &global->values, "trap: not a method");
        return;
    }
    assert((step->ctx->trap_pc >> VALUE_BITS) < global->code.len);
    assert(strcmp(global->code.instrs[step->ctx->trap_pc >> VALUE_BITS].oi->name, "Frame") == 0);
    step->ctx->trap_arg = value_ctx_pop(&step->ctx);
    step->ctx->pc++;
}

void *init_Address(struct dict *map, struct values_t *values){ return NULL; }
void *init_Apply(struct dict *map, struct values_t *values){ return NULL; }
void *init_Assert(struct dict *map, struct values_t *values){ return NULL; }
void *init_Assert2(struct dict *map, struct values_t *values){ return NULL; }
void *init_AtomicDec(struct dict *map, struct values_t *values){ return NULL; }
void *init_Choose(struct dict *map, struct values_t *values){ return NULL; }
void *init_Continue(struct dict *map, struct values_t *values){ return NULL; }
void *init_Dup(struct dict *map, struct values_t *values){ return NULL; }
void *init_Go(struct dict *map, struct values_t *values){ return NULL; }
void *init_Print(struct dict *map, struct values_t *values){ return NULL; }
void *init_Pop(struct dict *map, struct values_t *values){ return NULL; }
void *init_ReadonlyDec(struct dict *map, struct values_t *values){ return NULL; }
void *init_ReadonlyInc(struct dict *map, struct values_t *values){ return NULL; }
void *init_Return(struct dict *map, struct values_t *values){ return NULL; }
void *init_Save(struct dict *map, struct values_t *values){ return NULL; }
void *init_Sequential(struct dict *map, struct values_t *values){ return NULL; }
void *init_SetIntLevel(struct dict *map, struct values_t *values){ return NULL; }
void *init_Trap(struct dict *map, struct values_t *values){ return NULL; }

void *init_Cut(struct dict *map, struct values_t *values){
    struct env_Cut *env = new_alloc(struct env_Cut);
    struct json_value *set = dict_lookup(map, "set", 3);
    assert(set->type == JV_ATOM);
    env->set = value_put_atom(values, set->u.atom.base, set->u.atom.len);

    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_ATOM);
    int index = 0;
    env->value = var_parse(values, value->u.atom.base, value->u.atom.len, &index);

    struct json_value *key = dict_lookup(map, "key", 3);
    if (key != NULL) {
        assert(key->type == JV_ATOM);
        index = 0;
        env->key = var_parse(values, key->u.atom.base, key->u.atom.len, &index);
    }

    return env;
}

void *init_AtomicInc(struct dict *map, struct values_t *values){
    struct env_AtomicInc *env = new_alloc(struct env_AtomicInc);
    struct json_value *lazy = dict_lookup(map, "lazy", 4);
    if (lazy == NULL) {
        env->lazy = false;
    }
    else {
		assert(lazy->type == JV_ATOM);
        if (lazy->u.atom.len == 0) {
            env->lazy = false;
        }
        else {
            char *p = lazy->u.atom.base;
            env->lazy = *p == 't' || *p == 'T';
        }
    }
    return env;
}

void *init_Del(struct dict *map, struct values_t *values){
    struct json_value *jv = dict_lookup(map, "value", 5);
    if (jv == NULL) {
        return NULL;
    }
    assert(jv->type == JV_LIST);
    struct env_Del *env = new_alloc(struct env_Del);
    env->n = jv->u.list.nvals;
    env->indices = malloc(env->n * sizeof(hvalue_t));
    for (int i = 0; i < env->n; i++) {
        struct json_value *index = jv->u.list.vals[i];
        assert(index->type == JV_MAP);
        env->indices[i] = value_from_json(values, index->u.map);
    }
    return env;
}

void *init_DelVar(struct dict *map, struct values_t *values){
    struct json_value *name = dict_lookup(map, "value", 5);
	if (name == NULL) {
		return NULL;
	}
	else {
		struct env_DelVar *env = new_alloc(struct env_DelVar);
		assert(name->type == JV_ATOM);
		env->name = value_put_atom(values, name->u.atom.base, name->u.atom.len);
		return env;
	}
}

void *init_Frame(struct dict *map, struct values_t *values){
    struct env_Frame *env = new_alloc(struct env_Frame);

    struct json_value *name = dict_lookup(map, "name", 4);
    assert(name->type == JV_ATOM);
    env->name = value_put_atom(values, name->u.atom.base, name->u.atom.len);

    struct json_value *args = dict_lookup(map, "args", 4);
    assert(args->type == JV_ATOM);
    int index = 0;
    env->args = var_parse(values, args->u.atom.base, args->u.atom.len, &index);

    return env;
}

void *init_IncVar(struct dict *map, struct values_t *values){
    struct env_IncVar *env = new_alloc(struct env_IncVar);
    struct json_value *name = dict_lookup(map, "value", 5);
    assert(name->type == JV_ATOM);
    env->name = value_put_atom(values, name->u.atom.base, name->u.atom.len);
    return env;
}

void *init_Load(struct dict *map, struct values_t *values){
    struct json_value *jv = dict_lookup(map, "value", 5);
    if (jv == NULL) {
        return NULL;
    }
    assert(jv->type == JV_LIST);
    struct env_Load *env = new_alloc(struct env_Load);
    env->n = jv->u.list.nvals;
    env->indices = malloc(env->n * sizeof(hvalue_t));
    for (int i = 0; i < env->n; i++) {
        struct json_value *index = jv->u.list.vals[i];
        assert(index->type == JV_MAP);
        env->indices[i] = value_from_json(values, index->u.map);
    }
    return env;
}

void *init_LoadVar(struct dict *map, struct values_t *values){
    struct json_value *value = dict_lookup(map, "value", 5);
    if (value == NULL) {
        return NULL;
    }
    else {
        struct env_LoadVar *env = new_alloc(struct env_LoadVar);
        assert(value->type == JV_ATOM);
        env->name = value_put_atom(values, value->u.atom.base, value->u.atom.len);
        return env;
    }
}

void *init_Move(struct dict *map, struct values_t *values){
    struct env_Move *env = new_alloc(struct env_Move);

    struct json_value *offset = dict_lookup(map, "offset", 6);
    assert(offset->type == JV_ATOM);
    char *copy = malloc(offset->u.atom.len + 1);
    memcpy(copy, offset->u.atom.base, offset->u.atom.len);
    copy[offset->u.atom.len] = 0;
    env->offset = atoi(copy);
    free(copy);

    return env;
}

void *init_Nary(struct dict *map, struct values_t *values){
    struct env_Nary *env = new_alloc(struct env_Nary);

    struct json_value *arity = dict_lookup(map, "arity", 5);
    assert(arity->type == JV_ATOM);
    char *copy = malloc(arity->u.atom.len + 1);
    memcpy(copy, arity->u.atom.base, arity->u.atom.len);
    copy[arity->u.atom.len] = 0;
    env->arity = atoi(copy);
    free(copy);

    struct json_value *op = dict_lookup(map, "value", 5);
    assert(op->type == JV_ATOM);
    struct f_info *fi = dict_lookup(f_map, op->u.atom.base, op->u.atom.len);
    if (fi == NULL) {
        fprintf(stderr, "Nary: unknown function '%.*s'\n", op->u.atom.len, op->u.atom.base);
        exit(1);
    }
    env->fi = fi;

    return env;
}

void *init_Invariant(struct dict *map, struct values_t *values){
    struct env_Invariant *env = new_alloc(struct env_Invariant);

    struct json_value *end = dict_lookup(map, "end", 3);
    assert(end->type == JV_ATOM);
    char *copy = malloc(end->u.atom.len + 1);
    memcpy(copy, end->u.atom.base, end->u.atom.len);
    copy[end->u.atom.len] = 0;
    env->end = atoi(copy);
    free(copy);
    return env;
}

void *init_Jump(struct dict *map, struct values_t *values){
    struct env_Jump *env = new_alloc(struct env_Jump);

    struct json_value *pc = dict_lookup(map, "pc", 2);
    assert(pc->type == JV_ATOM);
    char *copy = malloc(pc->u.atom.len + 1);
    memcpy(copy, pc->u.atom.base, pc->u.atom.len);
    copy[pc->u.atom.len] = 0;
    env->pc = atoi(copy);
    free(copy);
    return env;
}

void *init_JumpCond(struct dict *map, struct values_t *values){
    struct env_JumpCond *env = new_alloc(struct env_JumpCond);

    struct json_value *pc = dict_lookup(map, "pc", 2);
    assert(pc->type == JV_ATOM);
    char *copy = malloc(pc->u.atom.len + 1);
    memcpy(copy, pc->u.atom.base, pc->u.atom.len);
    copy[pc->u.atom.len] = 0;
    env->pc = atoi(copy);
    free(copy);

    struct json_value *cond = dict_lookup(map, "cond", 4);
    assert(cond->type == JV_MAP);
    env->cond = value_from_json(values, cond->u.map);

    return env;
}

void *init_Push(struct dict *map, struct values_t *values) {
    struct json_value *jv = dict_lookup(map, "value", 5);
    assert(jv->type == JV_MAP);
    struct env_Push *env = new_alloc(struct env_Push);
    env->value = value_from_json(values, jv->u.map);
    return env;
}

void *init_Spawn(struct dict *map, struct values_t *values){
    struct env_Spawn *env = new_alloc(struct env_Spawn);
    struct json_value *eternal = dict_lookup(map, "eternal", 7);
    if (eternal == NULL) {
        env->eternal = false;
    }
    else {
		assert(eternal->type == JV_ATOM);
        if (eternal->u.atom.len == 0) {
            env->eternal = false;
        }
        else {
            char *p = eternal->u.atom.base;
            env->eternal = *p == 't' || *p == 'T';
        }
    }
    return env;
}

void *init_Split(struct dict *map, struct values_t *values){
    struct env_Split *env = new_alloc(struct env_Split);

    struct json_value *count = dict_lookup(map, "count", 5);
    assert(count->type == JV_ATOM);
    char *copy = malloc(count->u.atom.len + 1);
    memcpy(copy, count->u.atom.base, count->u.atom.len);
    copy[count->u.atom.len] = 0;
    env->count = atoi(copy);
    free(copy);
    return env;
}

void *init_Stop(struct dict *map, struct values_t *values){
    struct json_value *jv = dict_lookup(map, "value", 5);
    if (jv == NULL) {
        return NULL;
    }
    assert(jv->type == JV_LIST);
    struct env_Stop *env = new_alloc(struct env_Stop);
    env->n = jv->u.list.nvals;
    env->indices = malloc(env->n * sizeof(hvalue_t));
    for (int i = 0; i < env->n; i++) {
        struct json_value *index = jv->u.list.vals[i];
        assert(index->type == JV_MAP);
        env->indices[i] = value_from_json(values, index->u.map);
    }
    return env;
}

void *init_Store(struct dict *map, struct values_t *values){
    struct json_value *jv = dict_lookup(map, "value", 5);
    if (jv == NULL) {
        return NULL;
    }
    assert(jv->type == JV_LIST);
    struct env_Store *env = new_alloc(struct env_Store);
    env->n = jv->u.list.nvals;
    env->indices = malloc(env->n * sizeof(hvalue_t));
    for (int i = 0; i < env->n; i++) {
        struct json_value *index = jv->u.list.vals[i];
        assert(index->type == JV_MAP);
        env->indices[i] = value_from_json(values, index->u.map);
    }
    return env;
}

void *init_StoreVar(struct dict *map, struct values_t *values){
    struct json_value *jv = dict_lookup(map, "value", 5);
    if (jv == NULL) {
        return NULL;
    }
    else {
        assert(jv->type == JV_ATOM);
        struct env_StoreVar *env = new_alloc(struct env_StoreVar);
        int index = 0;
        env->args = var_parse(values, jv->u.atom.base, jv->u.atom.len, &index);
        return env;
    }
}

hvalue_t f_abs(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
    if ((e & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "abs() can only be applied to integers");
    }
    e >>= VALUE_BITS;
    return e >= 0 ? args[0] : (((-e) << VALUE_BITS) | VALUE_INT);
}

hvalue_t f_all(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
	if (e == VALUE_SET || e == VALUE_DICT) {
		return VALUE_TRUE;
    }
    if ((e & VALUE_MASK) == VALUE_SET) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= sizeof(hvalue_t);
        for (int i = 0; i < size; i++) {
            if ((v[i] & VALUE_MASK) != VALUE_BOOL) {
                return value_ctx_failure(ctx, values, "set.all() can only be applied to booleans");
            }
            if (v[i] == VALUE_FALSE) {
                return VALUE_FALSE;
            }
        }
		return VALUE_TRUE;
    }
    if ((e & VALUE_MASK) == VALUE_DICT) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= 2 * sizeof(hvalue_t);
        for (int i = 0; i < size; i++) {
            if ((v[2*i+1] & VALUE_MASK) != VALUE_BOOL) {
                return value_ctx_failure(ctx, values, "dict.all() can only be applied to booleans");
            }
            if (v[2*i+1] == VALUE_FALSE) {
                return VALUE_FALSE;
            }
        }
		return VALUE_TRUE;
    }
    return value_ctx_failure(ctx, values, "all() can only be applied to sets or dictionaries");
}

hvalue_t f_any(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
	if (e == VALUE_SET || e == VALUE_DICT) {
		return VALUE_FALSE;
    }
    if ((e & VALUE_MASK) == VALUE_SET) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= sizeof(hvalue_t);
        for (int i = 0; i < size; i++) {
            if ((v[i] & VALUE_MASK) != VALUE_BOOL) {
                return value_ctx_failure(ctx, values, "set.any() can only be applied to booleans");
            }
            if (v[i] != VALUE_FALSE) {
                return VALUE_TRUE;
            }
        }
		return VALUE_FALSE;
    }
    if ((e & VALUE_MASK) == VALUE_DICT) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= 2 * sizeof(hvalue_t);
        for (int i = 0; i < size; i++) {
            if ((v[2*i+1] & VALUE_MASK) != VALUE_BOOL) {
                return value_ctx_failure(ctx, values, "dict.any() can only be applied to booleans");
            }
            if (v[2*i+1] != VALUE_FALSE) {
                return VALUE_TRUE;
            }
        }
		return VALUE_FALSE;
    }
    return value_ctx_failure(ctx, values, "any() can only be applied to sets or dictionaries");
}

hvalue_t nametag(struct context *ctx, struct values_t *values){
    hvalue_t nt = value_dict_store(values, VALUE_DICT,
            (0 << VALUE_BITS) | VALUE_INT, ctx->entry);
    return value_dict_store(values, nt,
            (1 << VALUE_BITS) | VALUE_INT, ctx->arg);
}

hvalue_t f_atLabel(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    if (ctx->atomic == 0) {
        return value_ctx_failure(ctx, values, "atLabel: can only be called in atomic mode");
    }
    hvalue_t e = args[0];
    if ((e & VALUE_MASK) != VALUE_PC) {
        return value_ctx_failure(ctx, values, "atLabel: not a label");
    }
    e >>= VALUE_BITS;

    int size;
    hvalue_t *vals = value_get(state->ctxbag, &size);
    size /= sizeof(hvalue_t);
    assert(size > 0);
    assert(size % 2 == 0);
    hvalue_t bag = VALUE_DICT;
    for (int i = 0; i < size; i += 2) {
        assert((vals[i] & VALUE_MASK) == VALUE_CONTEXT);
        assert((vals[i+1] & VALUE_MASK) == VALUE_INT);
        struct context *ctx = value_get(vals[i], NULL);
        if ((hvalue_t) ctx->pc == e) {
            bag = value_bag_add(values, bag, nametag(ctx, values),
                (int) (vals[i+1] >> VALUE_BITS));
        }
    }
    return bag;
}

hvalue_t f_countLabel(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    if (ctx->atomic == 0) {
        return value_ctx_failure(ctx, values, "countLabel: can only be called in atomic mode");
    }
    hvalue_t e = args[0];
    if ((e & VALUE_MASK) != VALUE_PC) {
        return value_ctx_failure(ctx, values, "countLabel: not a label");
    }
    e >>= VALUE_BITS;

    int size;
    hvalue_t *vals = value_get(state->ctxbag, &size);
    size /= sizeof(hvalue_t);
    assert(size > 0);
    assert(size % 2 == 0);
    hvalue_t result = 0;
    for (int i = 0; i < size; i += 2) {
        assert((vals[i] & VALUE_MASK) == VALUE_CONTEXT);
        assert((vals[i+1] & VALUE_MASK) == VALUE_INT);
        struct context *ctx = value_get(vals[i], NULL);
        if ((hvalue_t) ctx->pc == e) {
            result += vals[i+1] >> VALUE_BITS;;
        }
    }
    return (result << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_div(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    int64_t e1 = args[0], e2 = args[1];
    if ((e1 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "right argument to / not an integer");
    }
    if ((e2 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "left argument to / not an integer");
    }
    e1 >>= VALUE_BITS;
    if (e1 == 0) {
        return value_ctx_failure(ctx, values, "divide by zero");
    }
    int64_t result = (e2 >> VALUE_BITS) / e1;
    return (result << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_eq(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    return ((args[0] == args[1]) << VALUE_BITS) | VALUE_BOOL;
}

hvalue_t f_ge(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int cmp = value_cmp(args[1], args[0]);
    return ((cmp >= 0) << VALUE_BITS) | VALUE_BOOL;
}

hvalue_t f_gt(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int cmp = value_cmp(args[1], args[0]);
    return ((cmp > 0) << VALUE_BITS) | VALUE_BOOL;
}

hvalue_t f_ne(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    return ((args[0] != args[1]) << VALUE_BITS) | VALUE_BOOL;
}

hvalue_t f_in(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    hvalue_t s = args[0], e = args[1];
	if (s == VALUE_SET || s == VALUE_DICT) {
		return VALUE_FALSE;
	}
    if ((s & VALUE_MASK) == VALUE_ATOM) {
        if ((e & VALUE_MASK) != VALUE_ATOM) {
            return value_ctx_failure(ctx, values, "'in <string>' can only be applied to another string");
        }
        if (s == VALUE_ATOM) {
            return ((e == VALUE_ATOM) << VALUE_BITS) | VALUE_BOOL;
        }
        int size1, size2;
        char *v1 = value_get(e, &size1);
        char *v2 = value_get(s, &size2);
        if (size1 > size2) {
            return VALUE_FALSE;
        }
        // stupid way of checking if v1 is a substring of v2
        int n = size2 - size1;
        for (int i = 0; i <= n; i++) {
            if (memcmp(v1, v2 + i, size1) == 0) {
                return VALUE_TRUE;
            }
        }
        return VALUE_FALSE;
    }
    if ((s & VALUE_MASK) == VALUE_SET) {
        int size;
        hvalue_t *v = value_get(s, &size);
        size /= sizeof(hvalue_t);
        for (int i = 0; i < size; i++) {
            if (v[i] == e) {
                return VALUE_TRUE;
            }
        }
        return VALUE_FALSE;
    }
    if ((s & VALUE_MASK) == VALUE_DICT) {
        int size;
        hvalue_t *v = value_get(s, &size);
        size /= 2 * sizeof(hvalue_t);
        for (int i = 0; i < size; i++) {
            if (v[2*i+1] == e) {
                return VALUE_TRUE;
            }
        }
        return VALUE_FALSE;
    }
    return value_ctx_failure(ctx, values, "'in' can only be applied to sets or dictionaries");
}

hvalue_t f_intersection(
    struct state *state,
    struct context *ctx,
    hvalue_t *args,
    int n,
    struct values_t *values
) {
    hvalue_t e1 = args[0];

    if ((e1 & VALUE_MASK) == VALUE_INT) {
        for (int i = 1; i < n; i++) {
            hvalue_t e2 = args[i];
            if ((e2 & VALUE_MASK) != VALUE_INT) {
                return value_ctx_failure(ctx, values, "'&' applied to mix of ints and other types");
            }
            e1 &= e2;
        }
        return e1;
    }
	if (e1 == VALUE_SET) {
		return VALUE_SET;
	}
    if ((e1 & VALUE_MASK) == VALUE_SET) {
        // get all the sets
		assert(n > 0);
        struct val_info *vi = malloc(n * sizeof(*vi));
		vi[0].vals = value_get(args[0], &vi[0].size); 
		vi[0].index = 0;
        int min_size = vi[0].size;              // minimum set size
        hvalue_t max_val = vi[0].vals[0];       // maximum value over the minima of all sets
        for (int i = 1; i < n; i++) {
            if ((args[i] & VALUE_MASK) != VALUE_SET) {
                return value_ctx_failure(ctx, values, "'&' applied to mix of sets and other types");
            }
            if (args[i] == VALUE_SET) {
                min_size = 0;
            }
            else {
                vi[i].vals = value_get(args[i], &vi[i].size); 
                vi[i].index = 0;
				if (vi[i].size < min_size) {
					min_size = vi[i].size;
				}
				if (value_cmp(vi[i].vals[0], max_val) > 0) {
					max_val = vi[i].vals[0];
				}
            }
        }

        // If any are empty lists, we're done.
        if (min_size == 0) {
            return VALUE_SET;
        }

        // Allocate sufficient memory.
        hvalue_t *vals = malloc((size_t) min_size), *v = vals;

        bool done = false;
        for (int i = 0; i < min_size; i++) {
            hvalue_t old_max = max_val;
            for (int j = 0; j < n; j++) {
                int k, size = vi[j].size / sizeof(hvalue_t);
                while ((k = vi[j].index) < size) {
                    hvalue_t v = vi[j].vals[k];
                    int cmp = value_cmp(v, max_val);
                    if (cmp > 0) {
                        max_val = v;
                    }
                    if (cmp >= 0) {
                        break;
                    }
                    vi[j].index++;
                }
                if (vi[j].index == size) {
                    done = true;
                    break;
                }
            }
            if (done) {
                break;
            }
            if (old_max == max_val) {
                *v++ = max_val;
                for (int j = 0; j < n; j++) {
                    assert(vi[j].index < vi[j].size / sizeof(hvalue_t));
                    vi[j].index++;
                    int k, size = vi[j].size / sizeof(hvalue_t);
                    if ((k = vi[j].index) == size) {
                        done = true;
                        break;
                    }
                    if (value_cmp(vi[j].vals[k], max_val) > 0) {
                        max_val = vi[j].vals[k];
                    }
                }
            }
            if (done) {
                break;
            }
        }

        hvalue_t result = value_put_set(values, vals, (char *) v - (char *) vals);
        free(vi);
        free(vals);
        return result;
    }

	if (e1 == VALUE_DICT) {
		return VALUE_DICT;
	}
    if ((e1 & VALUE_MASK) != VALUE_DICT) {
        return value_ctx_failure(ctx, values, "'&' can only be applied to ints and dicts");
    }
    // get all the dictionaries
    struct val_info *vi = malloc(n * sizeof(*vi));
    int total = 0;
    for (int i = 0; i < n; i++) {
        if ((args[i] & VALUE_MASK) != VALUE_DICT) {
            return value_ctx_failure(ctx, values, "'&' applied to mix of dictionaries and other types");
        }
        if (args[i] == VALUE_DICT) {
            vi[i].vals = NULL;
            vi[i].size = 0;
        }
        else {
            vi[i].vals = value_get(args[i], &vi[i].size); 
            total += vi[i].size;
        }
    }

    // If all are empty dictionaries, we're done.
    if (total == 0) {
        return VALUE_DICT;
    }

    // Concatenate the dictionaries
    hvalue_t *vals = malloc(total);
    total = 0;
    for (int i = 0; i < n; i++) {
        memcpy((char *) vals + total, vi[i].vals, vi[i].size);
        total += vi[i].size;
    }

    // sort lexicographically, leaving duplicate keys
    int cnt = total / (2 * sizeof(hvalue_t));
    qsort(vals, cnt, 2 * sizeof(hvalue_t), q_kv_cmp);

    // now only leave the min value for duplicate keys
    int in = 0, out = 0;
    for (;;) {
        // if there are fewer than n copies of the key, then it's out
        if (in + n > cnt) {
            break;
        }
        int i;
        for (i = in + 1; i < in + n; i++) {
            if (vals[2*i] != vals[2*in]) {
                break;
            }
        }
        if (i == in + n) {
            // copy over the first value
            vals[2*out] = vals[2*in];
            vals[2*out+1] = vals[2*in+1];
            out++;
        }
        in = i;
    }

    hvalue_t result = value_put_dict(values, vals, 2 * out * sizeof(hvalue_t));
    free(vi);
    free(vals);
    return result;
}

hvalue_t f_invert(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    int64_t e = args[0];
    if ((e & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "~ can only be applied to ints");
    }
    e >>= VALUE_BITS;
    return ((~e) << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_isEmpty(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
    if ((e & VALUE_MASK) == VALUE_DICT) {
        return ((e == VALUE_DICT) << VALUE_BITS) | VALUE_BOOL;
    }
    if ((e & VALUE_MASK) == VALUE_SET) {
        return ((e == VALUE_SET) << VALUE_BITS) | VALUE_BOOL;
    }
    if ((e & VALUE_MASK) == VALUE_ATOM) {
        return ((e == VALUE_ATOM) << VALUE_BITS) | VALUE_BOOL;
    }
    return value_ctx_failure(ctx, values, "loops can only iterate over dictionaries and sets");
}

hvalue_t f_keys(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t v = args[0];
    if ((v & VALUE_MASK) != VALUE_DICT) {
        return value_ctx_failure(ctx, values, "keys() can only be applied to dictionaries");
    }
    if (v == VALUE_DICT) {
        return VALUE_SET;
    }

    int size;
    hvalue_t *vals = value_get(v, &size);
    hvalue_t *keys = malloc(size / 2);
    size /= 2 * sizeof(hvalue_t);
    for (int i = 0; i < size; i++) {
        keys[i] = vals[2*i];
    }
    hvalue_t result = value_put_set(values, keys, size * sizeof(hvalue_t));
    free(keys);
    return result;
}

hvalue_t f_str(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
    char *s = value_string(e);
    hvalue_t v = value_put_atom(values, s, strlen(s));
    free(s);
    return v;
}

hvalue_t f_len(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
	if (e == VALUE_SET || e == VALUE_DICT) {
		return VALUE_INT;
	}
    if ((e & VALUE_MASK) == VALUE_SET) {
        int size;
        (void) value_get(e, &size);
        size /= sizeof(hvalue_t);
        return (size << VALUE_BITS) | VALUE_INT;
    }
    if ((e & VALUE_MASK) == VALUE_DICT) {
        int size;
        (void) value_get(e, &size);
        size /= 2 * sizeof(hvalue_t);
        return (size << VALUE_BITS) | VALUE_INT;
    }
    if ((e & VALUE_MASK) == VALUE_ATOM) {
        int size;
        (void) value_get(e, &size);
        return (size << VALUE_BITS) | VALUE_INT;
    }
    return value_ctx_failure(ctx, values, "len() can only be applied to sets, dictionaries, or strings");
}

hvalue_t f_le(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int cmp = value_cmp(args[1], args[0]);
    return ((cmp <= 0) << VALUE_BITS) | VALUE_BOOL;
}

hvalue_t f_lt(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int cmp = value_cmp(args[1], args[0]);
    return ((cmp < 0) << VALUE_BITS) | VALUE_BOOL;
}

hvalue_t f_max(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
	if (e == VALUE_SET) {
        return value_ctx_failure(ctx, values, "can't apply max() to empty set");
    }
    if (e == VALUE_DICT) {
        return value_ctx_failure(ctx, values, "can't apply max() to empty list");
    }
    if ((e & VALUE_MASK) == VALUE_SET) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= sizeof(hvalue_t);
        hvalue_t max = v[0];
        for (int i = 1; i < size; i++) {
            if (value_cmp(v[i], max) > 0) {
                max = v[i];
            }
        }
		return max;
    }
    if ((e & VALUE_MASK) == VALUE_DICT) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= 2 * sizeof(hvalue_t);
        hvalue_t max = v[1];
        for (int i = 0; i < size; i++) {
            if (value_cmp(v[2*i+1], max) > 0) {
                max = v[2*i+1];
            }
        }
		return max;
    }
    return value_ctx_failure(ctx, values, "max() can only be applied to sets or lists");
}

hvalue_t f_min(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
	if (e == VALUE_SET) {
        return value_ctx_failure(ctx, values, "can't apply min() to empty set");
    }
    if (e == VALUE_DICT) {
        return value_ctx_failure(ctx, values, "can't apply min() to empty list");
    }
    if ((e & VALUE_MASK) == VALUE_SET) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= sizeof(hvalue_t);
        hvalue_t min = v[0];
        for (int i = 1; i < size; i++) {
            if (value_cmp(v[i], min) < 0) {
                min = v[i];
            }
        }
		return min;
    }
    if ((e & VALUE_MASK) == VALUE_DICT) {
        int size;
        hvalue_t *v = value_get(e, &size);
        size /= 2 * sizeof(hvalue_t);
        hvalue_t min = v[1];
        for (int i = 0; i < size; i++) {
            if (value_cmp(v[2*i+1], min) < 0) {
                min = v[2*i+1];
            }
        }
		return min;
    }
    return value_ctx_failure(ctx, values, "min() can only be applied to sets or lists");
}

hvalue_t f_minus(
    struct state *state,
    struct context *ctx,
    hvalue_t *args,
    int n,
    struct values_t *values
) {
    assert(n == 1 || n == 2);
    if (n == 1) {
        int64_t e = args[0];
        if ((e & VALUE_MASK) != VALUE_INT) {
            return value_ctx_failure(ctx, values, "unary minus can only be applied to ints");
        }
        e >>= VALUE_BITS;
        if (e == VALUE_MAX) {
            return ((hvalue_t) VALUE_MIN << VALUE_BITS) | VALUE_INT;
        }
        if (e == VALUE_MIN) {
            return (VALUE_MAX << VALUE_BITS) | VALUE_INT;
        }
        if (-e <= VALUE_MIN || -e >= VALUE_MAX) {
            return value_ctx_failure(ctx, values, "unary minus overflow (model too large)");
        }
        return ((-e) << VALUE_BITS) | VALUE_INT;
    }
    else {
        if ((args[0] & VALUE_MASK) == VALUE_INT) {
            int64_t e1 = args[0], e2 = args[1];
            if ((e2 & VALUE_MASK) != VALUE_INT) {
                return value_ctx_failure(ctx, values, "minus applied to int and non-int");
            }
            e1 >>= VALUE_BITS;
            e2 >>= VALUE_BITS;
            int64_t result = e2 - e1;
            if (result <= VALUE_MIN || result >= VALUE_MAX) {
                return value_ctx_failure(ctx, values, "minus overflow (model too large)");
            }
            return (result << VALUE_BITS) | VALUE_INT;
        }

        hvalue_t e1 = args[0], e2 = args[1];
        if ((e1 & VALUE_MASK) != VALUE_SET || (e2 & VALUE_MASK) != VALUE_SET) {
            return value_ctx_failure(ctx, values, "minus can only be applied to ints or sets");
        }
        int size1, size2;
        hvalue_t *vals1, *vals2;
        if (e1 == VALUE_SET) {
            size1 = 0;
            vals1 = NULL;
        }
        else {
            vals1 = value_get(e1, &size1);
        }
        if (e2 == VALUE_SET) {
            size2 = 0;
            vals2 = NULL;
        }
        else {
            vals2 = value_get(e2, &size2);
        }
        hvalue_t *vals = malloc(size2);
        size1 /= sizeof(hvalue_t);
        size2 /= sizeof(hvalue_t);

        hvalue_t *p1 = vals1, *p2 = vals2, *q = vals;
        while (size1 > 0 && size2 > 0) {
            if (*p1 == *p2) {
                p1++; size1--;
                p2++; size2--;
            }
            else {
                int cmp = value_cmp(*p1, *p2);
                if (cmp < 0) {
                    p1++; size1--;
                }
                else {
                    assert(cmp > 0);
                    *q++ = *p2++; size2--;
                }
            }
        }
        while (size2 > 0) {
            *q++ = *p2++; size2--;
        }
        hvalue_t result = value_put_set(values, vals, (char *) q - (char *) vals);
        free(vals);
        return result;
    }
}

hvalue_t f_mod(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    int64_t e1 = args[0], e2 = args[1];
    if ((e1 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "right argument to mod not an integer");
    }
    if ((e2 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "left argument to mod not an integer");
    }
    int64_t mod = (e1 >> VALUE_BITS);
    int64_t result = (e2 >> VALUE_BITS) % mod;
    if (result < 0) {
        result += mod;
    }
    return (result << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_not(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 1);
    hvalue_t e = args[0];
    if ((e & VALUE_MASK) != VALUE_BOOL) {
        return value_ctx_failure(ctx, values, "not can only be applied to booleans");
    }
    return e ^ (1 << VALUE_BITS);
}

hvalue_t f_plus(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    int64_t e1 = args[0];
    if ((e1 & VALUE_MASK) == VALUE_INT) {
        e1 >>= VALUE_BITS;
        for (int i = 1; i < n; i++) {
            int64_t e2 = args[i];
            if ((e2 & VALUE_MASK) != VALUE_INT) {
                return value_ctx_failure(ctx, values,
                    "+: applied to mix of integers and other values");
            }
            e2 >>= VALUE_BITS;
            int64_t sum = e1 + e2;
            if (sum <= VALUE_MIN || sum >= VALUE_MAX) {
                return value_ctx_failure(ctx, values,
                    "+: integer overflow (model too large)");
            }
            e1 = sum;
        }
        return (e1 << VALUE_BITS) | VALUE_INT;
    }

    if ((e1 & VALUE_MASK) == VALUE_ATOM) {
        struct strbuf sb;
        strbuf_init(&sb);
        for (int i = n; --i >= 0;) {
            if ((args[i] & VALUE_MASK) != VALUE_ATOM) {
                return value_ctx_failure(ctx, values,
                    "+: applied to mix of strings and other values");
            }
            int size;
            char *chars = value_get(args[i], &size);
            strbuf_append(&sb, chars, size);
        }
        char *result = strbuf_convert(&sb);
        hvalue_t v = value_put_atom(values, result, strbuf_getlen(&sb));
        return v;
    }

    // get all the lists
    struct val_info *vi = malloc(n * sizeof(*vi));
    int total = 0;
    for (int i = 0; i < n; i++) {
        if ((args[i] & VALUE_MASK) != VALUE_DICT) {
            value_ctx_failure(ctx, values, "+: applied to mix of value types");
            free(vi);
            return 0;
        }
        if (args[i] == VALUE_DICT) {
            vi[i].vals = NULL;
            vi[i].size = 0;
        }
        else {
            vi[i].vals = value_get(args[i], &vi[i].size); 
            total += vi[i].size;
        }
    }

    // If all are empty lists, we're done.
    if (total == 0) {
        return VALUE_DICT;
    }

    // Concatenate the sets
    hvalue_t *vals = malloc(total);
    total = 0;
    for (int i = n; --i >= 0;) {
        memcpy((char *) vals + total, vi[i].vals, vi[i].size);
        total += vi[i].size;
    }

    // Update the indices
    n = total / (2 * sizeof(hvalue_t));
    for (int i = 0; i < n; i++) {
        vals[2*i] = (i << VALUE_BITS) | VALUE_INT;
    }
    hvalue_t result = value_put_dict(values, vals, total);

    free(vi);
    free(vals);
    return result;
}

hvalue_t f_power(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int64_t e1 = args[0], e2 = args[1];

    if ((e1 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "right argument to ** not an integer");
    }
    if ((e2 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "left argument to ** not an integer");
    }
    int64_t base = e2 >> VALUE_BITS;
    int64_t exp = e1 >> VALUE_BITS;
    if (exp == 0) {
        return (1 << VALUE_BITS) | VALUE_INT;
    }
    if (exp < 0) {
        return value_ctx_failure(ctx, values, "**: negative exponent");
    }

    int64_t result = 1, orig = base;
    for (;;) {
        if (exp & 1) {
            result *= base;
        }
        exp >>= 1;
        if (exp == 0) {
            break;
        }
        base *= base;
    }
    if (result < orig) {
        // TODO.  Improve overflow detection
        return value_ctx_failure(ctx, values, "**: overflow (model too large)");
    }

    return (result << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_range(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int64_t e1 = args[0], e2 = args[1];

    if ((e1 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "right argument to .. not an integer");
    }
    if ((e2 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "left argument to .. not an integer");
    }
    int64_t start = e2 >> VALUE_BITS;
    int64_t finish = e1 >> VALUE_BITS;
	if (finish < start) {
		return VALUE_SET;
	}
    int cnt = (finish - start) + 1;
	assert(cnt > 0);
	assert(cnt < 1000);		// seems unlikely...
    hvalue_t *v = malloc(cnt * sizeof(hvalue_t));
    for (int i = 0; i < cnt; i++) {
        v[i] = ((start + i) << VALUE_BITS) | VALUE_INT;
    }
    hvalue_t result = value_put_set(values, v, cnt * sizeof(hvalue_t));
    free(v);
    return result;
}

hvalue_t f_dict_add(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 3);
    int64_t value = args[0], key = args[1], dict = args[2];
    assert((dict & VALUE_MASK) == VALUE_DICT);
    int size;
    hvalue_t *vals = value_get(dict, &size), *v;

    int i = 0, cmp = 1;
    for (v = vals; i < size; i += 2 * sizeof(hvalue_t), v += 2) {
        cmp = value_cmp(key, *v);
        if (cmp <= 0) {
            break;
        }
    }

    // See if we found the key.  In that case, we take the bigger value.
    if (cmp == 0) {
        cmp = value_cmp(value, v[1]);
        if (cmp <= 0) {
            return dict;
        }
        hvalue_t *nvals = malloc(size);
        memcpy(nvals, vals, size);
        * (hvalue_t *) ((char *) nvals + (i + sizeof(hvalue_t))) = value;

        hvalue_t result = value_put_dict(values, nvals, size);
        free(nvals);
        return result;
    }
    else {
        hvalue_t *nvals = malloc(size + 2*sizeof(hvalue_t));
        memcpy(nvals, vals, i);
        * (hvalue_t *) ((char *) nvals + i) = key;
        * (hvalue_t *) ((char *) nvals + (i + sizeof(hvalue_t))) = value;
        memcpy((char *) nvals + i + 2*sizeof(hvalue_t), v, size - i);

        hvalue_t result = value_put_dict(values, nvals, size + 2*sizeof(hvalue_t));
        free(nvals);
        return result;
    }
}

hvalue_t f_set_add(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int64_t elt = args[0], set = args[1];
    assert((set & VALUE_MASK) == VALUE_SET);
    int size;
    hvalue_t *vals = value_get(set, &size), *v;

    int i = 0;
    for (v = vals; i < size; i += sizeof(hvalue_t), v++) {
        int cmp = value_cmp(elt, *v);
        if (cmp == 0) {
            return set;
        }
        if (cmp < 0) {
            break;
        }
    }

    hvalue_t *nvals = malloc(size + sizeof(hvalue_t));
    memcpy(nvals, vals, i);
    * (hvalue_t *) ((char *) nvals + i) = elt;
    memcpy((char *) nvals + i + sizeof(hvalue_t), v, size - i);

    hvalue_t result = value_put_set(values, nvals, size + sizeof(hvalue_t));
    free(nvals);
    return result;
}

hvalue_t f_value_bag_add(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int64_t elt = args[0], dict = args[1];
    assert((dict & VALUE_MASK) == VALUE_DICT);
    int size;
    hvalue_t *vals = value_get(dict, &size), *v;

    int i = 0, cmp = 1;
    for (v = vals; i < size; i += 2 * sizeof(hvalue_t), v++) {
        cmp = value_cmp(elt, *v);
        if (cmp <= 0) {
            break;
        }
    }

    if (cmp == 0) {
        assert((v[1] & VALUE_MASK) == VALUE_INT);
        int cnt = (v[1] >> VALUE_BITS) + 1;
        hvalue_t *nvals = malloc(size);
        memcpy(nvals, vals, size);
        * (hvalue_t *) ((char *) nvals + (i + sizeof(hvalue_t))) =
                                        (cnt << VALUE_BITS) | VALUE_INT;

        hvalue_t result = value_put_dict(values, nvals, size);
        free(nvals);
        return result;
    }
    else {
        hvalue_t *nvals = malloc(size + 2*sizeof(hvalue_t));
        memcpy(nvals, vals, i);
        * (hvalue_t *) ((char *) nvals + i) = elt;
        * (hvalue_t *) ((char *) nvals + (i + sizeof(hvalue_t))) =
                                        (1 << VALUE_BITS) | VALUE_INT;
        memcpy((char *) nvals + i + 2*sizeof(hvalue_t), v, size - i);

        hvalue_t result = value_put_dict(values, nvals, size + 2*sizeof(hvalue_t));
        free(nvals);
        return result;
    }
}

hvalue_t f_shiftleft(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int64_t e1 = args[0], e2 = args[1];

    if ((e1 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "right argument to << not an integer");
    }
    if ((e2 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "left argument to << not an integer");
    }
    e1 >>= VALUE_BITS;
    if (e1 < 0) {
        return value_ctx_failure(ctx, values, "<<: negative shift count");
    }
    e2 >>= VALUE_BITS;
    int64_t result = e2 << e1;
    if (((result << VALUE_BITS) >> VALUE_BITS) != result) {
        return value_ctx_failure(ctx, values, "<<: overflow (model too large)");
    }
    if (result <= VALUE_MIN || result >= VALUE_MAX) {
        return value_ctx_failure(ctx, values, "<<: overflow (model too large)");
    }
    return (result << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_shiftright(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    assert(n == 2);
    int64_t e1 = args[0], e2 = args[1];

    if ((e1 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "right argument to >> not an integer");
    }
    if ((e2 & VALUE_MASK) != VALUE_INT) {
        return value_ctx_failure(ctx, values, "left argument to >> not an integer");
    }
    if (e1 < 0) {
        return value_ctx_failure(ctx, values, ">>: negative shift count");
    }
    e1 >>= VALUE_BITS;
    e2 >>= VALUE_BITS;
    return ((e2 >> e1) << VALUE_BITS) | VALUE_INT;
}

hvalue_t f_times(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    int64_t result = 1;
    int list = -1;
    for (int i = 0; i < n; i++) {
        int64_t e = args[i];
        if ((e & VALUE_MASK) == VALUE_DICT || (e & VALUE_MASK) == VALUE_ATOM) {
            if (list >= 0) {
                return value_ctx_failure(ctx, values, "* can only have at most one list or string");
            }
            list = i;
        }
        else {
            if ((e & VALUE_MASK) != VALUE_INT) {
                return value_ctx_failure(ctx, values,
                    "* can only be applied to integers and at most one list or string");
            }
            e >>= VALUE_BITS;
            if (e == 0) {
                result = 0;
            }
            else {
                int64_t product = result * e;
                if (product / result != e) {
                    return value_ctx_failure(ctx, values, "*: overflow (model too large)");
                }
                result = product;
            }
        }
    }
    if (result != (result << VALUE_BITS) >> VALUE_BITS) {
        return value_ctx_failure(ctx, values, "*: overflow (model too large)");
    }
    if (list < 0) {
        return (result << VALUE_BITS) | VALUE_INT;
    }
    if (result == 0) {
        return args[list] & VALUE_MASK;
    }
    if ((args[list] & VALUE_MASK) == VALUE_DICT) {
        int size;
        hvalue_t *vals = value_get(args[list], &size);
        if (size == 0) {
            return VALUE_DICT;
        }
        hvalue_t *r = malloc(result * size);
        unsigned int cnt = size / (2 * sizeof(hvalue_t));
        int index = 0;
        for (int i = 0; i < result; i++) {
            for (unsigned int j = 0; j < cnt; j++) {
                r[2*index] = (index << VALUE_BITS) | VALUE_INT;
                r[2*index+1] = vals[2*j+1];
                index++;
            }
        }
        hvalue_t v = value_put_dict(values, r, result * size);
        free(r);
        return v;
    }
    assert((args[list] & VALUE_MASK) == VALUE_ATOM);
	int size;
	char *chars = value_get(args[list], &size);
	if (size == 0) {
		return VALUE_ATOM;
	}
	struct strbuf sb;
	strbuf_init(&sb);
	for (int i = 0; i < result; i++) {
		strbuf_append(&sb, chars, size);
	}
	char *s = strbuf_getstr(&sb);
	hvalue_t v = value_put_atom(values, s, result * size);
	free(s);
	return v;
}

hvalue_t f_union(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    hvalue_t e1 = args[0];

    if ((e1 & VALUE_MASK) == VALUE_INT) {
        for (int i = 1; i < n; i++) {
            hvalue_t e2 = args[i];
            if ((e2 & VALUE_MASK) != VALUE_INT) {
                return value_ctx_failure(ctx, values, "'|' applied to mix of ints and other types");
            }
            e1 |= e2;
        }
        return e1;
    }

    if ((e1 & VALUE_MASK) == VALUE_SET) {
        // get all the sets
        struct val_info *vi = malloc(n * sizeof(*vi));
        int total = 0;
        for (int i = 0; i < n; i++) {
            if ((args[i] & VALUE_MASK) != VALUE_SET) {
                return value_ctx_failure(ctx, values, "'|' applied to mix of sets and other types");
            }
            if (args[i] == VALUE_SET) {
                vi[i].vals = NULL;
                vi[i].size = 0;
            }
            else {
                vi[i].vals = value_get(args[i], &vi[i].size); 
                total += vi[i].size;
            }
        }

        // If all are empty lists, we're done.
        if (total == 0) {
            return VALUE_SET;
        }

        // Concatenate the sets
        hvalue_t *vals = malloc(total);
        total = 0;
        for (int i = 0; i < n; i++) {
            memcpy((char *) vals + total, vi[i].vals, vi[i].size);
            total += vi[i].size;
        }

        n = sort(vals, total / sizeof(hvalue_t));
        hvalue_t result = value_put_set(values, vals, n * sizeof(hvalue_t));
        free(vi);
        free(vals);
        return result;
    }

    if ((e1 & VALUE_MASK) != VALUE_DICT) {
        return value_ctx_failure(ctx, values, "'|' can only be applied to ints and dicts");
    }
    // get all the dictionaries
    struct val_info *vi = malloc(n * sizeof(*vi));
    int total = 0;
    for (int i = 0; i < n; i++) {
        if ((args[i] & VALUE_MASK) != VALUE_DICT) {
            return value_ctx_failure(ctx, values, "'|' applied to mix of dictionaries and other types");
        }
        if (args[i] == VALUE_DICT) {
            vi[i].vals = NULL;
            vi[i].size = 0;
        }
        else {
            vi[i].vals = value_get(args[i], &vi[i].size); 
            total += vi[i].size;
        }
    }

    // If all are empty dictionaries, we're done.
    if (total == 0) {
        return VALUE_DICT;
    }

    // Concatenate the dictionaries
    hvalue_t *vals = malloc(total);
    total = 0;
    for (int i = 0; i < n; i++) {
        memcpy((char *) vals + total, vi[i].vals, vi[i].size);
        total += vi[i].size;
    }

    // sort lexicographically, leaving duplicate keys
    int cnt = total / (2 * sizeof(hvalue_t));
    qsort(vals, cnt, 2 * sizeof(hvalue_t), q_kv_cmp);

    // now only leave the max value for duplicate keys
    n = 0;
    for (int i = 1; i < cnt; i++) {
        if (vals[2*i] != vals[2*n]) {
            n++;
        }
        vals[2*n] = vals[2*i];
        vals[2*n+1] = vals[2*i+1];
    }
    n++;

    hvalue_t result = value_put_dict(values, vals, 2 * n * sizeof(hvalue_t));
    free(vi);
    free(vals);
    return result;
}

hvalue_t f_xor(struct state *state, struct context *ctx, hvalue_t *args, int n, struct values_t *values){
    hvalue_t e1 = args[0];

    if ((e1 & VALUE_MASK) == VALUE_INT) {
        for (int i = 1; i < n; i++) {
            hvalue_t e2 = args[i];
            if ((e2 & VALUE_MASK) != VALUE_INT) {
                return value_ctx_failure(ctx, values, "'^' applied to mix of ints and other types");
            }
            e1 ^= e2;
        }
        return e1 | VALUE_INT;
    }

    // get all the sets
    struct val_info *vi = malloc(n * sizeof(*vi));
    int total = 0;
    for (int i = 0; i < n; i++) {
        if ((args[i] & VALUE_MASK) != VALUE_SET) {
            return value_ctx_failure(ctx, values, "'^' applied to mix of value types");
        }
        if (args[i] == VALUE_SET) {
            vi[i].vals = NULL;
            vi[i].size = 0;
        }
        else {
            vi[i].vals = value_get(args[i], &vi[i].size); 
            total += vi[i].size;
        }
    }

    // If all are empty lists, we're done.
    if (total == 0) {
        return VALUE_SET;
    }

    // Concatenate the sets
    hvalue_t *vals = malloc(total);
    total = 0;
    for (int i = 0; i < n; i++) {
        memcpy((char *) vals + total, vi[i].vals, vi[i].size);
        total += vi[i].size;
    }

    // sort the values, but retain duplicates
    int cnt = total / sizeof(hvalue_t);
    qsort(vals, cnt, sizeof(hvalue_t), q_value_cmp);

    // Now remove the values that have an even number
    int i = 0, j = 0, k = 0;
    while (i < cnt) {
        while (i < cnt && vals[i] == vals[j]) {
            i++;
        }
        if ((i - j) % 2 != 0) {
            vals[k++] = vals[j];
        }
        j = i;
    }

    hvalue_t result = value_put_set(values, vals, k * sizeof(hvalue_t));
    free(vi);
    free(vals);
    return result;
}

struct op_info op_table[] = {
	{ "Address", init_Address, op_Address },
	{ "Apply", init_Apply, op_Apply },
	{ "Assert", init_Assert, op_Assert },
	{ "Assert2", init_Assert2, op_Assert2 },
	{ "AtomicDec", init_AtomicDec, op_AtomicDec },
	{ "AtomicInc", init_AtomicInc, op_AtomicInc },
	{ "Choose", init_Choose, op_Choose },
	{ "Continue", init_Continue, op_Continue },
	{ "Cut", init_Cut, op_Cut },
	{ "Del", init_Del, op_Del },
	{ "DelVar", init_DelVar, op_DelVar },
	{ "Dup", init_Dup, op_Dup },
	{ "Frame", init_Frame, op_Frame },
	{ "Go", init_Go, op_Go },
	{ "IncVar", init_IncVar, op_IncVar },
	{ "Invariant", init_Invariant, op_Invariant },
	{ "Jump", init_Jump, op_Jump },
	{ "JumpCond", init_JumpCond, op_JumpCond },
	{ "Load", init_Load, op_Load },
	{ "LoadVar", init_LoadVar, op_LoadVar },
	{ "Move", init_Move, op_Move },
	{ "Nary", init_Nary, op_Nary },
	{ "Pop", init_Pop, op_Pop },
	{ "Print", init_Print, op_Print },
	{ "Push", init_Push, op_Push },
	{ "ReadonlyDec", init_ReadonlyDec, op_ReadonlyDec },
	{ "ReadonlyInc", init_ReadonlyInc, op_ReadonlyInc },
	{ "Return", init_Return, op_Return },
	{ "Save", init_Save, op_Save },
	{ "Sequential", init_Sequential, op_Sequential },
	{ "SetIntLevel", init_SetIntLevel, op_SetIntLevel },
	{ "Spawn", init_Spawn, op_Spawn },
	{ "Split", init_Split, op_Split },
	{ "Stop", init_Stop, op_Stop },
	{ "Store", init_Store, op_Store },
	{ "StoreVar", init_StoreVar, op_StoreVar },
	{ "Trap", init_Trap, op_Trap },
    { NULL, NULL, NULL }
};

struct f_info f_table[] = {
	{ "+", f_plus },
	{ "-", f_minus },
	{ "~", f_invert },
	{ "*", f_times },
	{ "/", f_div },
	{ "//", f_div },
	{ "%", f_mod },
	{ "**", f_power },
	{ "<<", f_shiftleft },
	{ ">>", f_shiftright },
    { "<", f_lt },
    { "<=", f_le },
    { ">=", f_ge },
    { ">", f_gt },
    { "|", f_union },
    { "&", f_intersection },
    { "^", f_xor },
    { "..", f_range },
    { "==", f_eq },
    { "!=", f_ne },
    { "abs", f_abs },
    { "all", f_all },
    { "any", f_any },
    { "atLabel", f_atLabel },
    { "BagAdd", f_value_bag_add },
    { "countLabel", f_countLabel },
    { "DictAdd", f_dict_add },
    { "in", f_in },
    { "IsEmpty", f_isEmpty },
    { "keys", f_keys },
    { "len", f_len },
    { "max", f_max },
    { "min", f_min },
	{ "mod", f_mod },
    { "not", f_not },
    { "str", f_str },
    { "SetAdd", f_set_add },
    { NULL, NULL }
};

struct op_info *ops_get(char *opname, int size){
    return dict_lookup(ops_map, opname, size);
}

void ops_init(struct global_t *global) {
    ops_map = dict_new(0);
    f_map = dict_new(0);
	underscore = value_put_atom(&global->values, "_", 1);
	this_atom = value_put_atom(&global->values, "this", 4);

    for (struct op_info *oi = op_table; oi->name != NULL; oi++) {
        void **p = dict_insert(ops_map, oi->name, strlen(oi->name));
        *p = oi;
    }
    for (struct f_info *fi = f_table; fi->name != NULL; fi++) {
        void **p = dict_insert(f_map, fi->name, strlen(fi->name));
        *p = fi;
    }
}
#include <assert.h>

#ifndef HARMONY_COMBINE
#include <stdio.h>
#include <stdlib.h>
#include "minheap.h"
#endif

#define parent(k)   (int) (((k) - 1) / 2)
#define leftc(k)    (2 * (k) + 1)
#define rightc(k)    (2 * (k) + 2)

struct minheap {
    int (*cmp)(void *v1, void *v2);
    void **array;
    int alloc_size, size;
};

bool minheap_check(struct minheap *mh, void *key) {
    for (int i = 0; i < mh->size; i++) {
        if (mh->array[i] == key) {
            return true;
        }
    }
    return false;
}

struct minheap *minheap_create(int (*cmp)(void *, void *)) {
    struct minheap *mh = malloc(sizeof(struct minheap));
    if (mh == NULL) {
        fprintf(stderr, "minheap_create: out of memory\n");
        exit(1);
    }
    mh->cmp = cmp;
    mh->size = 0;
    mh->alloc_size = 1024;
    mh->array = malloc(sizeof(void *) * mh->alloc_size);
    if (mh->array == NULL) {
        fprintf(stderr, "minheap_create: out of memory\n");
        exit(1);
    }
    return mh;
}

static void minheap_swap(struct minheap *mh, int i, int j) {
    void *tmp = mh->array[i];
    mh->array[i] = mh->array[j];
    mh->array[j] = tmp;
}

static void minheap_fixup(struct minheap *mh, int k) {
    while (k > 0 && (*mh->cmp)(mh->array[k], mh->array[parent(k)]) < 0) {
        minheap_swap(mh, parent(k), k);
        k = parent(k);
    }
}

static void minheap_fixdown(struct minheap *mh, int k) {
    int j;

    while ((j = leftc(k)) < mh->size) {
        assert(j > 0);
        if (j < mh->size - 1 && (*mh->cmp)(mh->array[j+1], mh->array[j]) < 0) {
            j++;
        }
        if ((*mh->cmp)(mh->array[k], mh->array[j]) <= 0) {
            break;
        }
        minheap_swap(mh, k, j);
        k = j;
    }
}

void minheap_insert(struct minheap *mh, void *key) {
    if (mh->size == mh->alloc_size) {
        mh->alloc_size *= 2;
        mh->array = realloc(mh->array, sizeof(void *) * mh->alloc_size);
    }
    mh->array[mh->size++] = key;
    minheap_fixup(mh, mh->size - 1);
}

void minheap_decrease(struct minheap *mh, void *key) {
    for (int i = 0; i < mh->size; i++) {
        if (mh->array[i] == key) {
            minheap_fixup(mh, i);
            break;
        }
    }
}

bool minheap_empty(struct minheap *mh) {
    return mh->size == 0;
}

void *minheap_getmin(struct minheap *mh) {
    if (minheap_empty(mh)) {
        fprintf(stderr, "minheap_getmin: heap is empty\n");
        exit(1);
    }
    void *result = mh->array[0];
    mh->array[0] = mh->array[--mh->size];
    minheap_fixdown(mh, 0);
    return result;
}

int minheap_size(struct minheap *mh) {
    return mh->size;
}

// TODO: make more efficient
// Move everything from mh1 into mh2
void minheap_move(struct minheap *mh1, struct minheap *mh2){
    while (!minheap_empty(mh1)) {
        void *v = minheap_getmin(mh1);
        minheap_insert(mh2, v);
    }
}

void minheap_destroy(struct minheap *mh) {
    free(mh->array);
    free(mh);
}
/* An element in a queue.
 */
struct element {
	struct element *next;
	void *item;
};

void queue_init(struct queue *q){
	q->first = 0;
	q->last = &q->first;
	q->nelts = 0;
}

/* Put it on the wrong side of the queue.  I.e., make it the next
 * item to be returned.  Sort of like a stack...
 */
void queue_insert(struct queue *q, void *item){
	struct element *e = calloc(1, sizeof(*e));

	e->item = item;
	if (q->first == 0) {
		q->last = &e->next;
	}
	e->next = q->first;
	q->first = e;
	q->nelts++;
}

void queue_add(struct queue *q, void *item){
	struct element *e = calloc(1, sizeof(*e));

	e->item = item;
	e->next = 0;
	*q->last = e;
	q->last = &e->next;
	q->nelts++;
}

void queue_add_uint(struct queue *q, uint64_t item){
	queue_add(q, (void *) item);
}

void *queue_get(struct queue *q){
	void *item;
	struct element *e;

	if ((e = q->first) == 0) {
		return 0;
	}
	if ((q->first = e->next) == 0) {
		q->last = &q->first;
	}
	item = e->item;
	free(e);
	q->nelts--;
	return item;
}

bool queue_tget(struct queue *q, void **item){
	struct element *e;

	if ((e = q->first) == 0) {
		return false;
	}
	if ((q->first = e->next) == 0) {
		q->last = &q->first;
	}
	*item = e->item;
	free(e);
	q->nelts--;
	return true;
}

bool queue_get_uint(struct queue *q, uint64_t *item){
	void *x;
	bool r = queue_tget(q, &x);
	if (!r) {
		return false;
	}
	*item = (uint64_t) x;
	return true;
}

bool queue_empty(struct queue *q){
	return q->first == 0;
}

unsigned int queue_size(struct queue *q){
	return q->nelts;
}

void queue_release(struct queue *q){
	assert(q->first == 0);
	assert(q->nelts == 0);
}
#define _GNU_SOURCE

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#ifndef HARMONY_COMBINE
#include "global.h"
#endif

void *value_get(hvalue_t v, int *psize){
    v &= ~VALUE_MASK;
    if (v == 0) {
        *psize = 0;
        return NULL;
    }
    return dict_retrieve((void *) v, psize);
}

void *value_copy(hvalue_t v, int *psize){
    v &= ~VALUE_MASK;
    if (v == 0) {
        *psize = 0;
        return NULL;
    }
    int size;
    void *p = dict_retrieve((void *) v, &size);
    void *r = malloc(size);
    memcpy(r, p, size);
    if (psize != NULL) {
        *psize = size;
    }
    return r;
}

hvalue_t value_put_atom(struct values_t *values, const void *p, int size){
    if (size == 0) {
        return VALUE_ATOM;
    }
    void *q = dict_find(values->atoms, p, size);
    return (hvalue_t) q | VALUE_ATOM;
}

hvalue_t value_put_set(struct values_t *values, void *p, int size){
    if (size == 0) {
        return VALUE_SET;
    }
    void *q = dict_find(values->sets, p, size);
    return (hvalue_t) q | VALUE_SET;
}

hvalue_t value_put_dict(struct values_t *values, void *p, int size){
    if (size == 0) {
        return VALUE_DICT;
    }
    void *q = dict_find(values->dicts, p, size);
    return (hvalue_t) q | VALUE_DICT;
}

hvalue_t value_put_address(struct values_t *values, void *p, int size){
    if (size == 0) {
        return VALUE_ADDRESS;
    }
    void *q = dict_find(values->addresses, p, size);
    return (hvalue_t) q | VALUE_ADDRESS;
}

hvalue_t value_put_context(struct values_t *values, struct context *ctx){
	assert(ctx->pc >= 0);
    int size = sizeof(*ctx) + (ctx->sp * sizeof(hvalue_t));
    void *q = dict_find(values->contexts, ctx, size);
    return (hvalue_t) q | VALUE_CONTEXT;
}

int value_cmp_bool(hvalue_t v1, hvalue_t v2){
    return v1 == 0 ? -1 : 1;
}

int value_cmp_int(hvalue_t v1, hvalue_t v2){
    return (int64_t) v1 < (int64_t) v2 ? -1 : 1;
}

int value_cmp_atom(hvalue_t v1, hvalue_t v2){
    int size1, size2;
    char *s1 = value_get(v1, &size1);
    char *s2 = value_get(v2, &size2);
    int size = size1 < size2 ? size1 : size2;
    int cmp = strncmp(s1, s2, size);
    if (cmp != 0) {
        return cmp;
    }
    return size1 < size2 ? -1 : 1;
}

int value_cmp_pc(hvalue_t v1, hvalue_t v2){
    return v1 < v2 ? -1 : 1;
}

int value_cmp_dict(hvalue_t v1, hvalue_t v2){
    if (v1 == 0) {
        return v2 == 0 ? 0 : -1;
    }
    if (v2 == 0) {
        return 1;
    }
    void *p1 = (void *) v1, *p2 = (void *) v2;
    int size1, size2;
    hvalue_t *vals1 = dict_retrieve(p1, &size1);
    hvalue_t *vals2 = dict_retrieve(p2, &size2);
    size1 /= sizeof(hvalue_t);
    size2 /= sizeof(hvalue_t);
    int size = size1 < size2 ? size1 : size2;
    for (int i = 0; i < size; i++) {
        int cmp = value_cmp(vals1[i], vals2[i]);
        if (cmp != 0) {
            return cmp;
        }
    }
    return size1 < size2 ? -1 : 1;
}

int value_cmp_set(hvalue_t v1, hvalue_t v2){
    if (v1 == 0) {
        return v2 == 0 ? 0 : -1;
    }
    if (v2 == 0) {
        return 1;
    }
    void *p1 = (void *) v1, *p2 = (void *) v2;
    int size1, size2;
    hvalue_t *vals1 = dict_retrieve(p1, &size1);
    hvalue_t *vals2 = dict_retrieve(p2, &size2);
    size1 /= sizeof(hvalue_t);
    size2 /= sizeof(hvalue_t);
    int size = size1 < size2 ? size1 : size2;
    for (int i = 0; i < size; i++) {
        int cmp = value_cmp(vals1[i], vals2[i]);
        if (cmp != 0) {
            return cmp;
        }
    }
    return size1 < size2 ? -1 : 1;
}

int value_cmp_address(hvalue_t v1, hvalue_t v2){
    if (v1 == 0) {
        return v2 == 0 ? 0 : -1;
    }
    if (v2 == 0) {
        return 1;
    }
    void *p1 = (void *) v1, *p2 = (void *) v2;
    int size1, size2;
    hvalue_t *vals1 = dict_retrieve(p1, &size1);
    hvalue_t *vals2 = dict_retrieve(p2, &size2);
    size1 /= sizeof(hvalue_t);
    size2 /= sizeof(hvalue_t);
    int size = size1 < size2 ? size1 : size2;
    for (int i = 0; i < size; i++) {
        int cmp = value_cmp(vals1[i], vals2[i]);
        if (cmp != 0) {
            return cmp;
        }
    }
    return size1 < size2 ? -1 : 1;
}

// TODO.  Maybe should compare name tag, pc, ...
int value_cmp_context(hvalue_t v1, hvalue_t v2){
    void *p1 = (void *) v1, *p2 = (void *) v2;
    int size1, size2;
    char *s1 = dict_retrieve(p1, &size1);
    char *s2 = dict_retrieve(p2, &size2);
    int size = size1 < size2 ? size1 : size2;
    int cmp = memcmp(s1, s2, size);
    if (cmp != 0) {
        return cmp < 0 ? -1 : 1;
    }
    return size1 < size2 ? -1 : 1;
}

int value_cmp(hvalue_t v1, hvalue_t v2){
    if (v1 == v2) {
        return 0;
    }
    int t1 = v1 & VALUE_MASK;
    int t2 = v2 & VALUE_MASK;
    if (t1 != t2) {
        return t1 < t2 ? -1 : 1;
    }
    switch (t1) {
    case VALUE_BOOL:
        return value_cmp_bool(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_INT:
        return value_cmp_int(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_ATOM:
        return value_cmp_atom(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_PC:
        return value_cmp_pc(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_DICT:
        return value_cmp_dict(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_SET:
        return value_cmp_set(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_ADDRESS:
        return value_cmp_address(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    case VALUE_CONTEXT:
        return value_cmp_context(v1 & ~VALUE_MASK, v2 & ~VALUE_MASK);
    default:
        panic("value_cmp: bad value type");
        return 0;
    }
}

static void value_string_bool(struct strbuf *sb, hvalue_t v) {
    if (v != 0 && v != (1 << VALUE_BITS)) {
        fprintf(stderr, "value_string_bool %"PRI_HVAL"\n", v);
        panic("value_string_bool: bad value");
    }
    assert(v == 0 || v == (1 << VALUE_BITS));

    strbuf_printf(sb, v == 0 ? "False" : "True");
}

static void value_json_bool(struct strbuf *sb, hvalue_t v) {
    if (v != 0 && v != (1 << VALUE_BITS)) {
        fprintf(stderr, "value_json_bool %"PRI_HVAL"\n", v);
        panic("value_json_bool: bad value");
    }
    assert(v == 0 || v == (1 << VALUE_BITS));

    strbuf_printf(sb, "{ \"type\": \"bool\", \"value\": \"%s\" }", v == 0 ? "False" : "True");
}

static void value_string_int(struct strbuf *sb, hvalue_t v) {
    int64_t w = ((int64_t) v) >> VALUE_BITS;
    strbuf_printf(sb, "%"PRId64"", (int64_t) w);
}

static void value_json_int(struct strbuf *sb, hvalue_t v) {
    int64_t w = ((int64_t) v) >> VALUE_BITS;
    strbuf_printf(sb, "{ \"type\": \"int\", \"value\": \"%"PRId64"\" }", (int64_t) w);
}

static void value_string_atom(struct strbuf *sb, hvalue_t v) {
    int size;
    char *s = value_get(v, &size);

    strbuf_append(sb, "\"", 1);
	while (size > 0) {
		switch (*s) {
		case '"':
			strbuf_append(sb, "\\\"", 2);
			break;
		case '\\':
			strbuf_append(sb, "\\\\", 2);
			break;
		case '\n':
			strbuf_append(sb, "\\n", 2);
			break;
		case '\r':
			strbuf_append(sb, "\\r", 2);
			break;
		default:
			strbuf_append(sb, s, 1);
		}
		s++;
		size--;
	}
    strbuf_append(sb, "\"", 1);
}

static void value_json_atom(struct strbuf *sb, hvalue_t v) {
    int size;
    char *s = value_get(v, &size);
    char *esc = json_escape(s, size);

    strbuf_printf(sb, "{ \"type\": \"atom\", \"value\": \"%s\" }", esc);
    free(esc);
}

static void value_string_pc(struct strbuf *sb, hvalue_t v) {
    assert((v >> VALUE_BITS) < 10000);      // debug
    strbuf_printf(sb, "PC(%u)", (unsigned int) (v >> VALUE_BITS));
}

static void value_json_pc(struct strbuf *sb, hvalue_t v) {
    strbuf_printf(sb, "{ \"type\": \"pc\", \"value\": \"%u\" }", (unsigned int) (v >> VALUE_BITS));
}

static void value_string_dict(struct strbuf *sb, hvalue_t v) {
    if (v == 0) {
        strbuf_printf(sb, "()");
        return;
    }

    void *p = (void *) v;
    int size;
    hvalue_t *vals = dict_retrieve(p, &size);
    size /= 2 * sizeof(hvalue_t);

    bool islist = true;
    for (int i = 0; i < size; i++) {
        if (vals[2*i] != (((hvalue_t) i << VALUE_BITS) | VALUE_INT)) {
            islist = false;
            break;
        }
    }

    if (islist) {
        strbuf_printf(sb, "(");
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                strbuf_printf(sb, ", ");
            }
            strbuf_value_string(sb, vals[2*i+1]);
        }
        strbuf_printf(sb, ")");
    }
    else {
        strbuf_printf(sb, "{ ");
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                strbuf_printf(sb, ", ");
            }
            strbuf_value_string(sb, vals[2*i]);
            strbuf_printf(sb, ": ");
            strbuf_value_string(sb, vals[2*i+1]);
        }
        strbuf_printf(sb, " }");
    }
}

static void value_json_dict(struct strbuf *sb, hvalue_t v) {
    if (v == 0) {
        strbuf_printf(sb, "{ \"type\": \"dict\", \"value\": [] }");
        return;
    }

    void *p = (void *) v;
    int size;
    hvalue_t *vals = dict_retrieve(p, &size);
    size /= 2 * sizeof(hvalue_t);

    strbuf_printf(sb, "{ \"type\": \"dict\", \"value\": [");
    for (int i = 0; i < size; i++) {
        if (i != 0) {
            strbuf_printf(sb, ", ");
        }
        strbuf_printf(sb, "{ \"key\": ");
        strbuf_value_json(sb, vals[2*i]);
        strbuf_printf(sb, ", \"value\": ");
        strbuf_value_json(sb, vals[2*i+1]);
        strbuf_printf(sb, " }");
    }
    strbuf_printf(sb, " ] }");
}

static void value_string_set(struct strbuf *sb, hvalue_t v) {
    if (v == 0) {
        strbuf_printf(sb, "{}");
        return;
    }

    void *p = (void *) v;
    int size;
    hvalue_t *vals = dict_retrieve(p, &size);
    size /= sizeof(hvalue_t);

    strbuf_printf(sb, "{ ");
    for (int i = 0; i < size; i++) {
        if (i != 0) {
            strbuf_printf(sb, ", ");
        }
        strbuf_value_string(sb, vals[i]);
    }
    strbuf_printf(sb, " }");
}

static void value_json_set(struct strbuf *sb, hvalue_t v) {
    if (v == 0) {
        strbuf_printf(sb, "{ \"type\": \"set\", \"value\": [] }");
        return;
    }

    void *p = (void *) v;
    int size;
    hvalue_t *vals = dict_retrieve(p, &size);
    size /= sizeof(hvalue_t);

    strbuf_printf(sb, "{ \"type\": \"set\", \"value\": [");
    for (int i = 0; i < size; i++) {
        if (i != 0) {
            strbuf_printf(sb, ", ");
        }
        strbuf_value_json(sb, vals[i]);
    }
    strbuf_printf(sb, " ] }");
}

static void strbuf_indices_string(struct strbuf *sb, const hvalue_t *vec, int size) {
    if (size == 0) {
        strbuf_printf(sb, "None");
        return;
    }
    char *s = value_string(vec[0]);
    assert(s[0] == '"');
	int len = strlen(s);
    strbuf_printf(sb, "?%.*s", len - 2, s + 1);
    free(s);

    for (int i = 1; i < size; i++) {
        strbuf_printf(sb, "[");
        strbuf_value_string(sb, vec[i]);
        strbuf_printf(sb, "]");
    }
}

char *indices_string(const hvalue_t *vec, int size) {
    struct strbuf sb;

    strbuf_init(&sb);
    strbuf_indices_string(&sb, vec, size);
    return strbuf_convert(&sb);
}

static void value_string_address(struct strbuf *sb, hvalue_t v) {
    if (v == 0) {
        strbuf_printf(sb, "None");
        return;
    }

    void *p = (void *) v;
    int size;
    hvalue_t *indices = dict_retrieve(p, &size);
    size /= sizeof(hvalue_t);
    assert(size > 0);
    strbuf_indices_string(sb, indices, size);
}

static void value_json_address(struct strbuf *sb, hvalue_t v) {
    if (v == 0) {
        strbuf_printf(sb, "{ \"type\": \"address\", \"value\": [] }");
        return;
    }

    void *p = (void *) v;
    int size;
    hvalue_t *vals = dict_retrieve(p, &size);
    size /= sizeof(hvalue_t);
    assert(size > 0);
    strbuf_printf(sb, "{ \"type\": \"address\", \"value\": [");
    for (int i = 0; i < size; i++) {
        if (i != 0) {
            strbuf_printf(sb, ", ");
        }
        strbuf_value_json(sb, vals[i]);
    }

    strbuf_printf(sb, " ] }");
}

static void value_string_context(struct strbuf *sb, hvalue_t v) {
    struct context *ctx = value_get(v, NULL);
    strbuf_printf(sb, "CONTEXT(");
#ifdef SHORT
    strbuf_value_string(sb, ctx->name);
    strbuf_printf(sb, ", %d)", ctx->pc);
    free(name);
#else
    strbuf_printf(sb, "name=");
    strbuf_value_string(sb, ctx->name);
    strbuf_printf(sb, ",entry=");
    strbuf_value_string(sb, ctx->entry);
    strbuf_printf(sb, ",arg=");
    strbuf_value_string(sb, ctx->arg);
    strbuf_printf(sb, ",this=");
    strbuf_value_string(sb, ctx->this);
    strbuf_printf(sb, ",vars=");
    strbuf_value_string(sb, ctx->vars);
    strbuf_printf(sb, ",trap_pc=");
    strbuf_value_string(sb, ctx->trap_pc);
    strbuf_printf(sb, ",trap_arg=");
    strbuf_value_string(sb, ctx->trap_arg);
    strbuf_printf(sb, ",failure=");
    strbuf_value_string(sb, ctx->failure);

    strbuf_printf(sb, ",pc=%d", ctx->pc);
    strbuf_printf(sb, ",fp=%d", ctx->fp);
    strbuf_printf(sb, ",readonly=%d", ctx->readonly);
    strbuf_printf(sb, ",atomic=%d", ctx->atomic);
    strbuf_printf(sb, ",aflag=%d", ctx->atomicFlag);
    strbuf_printf(sb, ",il=%d", ctx->interruptlevel);
    strbuf_printf(sb, ",stopped=%d", ctx->stopped);
    strbuf_printf(sb, ",terminated=%d", ctx->terminated);
    strbuf_printf(sb, ",eternal=%d", ctx->eternal);

    strbuf_printf(sb, ",sp=%d,STACK[", ctx->sp);

    for (int i = 0; i < ctx->sp; i++) {
        if (i != 0) {
            strbuf_printf(sb, ",");
        }
        strbuf_value_string(sb, ctx->stack[i]);
    }

    strbuf_printf(sb, "])");
#endif
}

static void value_json_context(struct strbuf *sb, hvalue_t v) {
    struct context *ctx = value_get(v, NULL);
    
    strbuf_printf(sb, "{ \"type\": \"context\", \"value\": {");
    strbuf_printf(sb, "\"name\": ");
    strbuf_value_json(sb, ctx->name);
    strbuf_printf(sb, ", \"arg\": ");
    strbuf_value_json(sb, ctx->arg);
    strbuf_printf(sb, ", \"pc\": { \"type\": \"pc\", \"value\": \"%d\" }", ctx->pc);

    strbuf_printf(sb, " } }");
}

void strbuf_value_string(struct strbuf *sb, hvalue_t v){
    switch (v & VALUE_MASK) {
    case VALUE_BOOL:
        value_string_bool(sb, v & ~VALUE_MASK);
        break;
    case VALUE_INT:
        value_string_int(sb, v & ~VALUE_MASK);
        break;
    case VALUE_ATOM:
        value_string_atom(sb, v & ~VALUE_MASK);
        break;
    case VALUE_PC:
        value_string_pc(sb, v & ~VALUE_MASK);
        break;
    case VALUE_DICT:
        value_string_dict(sb, v & ~VALUE_MASK);
        break;
    case VALUE_SET:
        value_string_set(sb, v & ~VALUE_MASK);
        break;
    case VALUE_ADDRESS:
        value_string_address(sb, v & ~VALUE_MASK);
        break;
    case VALUE_CONTEXT:
        value_string_context(sb, v & ~VALUE_MASK);
        break;
    default:
        panic("strbuf_value_string: bad value type");
    }
}

char *value_string(hvalue_t v){
    struct strbuf sb;
    strbuf_init(&sb);
    strbuf_value_string(&sb, v);
    return strbuf_convert(&sb);
}

void strbuf_value_json(struct strbuf *sb, hvalue_t v){
    switch (v & VALUE_MASK) {
    case VALUE_BOOL:
        value_json_bool(sb, v & ~VALUE_MASK);
        break;
    case VALUE_INT:
        value_json_int(sb, v & ~VALUE_MASK);
        break;
    case VALUE_ATOM:
        value_json_atom(sb, v & ~VALUE_MASK);
        break;
    case VALUE_PC:
        value_json_pc(sb, v & ~VALUE_MASK);
        break;
    case VALUE_DICT:
        value_json_dict(sb, v & ~VALUE_MASK);
        break;
    case VALUE_SET:
        value_json_set(sb, v & ~VALUE_MASK);
        break;
    case VALUE_ADDRESS:
        value_json_address(sb, v & ~VALUE_MASK);
        break;
    case VALUE_CONTEXT:
        value_json_context(sb, v & ~VALUE_MASK);
        break;
    default:
        panic("strbuf_value_json: bad value type");
    }
}

char *value_json(hvalue_t v){
    struct strbuf sb;
    strbuf_init(&sb);
    strbuf_value_json(&sb, v);
    return strbuf_convert(&sb);
}

bool atom_cmp(json_buf_t buf, char *s){
    unsigned int n = strlen(s);
    if (n != buf.len) {
        return false;
    }
    return strncmp(buf.base, s, n) == 0;
}

hvalue_t value_bool(struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_ATOM);
    if (atom_cmp(value->u.atom, "False")) {
        return VALUE_BOOL;
    }
    if (atom_cmp(value->u.atom, "True")) {
        return (1 << VALUE_BITS) | VALUE_BOOL;
    }
    panic("value_bool: bad value");
    return 0;
}

hvalue_t value_int(struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_ATOM);
    hvalue_t v;
    if (atom_cmp(value->u.atom, "inf")) {
        v = VALUE_MAX;
    }
    else if (atom_cmp(value->u.atom, "-inf")) {
        v = VALUE_MIN;
    }
    else {
        char *copy = malloc(value->u.atom.len + 1);
        memcpy(copy, value->u.atom.base, value->u.atom.len);
        copy[value->u.atom.len] = 0;
        v = atol(copy);
        free(copy);
    }
    return (v << VALUE_BITS) | VALUE_INT;
}

hvalue_t value_pc(struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_ATOM);
    char *copy = malloc(value->u.atom.len + 1);
    memcpy(copy, value->u.atom.base, value->u.atom.len);
    copy[value->u.atom.len] = 0;
    long v = atol(copy);
    free(copy);
    return (v << VALUE_BITS) | VALUE_PC;
}

hvalue_t value_atom(struct values_t *values, struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_ATOM);
    if (value->u.atom.len == 0) {
        return VALUE_ATOM;
    }
    void *p = dict_find(values->atoms, value->u.atom.base, value->u.atom.len);
    return (hvalue_t) p | VALUE_ATOM;
}

hvalue_t value_dict(struct values_t *values, struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_LIST);
    if (value->u.list.nvals == 0) {
        return VALUE_DICT;
    }
    hvalue_t *vals = malloc(value->u.list.nvals * sizeof(hvalue_t) * 2);
    for (unsigned int i = 0; i < value->u.list.nvals; i++) {
        struct json_value *jv = value->u.list.vals[i];
        assert(jv->type == JV_MAP);
        struct json_value *k = dict_lookup(jv->u.map, "key", 3);
        assert(k->type == JV_MAP);
        struct json_value *v = dict_lookup(jv->u.map, "value", 5);
        assert(v->type == JV_MAP);
        vals[2*i] = value_from_json(values, k->u.map);
        vals[2*i+1] = value_from_json(values, v->u.map);
    }

    // vals is sorted already by harmony compiler
    void *p = dict_find(values->dicts, vals,
                    value->u.list.nvals * sizeof(hvalue_t) * 2);
    free(vals);
    return (hvalue_t) p | VALUE_DICT;
}

hvalue_t value_set(struct values_t *values, struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_LIST);
    if (value->u.list.nvals == 0) {
        return (hvalue_t) VALUE_SET;
    }
    hvalue_t *vals = malloc(value->u.list.nvals * sizeof(hvalue_t));
    for (unsigned int i = 0; i < value->u.list.nvals; i++) {
        struct json_value *jv = value->u.list.vals[i];
        assert(jv->type == JV_MAP);
        vals[i] = value_from_json(values, jv->u.map);
    }

    // vals is sorted already by harmony compiler
    void *p = dict_find(values->sets, vals, value->u.list.nvals * sizeof(hvalue_t));
    free(vals);
    return (hvalue_t) p | VALUE_SET;
}

hvalue_t value_address(struct values_t *values, struct dict *map){
    struct json_value *value = dict_lookup(map, "value", 5);
    assert(value->type == JV_LIST);
    if (value->u.list.nvals == 0) {
        return (hvalue_t) VALUE_ADDRESS;
    }
    hvalue_t *vals = malloc(value->u.list.nvals * sizeof(hvalue_t));
    for (unsigned int i = 0; i < value->u.list.nvals; i++) {
        struct json_value *jv = value->u.list.vals[i];
        assert(jv->type == JV_MAP);
        vals[i] = value_from_json(values, jv->u.map);
    }
    void *p = dict_find(values->addresses, vals,
                            value->u.list.nvals * sizeof(hvalue_t));
    free(vals);
    return (hvalue_t) p | VALUE_ADDRESS;
}

hvalue_t value_from_json(struct values_t *values, struct dict *map){
    struct json_value *type = dict_lookup(map, "type", 4);
    assert(type != 0);
    assert(type->type == JV_ATOM);
    if (atom_cmp(type->u.atom, "bool")) {
        return value_bool(map);
    }
    else if (atom_cmp(type->u.atom, "int")) {
        return value_int(map);
    }
    else if (atom_cmp(type->u.atom, "atom")) {
        return value_atom(values, map);
    }
    else if (atom_cmp(type->u.atom, "dict")) {
        return value_dict(values, map);
    }
    else if (atom_cmp(type->u.atom, "set")) {
        return value_set(values, map);
    }
    else if (atom_cmp(type->u.atom, "pc")) {
        return value_pc(map);
    }
    else if (atom_cmp(type->u.atom, "address")) {
        return value_address(values, map);
    }
    else {
        panic("value_from_json: bad type");
        return 0;
    }
}

void value_init(struct values_t *values){
    values->atoms = dict_new(0);
    values->dicts = dict_new(0);
    values->sets = dict_new(0);
    values->addresses = dict_new(0);
    values->contexts = dict_new(0);
}

void value_set_concurrent(struct values_t *values, int concurrent){
    dict_set_concurrent(values->atoms, concurrent);
    dict_set_concurrent(values->dicts, concurrent);
    dict_set_concurrent(values->sets, concurrent);
    dict_set_concurrent(values->addresses, concurrent);
    dict_set_concurrent(values->contexts, concurrent);
}

// Store key:value in the given dictionary and returns its value code
hvalue_t value_dict_store(struct values_t *values, hvalue_t dict, hvalue_t key, hvalue_t value){
    assert((dict & VALUE_MASK) == VALUE_DICT);

    hvalue_t *vals;
    int size;
    if (dict == VALUE_DICT) {
        vals = NULL;
        size = 0;
    }
    else {
        vals = value_get(dict & ~VALUE_MASK, &size);
        size /= sizeof(hvalue_t);
        assert(size % 2 == 0);
    }

    int i;
    for (i = 0; i < size; i += 2) {
        if (vals[i] == key) {
            if (vals[i + 1] == value) {
                return dict;
            }
            int n = size * sizeof(hvalue_t);
            hvalue_t *copy = malloc(n);
            memcpy(copy, vals, n);
            copy[i + 1] = value;
            hvalue_t v = value_put_dict(values, copy, n);
            free(copy);
            return v;
        }
        if (value_cmp(vals[i], key) > 0) {
            break;
        }
    }

    int n = (size + 2) * sizeof(hvalue_t);
    hvalue_t *nvals = malloc(n);
    memcpy(nvals, vals, i * sizeof(hvalue_t));
    nvals[i] = key;
    nvals[i+1] = value;
    memcpy(&nvals[i+2], &vals[i], (size - i) * sizeof(hvalue_t));
    hvalue_t v = value_put_dict(values, nvals, n);
    free(nvals);
    return v;
}

hvalue_t value_dict_load(hvalue_t dict, hvalue_t key){
    assert((dict & VALUE_MASK) == VALUE_DICT);

    hvalue_t *vals;
    int size;
    if (dict == VALUE_DICT) {
        vals = NULL;
        size = 0;
    }
    else {
        vals = value_get(dict & ~VALUE_MASK, &size);
        size /= sizeof(hvalue_t);
        assert(size % 2 == 0);
    }

    int i;
    for (i = 0; i < size; i += 2) {
        if (vals[i] == key) {
            return vals[i + 1];
        }
        /*
            if (value_cmp(vals[i], key) > 0) {
                break;
            }
        */
    }

    printf("CAN'T FIND %s in %s\n", value_string(key), value_string(dict));
    panic("dict_load");
    return 0;
}

hvalue_t value_dict_remove(struct values_t *values, hvalue_t dict, hvalue_t key){
    assert((dict & VALUE_MASK) == VALUE_DICT);

    hvalue_t *vals;
    int size;
    if (dict == VALUE_DICT) {
        return VALUE_DICT;
    }
    vals = value_get(dict & ~VALUE_MASK, &size);
    size /= sizeof(hvalue_t);
    assert(size % 2 == 0);

    if (size == 2) {
        return vals[0] == key ? VALUE_DICT : dict;
    }

    int i;
    for (i = 0; i < size; i += 2) {
        if (vals[i] == key) {
            int n = (size - 2) * sizeof(hvalue_t);
            hvalue_t *copy = malloc(n);
            memcpy(copy, vals, i * sizeof(hvalue_t));
            memcpy(&copy[i], &vals[i+2],
                   (size - i - 2) * sizeof(hvalue_t));
            hvalue_t v = value_put_dict(values, copy, n);
            free(copy);
            return v;
        }
        /*
            if (value_cmp(vals[i], key) > 0) {
                assert(false);
            }
        */
    }

    return dict;
}

bool value_dict_tryload(
    struct values_t *values,
    hvalue_t dict,
    hvalue_t key,
    hvalue_t *result
){
    if ((dict & VALUE_MASK) == VALUE_ATOM) {
        if ((key & VALUE_MASK) != VALUE_INT) {
            return false;
        }
        key >>= VALUE_BITS;
        int size;
        char *chars = value_get(dict, &size);
        if ((int) key >= size) {
            return false;
        }
        *result = value_put_atom(values, chars + key, 1);
        return true;
    }

    if ((dict & VALUE_MASK) != VALUE_DICT) {
        return false;
    }

    hvalue_t *vals;
    int size;
    if (dict == VALUE_DICT) {
        vals = NULL;
        size = 0;
    }
    else {
        vals = value_get(dict & ~VALUE_MASK, &size);
        size /= sizeof(hvalue_t);
        assert(size % 2 == 0);
    }

    int i;
    for (i = 0; i < size; i += 2) {
        if (vals[i] == key) {
            *result = vals[i + 1];
            return true;
        }
        /*
            if (value_cmp(vals[i], key) > 0) {
                break;
            }
        */
    }
    return false;
}

hvalue_t value_bag_add(struct values_t *values, hvalue_t bag, hvalue_t v, int multiplicity){
    hvalue_t count;
    if (value_dict_tryload(values, bag, v, &count)) {
        assert((count & VALUE_MASK) == VALUE_INT);
        assert(count != VALUE_INT);
        count += multiplicity << VALUE_BITS;
        return value_dict_store(values, bag, v, count);
    }
    else {
        return value_dict_store(values, bag, v, (1 << VALUE_BITS) | VALUE_INT);
    }
}

void value_ctx_push(struct context **pctx, hvalue_t v){
    assert(*pctx != NULL);
    struct context *ctx = realloc(*pctx, sizeof(struct context) +
                                         ((*pctx)->sp + 1) * sizeof(hvalue_t));

    ctx->stack[ctx->sp++] = v;
    *pctx = ctx;
}

hvalue_t value_ctx_pop(struct context **pctx){
    struct context *ctx = *pctx;

    assert(ctx->sp > 0);
    return ctx->stack[--ctx->sp];
}

hvalue_t value_ctx_failure(struct context *ctx, struct values_t *values, char *fmt, ...){
    va_list args;

    assert(ctx->failure == 0);

    struct strbuf sb;
    strbuf_init(&sb);
    va_start(args, fmt);
    strbuf_vprintf(&sb, fmt, args);
    va_end(args);
    ctx->failure = value_put_atom(values, strbuf_getstr(&sb), strbuf_getlen(&sb));
    strbuf_deinit(&sb);

    return 0;
}

bool value_ctx_all_eternal(hvalue_t ctxbag) {
    if (ctxbag == VALUE_DICT) {     // optimization
        return true;
    }
    int size;
    hvalue_t *vals = value_get(ctxbag, &size);
    size /= sizeof(hvalue_t);
    bool all = true;
    for (int i = 0; i < size; i += 2) {
        assert((vals[i] & VALUE_MASK) == VALUE_CONTEXT);
        assert((vals[i + 1] & VALUE_MASK) == VALUE_INT);
        struct context *ctx = value_get(vals[i], NULL);
        assert(ctx != NULL);
        if (!ctx->eternal) {
            all = false;
            break;
        }
    }
    return all;
}
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

#ifndef HARMONY_COMBINE
#include "code.h"
#include "hashdict.h"
#include "ops.h"
#endif

static struct instr_t code_instr_parse(struct values_t *values, struct json_value *jv) {
    assert(jv->type == JV_MAP);
    struct json_value *op = dict_lookup(jv->u.map, "op", 2);
    assert(op->type == JV_ATOM);
    struct op_info *oi = ops_get(op->u.atom.base, op->u.atom.len);
    if (oi == NULL) {
        fprintf(stderr, "Unknown HVM instruction: %.*s\n", op->u.atom.len, op->u.atom.base);
        exit(1);
    }
    struct instr_t i;
    i.oi = oi;
    i.env = (*oi->init)(jv->u.map, values);
    i.choose = strcmp(oi->name, "Choose") == 0;
    i.load = strcmp(oi->name, "Load") == 0;
    i.store = strcmp(oi->name, "Store") == 0;
    i.del = strcmp(oi->name, "Del") == 0;
    i.print = strcmp(oi->name, "Print") == 0;
    i.retop = strcmp(oi->name, "Return") == 0;
    i.breakable = i.load || i.store || i.del || i.print;
    if (strcmp(oi->name, "AtomicInc") == 0) {
        const struct env_AtomicInc *ea = i.env;
        if (!ea->lazy) {
            i.breakable = true;
        }
    }
    return i;
}

struct code_t code_init_parse(struct values_t *values, struct json_value *json_code) {
    assert(json_code->type == JV_LIST);

    struct code_t code;
    code.len = json_code->u.list.nvals;
    code.instrs = malloc(code.len * sizeof(struct instr_t));

    for (unsigned int i = 0; i < json_code->u.list.nvals; i++) {
        code.instrs[i] = code_instr_parse(values, json_code->u.list.vals[i]);
    }

    code.code_map = dict_new(0);

    return code;
}
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#ifndef HARMONY_COMBINE
#include "graph.h"
#endif

#define new_alloc(t)	(t *) calloc(1, sizeof(t))

void graph_init(struct graph_t *graph, int initial_size) {
    assert(initial_size >= 1);
    graph->size = 0;
    graph->alloc_size = initial_size;
    graph->nodes = malloc(graph->alloc_size * sizeof(struct node *));
}

void graph_add(struct graph_t *graph, struct node *node) {
    node->id = graph->size;
    if (graph->size >= graph->alloc_size) {
        graph->alloc_size = (graph->alloc_size + 1) * 2;
        graph->nodes = realloc(graph->nodes, (graph->alloc_size * sizeof(struct node *)));
    }
    graph->nodes[graph->size++] = node;
}

static struct stack {
    struct stack *next;
    struct node *node;
} *stack;

static void kosaraju_visit(struct node *node) {
    if (node->visited) {
        return;
    }
    node->visited = true;

    for (struct edge *edge = node->fwd; edge != NULL; edge = edge->next) {
        kosaraju_visit(edge->node);
    }

    // Push node
    struct stack *s = new_alloc(struct stack);
    s->node = node;
    s->next = stack;
    stack = s;
}

static void kosaraju_assign(struct node *node, int component) {
    if (node->visited) {
        return;
    }
    node->visited = true;
    node->component = component;
    for (struct edge *edge = node->bwd; edge != NULL; edge = edge->next) {
        kosaraju_assign(edge->node, component);
    }
}

int graph_find_scc(struct graph_t *graph) {
    for (int i = 0; i < graph->size; i++) {
        kosaraju_visit(graph->nodes[i]);
    }

    // make sure all nodes are marked and on the stack
    // while at it clear all the visited flags
    int count = 0;
    for (struct stack *s = stack; s != NULL; s = s->next) {
        assert(s->node->visited);
        s->node->visited = false;
        count++;
    }
    assert(count == graph->size);

    count = 0;
    while (stack != NULL) {
        // Pop
        struct stack *top = stack;
        stack = top->next;
        struct node *next = top->node;
        free(top);

        if (!next->visited) {
            kosaraju_assign(next, count++);
        }
    }
    for (int i = 0; i < graph->size; i++) {
        assert(graph->nodes[i]->visited);
    }

    return count;
}

// For tracking data races
struct access_info *graph_ai_alloc(int multiplicity, int atomic, int pc) {
    struct access_info *ai = calloc(1, sizeof(*ai));
    ai->multiplicity = multiplicity;
    ai->atomic = atomic;
    ai->pc = pc;
    return ai;
}

void graph_check_for_data_race(
    struct node *node,
    struct minheap *warnings,
    struct values_t *values
) {
    // TODO.  We're checking both if x and y conflict and y and x conflict for any two x and y, which is redundant
    for (struct edge *edge = node->fwd; edge != NULL; edge = edge->next) {
        for (struct access_info *ai = edge->ai; ai != NULL; ai = ai->next) {
            if (ai->indices != NULL) {
                assert(ai->n > 0);
                if (ai->multiplicity > 1 && !ai->load && ai->atomic == 0) {
                    struct failure *f = new_alloc(struct failure);
                    f->type = FAIL_RACE;
                    f->choice = node->choice;
                    f->node = node;
                    f->address = value_put_address(values, ai->indices, ai->n * sizeof(hvalue_t));
                    minheap_insert(warnings, f);
                }
                else {
                    for (struct edge *edge2 = edge->next; edge2 != NULL; edge2 = edge2->next) {
                        for (struct access_info *ai2 = edge2->ai; ai2 != NULL; ai2 = ai2->next) {
                            if (ai2->indices != NULL && !(ai->load && ai2->load) &&
                                (ai->atomic == 0 || ai2->atomic == 0)) {
                                int min = ai->n < ai2->n ? ai->n : ai2->n;
                                assert(min > 0);
                                if (memcmp(ai->indices, ai2->indices,
                                           min * sizeof(hvalue_t)) == 0) {
                                    struct failure *f = new_alloc(struct failure);
                                    f->type = FAIL_RACE;
                                    f->choice = node->choice;
                                    f->node = node;
                                    f->address = value_put_address(values, ai->indices, min * sizeof(hvalue_t));
                                    minheap_insert(warnings, f);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#ifndef HARMONY_COMBINE
#include "strbuf.h"
#endif

// Initializes a string buffer
void strbuf_init(struct strbuf *sb){
    sb->allocated = 64;               // random constant
    sb->buf = malloc(sb->allocated);  // initial size;
    *sb->buf = 0;                     // always keep buf \0-terminated
    sb->len = '\0';
};

// Append the given string to the string buffer
// len does not include the terminal \0 byte if any
void strbuf_append(struct strbuf *sb, const char *str, unsigned int len){
    if (sb->len + len + 1 > sb->allocated) {
        sb->allocated += len + 1;   // include room for \0
        sb->allocated *= 2;         // add some buffer space
        sb->buf = realloc(sb->buf, sb->allocated);
    }
    memcpy(&sb->buf[sb->len], str, len);
    sb->len += len;
    sb->buf[sb->len] = '\0';
}

// Formatted vprint, appended to string buffer
void strbuf_vprintf(struct strbuf *sb, const char *fmt, va_list args) {
    va_list copy;
    va_copy(copy, args);
    int len = vsnprintf(NULL, 0, fmt, copy);
    if (len < 0) {
		fprintf(stderr, "strbuf_vprintf: vsnprintf failed\n");
        exit(1);
    }

    // allocate enough space also for the null byte
    if (sb->len + len + 1 > sb->allocated) {
        sb->allocated += len + 1;   // include room for \0
        sb->allocated *= 2;         // add some buffer space
        sb->buf = realloc(sb->buf, sb->allocated);
    }

    // print into the buffer, including null byte
    if (vsprintf(&sb->buf[sb->len], fmt, args) < 0) {
		fprintf(stderr, "strbuf_vprintf: vsprintf failed\n");
        exit(1);
	}
    sb->len += len;
}

// Formatted print, appended to string buffer
void strbuf_printf(struct strbuf *sb, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    strbuf_vprintf(sb, fmt, args);
    va_end(args);
}

// Get a pointer to the current string, which is \0-terminated
char *strbuf_getstr(struct strbuf *sb){
    return sb->buf;
}

// Get the length of the current string (not including terminal \0 byte)
unsigned int strbuf_getlen(struct strbuf *sb){
    return sb->len;
}

void strbuf_deinit(struct strbuf *sb){
    free(sb->buf);
    sb->buf = (char *) 1;       // simplifies finding bugs
}

// alternative to strbuf_deinit
char *strbuf_convert(struct strbuf *sb){
    return sb->buf;
}
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#ifndef HARMONY_COMBINE
#include "dot.h"
#endif

struct dot_graph_t *dot_graph_init(int alloc_len) {
    struct dot_graph_t *graph = malloc(sizeof(struct dot_graph_t));
    graph->nodes = malloc(alloc_len * sizeof(struct dot_node_t *));
    graph->_alloc_len = alloc_len;
    graph->len = 0;
    return graph;
}

void dot_graph_deinit(struct dot_graph_t *graph) {
    for (int i = 0; i < graph->len; i++) {
        free(graph->nodes[i]);
    }
    free(graph->nodes);
    free(graph);
}

struct dot_node_t *dot_graph_new_node(struct dot_graph_t *graph) {
    struct dot_node_t *node = malloc(sizeof(struct dot_node_t));
    node->name = "";
    node->fwd = NULL;
    node->fwd_len = 0;

    int node_idx = graph->len;
    graph->len++;
    assert(graph->len <= graph->_alloc_len);
    if (graph->len == graph->_alloc_len) {
        graph->_alloc_len = 2 * graph->len;
        graph->nodes = realloc(graph->nodes, graph->_alloc_len * sizeof(struct dot_node_t));
    }

    graph->nodes[node_idx] = node;
    return node;
}

void dot_graph_add_edge(struct dot_graph_t *graph, int from_idx, int to_idx) {
    struct dot_node_t *from_node = graph->nodes[from_idx];
    for (int i = 0; i < from_node->fwd_len; i++) {
        if (from_node->fwd[i] == to_idx) {
            return;
        }
    }

    from_node->fwd_len++;
    from_node->fwd = realloc(from_node->fwd, from_node->fwd_len * sizeof(int));
    from_node->fwd[from_node->fwd_len-1] = to_idx;
    graph->nodes[from_idx] = from_node;
}

void node_fprint(struct dot_node_t *node, int i, FILE *f) {
    if (node->initial) {
        fprintf(f, "i");
    } else if (node->terminating) {
        fprintf(f, "t");
    } else {
        fprintf(f, "s");
    }

    fprintf(f, "%d", i);
}

void dot_graph_fprint(struct dot_graph_t *graph, FILE *f) {
    fprintf(f, "digraph {\n");
    for (int i = 0; i < graph->len; i++) {
        struct dot_node_t *node = graph->nodes[i];

        fprintf(f, "  ");

        node_fprint(node, i, f);

        if (node->initial) {
            fprintf(f, " [label=__init__, shape=octagon]\n");
        } else if (node->terminating) {
            fprintf(f, " [label=\"%s\", shape=doubleoctagon]\n", node->name);
        } else if (node->choosing) {
            fprintf(f, " [label=\"%s\", shape=tripleoctagon]\n", node->name);
        } else {
            fprintf(f, " [label=\"%s\", shape=box]\n", node->name);
        }
    }

    for (int node_idx = 0; node_idx < graph->len; node_idx++) {
        struct dot_node_t *node = graph->nodes[node_idx];

        for (int fwd_idx = 0; fwd_idx < node->fwd_len; fwd_idx++) {
            struct dot_node_t *fwd = graph->nodes[node->fwd[fwd_idx]];

            fprintf(f, "  ");
            node_fprint(node, node_idx, f);
            fprintf(f, " -> ");
            node_fprint(fwd, node->fwd[fwd_idx], f);
            fprintf(f, "\n");
        }
    }
    fprintf(f, "}\n");
}
#include <stdint.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#ifndef HARMONY_COMBINE
#include "iface.h"
#include "hashset.h"
#include "iface_graph.h"
#endif

struct node_vec_t {
    struct node **arr;
    int len;
    int _alloc_len;
};

struct node_vec_t *node_vec_init(int alloc_len) {
    assert(alloc_len > 0);

    struct node_vec_t *vec = malloc(sizeof(struct node_vec_t));
    vec->_alloc_len = alloc_len;
    vec->len = 0;
    vec->arr = malloc(vec->_alloc_len * sizeof(struct node *));
    return vec;
}

void node_vec_deinit(struct node_vec_t *vec) {
    free(vec->arr);
    free(vec);
}

void node_vec_push(struct node_vec_t *vec, struct node *n) {
    vec->len++;
    if (vec->len > vec->_alloc_len) {
        vec->_alloc_len = 2 * vec->len;
        vec->arr = realloc(vec->arr, vec->_alloc_len * sizeof(struct node));
    }

    vec->arr[vec->len - 1] = n;
}

struct node *node_stack_pop(struct node_vec_t *vec) {
    assert(vec->len > 0);
    vec->len--;
    return vec->arr[vec->len];
}

bool node_vec_is_empty(struct node_vec_t *vec) {
    return vec->len == 0;
}

void node_vec_append_all(struct node_vec_t *dst, struct node_vec_t *src) {
    for (int i = 0; i < src->len; i++) {
        node_vec_push(dst, src->arr[i]);
    }
}

void node_vec_print_node_ids(struct node_vec_t *vec) {
    printf("[");
    for (int i = 0; i < vec->len - 1; i++) {
        printf("%d,", vec->arr[i]->id);
    }
    if (vec->len > 0) {
        printf("%d", vec->arr[vec->len - 1]->id);
    }
    printf("]\n");
}

struct node_vec_t *find_one_step_children(struct node *n) {
    struct node_vec_t *result = node_vec_init(1);
    struct node_vec_t *work = node_vec_init(1);
    struct hashset_t visited = hashset_new(0);

    node_vec_push(work, n);
    while (!node_vec_is_empty(work)) {
        struct node *node = node_stack_pop(work);
        if (hashset_contains(visited, &node, sizeof(struct node *))) {
            continue;
        }

        hashset_insert(visited, &node, sizeof(struct node *));

        struct context *ctx = value_get(node->after, NULL);
        if (node != n && (ctx->atomic == 0 || ctx->terminated)) {
            node_vec_push(result, node);
        } else {
            for (struct edge *edge = node->fwd; edge != NULL; edge = edge->next) {
                if (!hashset_contains(visited, &edge->node, sizeof(struct node *))) {
                    node_vec_push(work, edge->node);
                }
            }
        }
    }

    node_vec_deinit(work);
    hashset_delete(visited);

    return result;
}

struct node_vec_t *find_all_children(struct node *n) {
    struct node_vec_t *result = node_vec_init(1);
    for (struct edge *e = n->fwd; e != NULL; e = e->next) {
        node_vec_push(result, e->node);
    }
    return result;
}

uint64_t iface_evaluate(struct global_t *global, struct state *state, struct step *step) {
    step->ctx->terminated = false;
    step->ctx->failure = 0;
    step->ctx->sp = 0;
    step->ctx->pc++;

    while (!step->ctx->terminated) {
        struct op_info *oi = global->code.instrs[step->ctx->pc].oi;
        if (strcmp(oi->name, "Return") == 0) {
            break;
        }

        int oldpc = step->ctx->pc;
        (*oi->op)(global->code.instrs[oldpc].env, state, step, global);
        if (step->ctx->failure != 0) {
            step->ctx->sp = 0;
            return 0;
        }
        assert(step->ctx->pc != oldpc);
    }

    return value_dict_load(step->ctx->vars, value_put_atom(&global->values, "result", 6));
}

int iface_find_pc(struct code_t *code) {
    const int len = code->len;
    for (int i = 0; i < len; i++) {
        struct instr_t instr = code->instrs[i];
        struct op_info *oi = instr.oi;
        if (strcmp(oi->name, "Frame") == 0) {
            const struct env_Frame *envFrame = instr.env;
            if (strcmp(value_string(envFrame->name), ".__iface__") == 0) {
                return i;
            }
        }
    }
    return -1;
}

/**
 * Creates an iface graph containing the results of evaluating the iface
 * expression at every step.
 *
 * @param global
 * @param iface_pc - pc of the instr that starts the iface function
 * @return
 */
struct iface_graph_t *iface_evaluate_spec_graph(struct global_t *global, int iface_pc) {
    // Create a context for evaluating iface
    struct step iface_step;
    memset(&iface_step, 0, sizeof(iface_step));
    iface_step.ctx = new_alloc(struct context);
    iface_step.ctx->name = value_put_atom(&global->values, "__iface__", 8);
    iface_step.ctx->arg = VALUE_DICT;
    iface_step.ctx->this = VALUE_DICT;
    iface_step.ctx->vars = VALUE_DICT;
    iface_step.ctx->atomic = iface_step.ctx->readonly = 1;
    iface_step.ctx->interruptlevel = false;

    // make a guess that the spec graph will have half as many nodes as the
    // global graph
    struct iface_graph_t *iface_graph = iface_graph_init(global->graph.size / 2);

    // dict from (struct node *) to (struct iface_node_t *)
    struct dict *node_to_iface_node = dict_new(global->graph.size / 2);

    struct hashset_t visited = hashset_new(0);

    assert(global->graph.size > 0);
    struct node_vec_t *worklist = node_vec_init(1);
    {
        // process initial node
        struct node *initial_node = global->graph.nodes[0];
        node_vec_push(worklist, initial_node);
        struct iface_node_t **iface_node = (struct iface_node_t **)
                dict_insert(node_to_iface_node, &initial_node, sizeof(struct node *));
        *iface_node = iface_graph_add_node(iface_graph);

        // Give initial node the None value, though it's displayed as "__init__"
        (*iface_node)->value = VALUE_ADDRESS;
        (*iface_node)->initial = true;
        (*iface_node)->terminated = false;
        (*iface_node)->choosing = false;
        (*iface_node)->state = initial_node->state;

        struct node_vec_t *children = find_all_children(initial_node);
        node_vec_append_all(worklist, children);
        for (int i = 0; i < children->len; i++) {
            struct node *child = children->arr[i];

            struct iface_node_t **child_iface_node = (struct iface_node_t **)
                    dict_insert(node_to_iface_node, &child, sizeof(struct node *));

            if (*child_iface_node == NULL) {
                *child_iface_node = iface_graph_add_node(iface_graph);
            }

            iface_graph_add_edge(iface_graph, (*iface_node)->idx, (*child_iface_node)->idx);
        }

        hashset_insert(visited, &initial_node, sizeof(struct node *));
        node_vec_deinit(children);
    }

    while (!node_vec_is_empty(worklist)) {
        struct node *node = node_stack_pop(worklist);

        if (hashset_contains(visited, &node, sizeof(struct node *))) {
            continue;
        }

        struct iface_node_t *iface_node
                = dict_lookup(node_to_iface_node, &node, sizeof(struct node *));
        assert(iface_node != NULL);

        iface_step.ctx->pc = iface_pc;
        iface_node->value = iface_evaluate(global, node->state, &iface_step);
        iface_node->initial = false;
        iface_node->terminated = value_ctx_all_eternal(node->state->ctxbag);
        iface_node->choosing = node->state->choosing != 0;
        iface_node->state = node->state;
        if (iface_step.ctx->failure != 0) {
            iface_node->value = VALUE_ADDRESS;
#ifndef NDEBUG
            printf("Iface Failure: %s\n", value_string(iface_step.ctx->failure));
#endif
        }

        struct node_vec_t *children = find_all_children(node);
        node_vec_append_all(worklist, children);
        for (int i = 0; i < children->len; i++) {
            struct node *child = children->arr[i];

            struct iface_node_t **child_iface_node = (struct iface_node_t **)
                    dict_insert(node_to_iface_node, &child, sizeof(struct node *));

            if (*child_iface_node == NULL) {
                *child_iface_node = iface_graph_add_node(iface_graph);
            }

            iface_graph_add_edge(iface_graph, iface_node->idx, (*child_iface_node)->idx);
        }

        hashset_insert(visited, &node, sizeof(struct node *));
        node_vec_deinit(children);
    }

    free(iface_step.ctx);
    free(iface_step.log);

    node_vec_deinit(worklist);

    return iface_graph;
}

struct dot_graph_t *iface_convert_to_dot(struct iface_graph_t *graph) {
    struct dot_graph_t *dot = dot_graph_init(graph->nodes_len);

    for (int i = 0; i < graph->nodes_len; i++) {
        struct iface_node_t *node = graph->nodes[i];

        struct dot_node_t *dot_node = dot_graph_new_node(dot);
        dot_node->name = value_string(node->value);
        dot_node->terminating = node->terminated;
        dot_node->initial = node->initial;
        dot_node->choosing = node->choosing;
    }

    for (int i = 0; i < graph->edges_len; i++) {
        struct iface_edge_t *edge = graph->edges[i];
        if (edge->is_fwd) {
            dot_graph_add_edge(dot, edge->src->idx, edge->dst->idx);
        }
    }

    return dot;
}

struct dot_graph_t *iface_generate_dot_graph(struct global_t *global) {
    int iface_pc = iface_find_pc(&global->code);
    if (iface_pc < 0) {
        return NULL;
    }

    struct iface_graph_t *iface_graph = iface_evaluate_spec_graph(global, iface_pc);
    struct dot_graph_t *dot;
#if false
    struct iface_graph_t *destuttered = iface_graph_destutter(iface_graph);
    printf("iface destutter: removed %d states\n", (iface_graph->nodes_len - destuttered->nodes_len));
    iface_graph_deinit(iface_graph);
    dot = iface_convert_to_dot(destuttered);
    iface_graph_deinit(destuttered);
#else
    dot = iface_convert_to_dot(iface_graph);
#endif
    return dot;
}

void iface_write_spec_graph_to_file(struct global_t *global, const char* filename) {
    struct dot_graph_t *dot = iface_generate_dot_graph(global);
    if (dot == NULL) {
        return;
    }

    FILE *iface_file = fopen(filename, "w");
    if (iface_file == NULL) {
        perror(filename);
        exit(1);
    }
    dot_graph_fprint(dot, iface_file);

    dot_graph_deinit(dot);
    fclose(iface_file);
}

void iface_write_spec_graph_to_json_file(struct global_t *global, const char* filename) {
    int iface_pc = iface_find_pc(&global->code);
    if (iface_pc < 0) {
        return;
    }
    struct iface_graph_t *iface_graph = iface_evaluate_spec_graph(global, iface_pc);
#if false
    struct iface_graph_t *destuttered = iface_graph_destutter(iface_graph);
    iface_graph_deinit(iface_graph);
    iface_graph = destuttered;
#endif

    FILE *iface_file = fopen(filename, "w");
    if (iface_file == NULL) {
        perror(filename);
        exit(1);
    }

    fprintf(iface_file, "{\n");

    fprintf(iface_file, "  \"nodes\": [\n");
    bool first = true;
    for (int i = 0; i < iface_graph->nodes_len; i++) {
        struct iface_node_t *node = iface_graph->nodes[i];

        if (!first) {
            fprintf(iface_file, ",\n");
        }
        else {
            first = false;
        }
        fprintf(iface_file, "    {\n");
        fprintf(iface_file, "      \"idx\": %d,\n", node->idx);
        fprintf(iface_file, "      \"value\": \"%s\",\n", value_string(node->value));
        if (node->choosing) {
            assert(node->state != NULL);
            struct context *ctx = value_get(node->state->choosing, NULL);
            fprintf(iface_file, "      \"choosing_atomic_level\": %d,\n", ctx->atomic);
            fprintf(iface_file, "      \"choosing_atomic_flag\": %s,\n", ctx->atomicFlag ? "true" : "false");
        }
        fprintf(iface_file, "      \"type\": ");
        if (node->initial) {
            fprintf(iface_file, "\"initial\"");
        }
        else if (node->terminated) {
            fprintf(iface_file, "\"terminal\"");
        }
        else if (node->choosing) {
            fprintf(iface_file, "\"choose\"");
        }
        else {
            fprintf(iface_file, "\"normal\"");
        }
        fprintf(iface_file, "\n");
        fprintf(iface_file, "    }");
    }
    fprintf(iface_file, "\n");
    fprintf(iface_file, "  ],\n");

    fprintf(iface_file, "  \"edges\": [\n");
    first = true;
    for (int i = 0; i < iface_graph->edges_len; i++) {
        struct iface_edge_t *edge = iface_graph->edges[i];

        if (edge->is_fwd) {
            if (!first) {
                fprintf(iface_file, ",\n");
            }
            else {
                first = false;
            }
            fprintf(iface_file, "    {\n");
            fprintf(iface_file, "      \"src\": %d,\n", edge->src->idx);
            fprintf(iface_file, "      \"dst\": %d\n", edge->dst->idx);
            fprintf(iface_file, "    }");
        }
    }
    fprintf(iface_file, "\n");
    fprintf(iface_file, "  ]\n");

    fprintf(iface_file, "}\n");

    iface_graph_deinit(iface_graph);
}
