aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/stack_machine_instruction.h1
-rw-r--r--tools/translate.c625
-rw-r--r--tools/translate_xmacro.h1
-rw-r--r--tools/wasm.h1
4 files changed, 408 insertions, 220 deletions
diff --git a/tools/stack_machine_instruction.h b/tools/stack_machine_instruction.h
index ea65334..003fa19 100644
--- a/tools/stack_machine_instruction.h
+++ b/tools/stack_machine_instruction.h
@@ -146,6 +146,7 @@ Y(nop, 0x0001) /* 0000_0000_0000_0001 */
Y(swap, 0x0002) /* 0000_0000_0000_0010 */
X(set_sp, 0x4000) /* 0100_0000_0xxx_xxxx */
X(jump, 0x4080) /* 0100_0000_1xxx_xxxx */
+X(add_sp, 0x4100) /* 0100_0001_0xxx_xxxx */
Y(tee, 0x1000) /* 0001_0000_0000_0000 */
Y(get_frame, 0x1001) /* 0001_0000_0000_0001 */
X(const, 0x5000) /* 0101_0000_0xxx_xxxx */
diff --git a/tools/translate.c b/tools/translate.c
index 3a4496d..64ed407 100644
--- a/tools/translate.c
+++ b/tools/translate.c
@@ -7,6 +7,13 @@ struct target {
struct target *prev;
};
+struct label {
+ struct label *prev;
+ struct target *target;
+ struct resulttype *arity;
+ uint32_t values_on_stack;
+};
+
struct types {
struct types *prev;
char type; /* should be one of VALTYPE_* constants from wasm.h */
@@ -18,6 +25,7 @@ struct translation {
struct function *function;
struct module *module;
struct types *types_stack;
+ struct label *labels;
};
struct end_markers {
@@ -53,6 +61,18 @@ static void put_type(struct types *type)
}
}
+static uint32_t stack_size(struct types *types_stack)
+{
+ uint32_t count = 0;
+
+ while (types_stack) {
+ count++;
+ types_stack = types_stack->prev;
+ }
+
+ return count;
+}
+
static void free_types_stack(struct types *top)
{
struct types *tmp;
@@ -67,94 +87,196 @@ static void free_types_stack(struct types *top)
static int translate_expr(struct translation *data, struct resulttype *args,
struct resulttype *results,
const struct end_markers *end_markers,
- char *marker_found);
+ char *marker_found, bool continuation_at_start);
+
+static struct target *add_target(struct module *module);
+
+static int parse_blocktype(FILE *handle, struct resulttype *args,
+ struct resulttype *results, char *storage,
+ struct module *module);
/* All functions, that go into one of function pointer arrays, start with _ */
-/** DEFINE INSTRUCTION TRANSLATION FUNCTIONS **/
+/** DEFINE ARGUMENT TYPECHECK FUNCTIONS **/
-/* Translate complex - those routines have to be defined manually */
-#define TC(wasm_opcode, name, argtypes, restype)
+static int _argcheck_empty(struct types **types_stack)
+{
+ return 0;
+}
-static int parse_blocktype(FILE *handle, struct resulttype *args,
- struct resulttype *results, char *storage,
- struct module *module)
+static int argcheck_generic_noremove(struct types *types_stack, char expected)
{
- int readval;
- uint32_t typeidx;
+ char *name;
- readval = fgetc(handle);
+ name =
+ expected == VALTYPE_F64 ? "f64" :
+ expected == VALTYPE_F32 ? "f32" :
+ expected == VALTYPE_I64 ? "i64" :
+ "i32";
- if (readval == EOF) {
- PRERR(MSG_EOF);
+ if (!types_stack) {
+ PRERR("Expected %s on stack, got nothing\n", name);
return -1;
}
- if (readval == 0x40) {
- /* Blocktype is empty (no arguments, no result values) */
- *args = (struct resulttype) {.count = 0, .types = NULL};
- *results = *args;
- return 0;
+ if (types_stack->type != VALTYPE_I32) {
+ PRERR("Expected %s (0x%02hhx) on stack, got 0x%02hhx\n",
+ name, expected, types_stack->type);
+ return -1;
}
- /*
- * A nonnegative array index encoded as signed number in LEB
- * shall have 0 as the second (most significant) bit of the first byte.
- * Otherwise, it can't be array index, but might be a simple value type.
- */
- if (readval & (1 << 6)) {
- if (!is_valid_valtype(readval))
- goto fail;
+ return 0;
+}
- *args = (struct resulttype) {.count = 0, .types = NULL};
- *storage = readval;
- *results = (struct resulttype) {.count = 1, .types = storage};
- return 0;
- }
+static int argcheck_generic(struct types **types_stack, char expected)
+{
+ struct types *top_type;
- /*
- * We know for sure it's a nonnegative number, we can just use leb_u32
- * decoding function (encoding as signed or unsigned is the same in this
- * particular case).
- */
- ungetc(readval, handle);
+ if (argcheck_generic_noremove(*types_stack, expected))
+ return -1;
- if (leb_u32(handle, &typeidx)) {
- PRERR(MSG_BAD_NUM);
- goto fail;
+ top_type = *types_stack;
+ *types_stack = top_type->prev;
+
+ if (*types_stack)
+ get_type(*types_stack);
+
+ put_type(top_type);
+
+ return 0;
+}
+
+static int _argcheck_i32(struct types **types_stack)
+{
+ return argcheck_generic(types_stack, VALTYPE_I32);
+}
+
+static int _argcheck_i32_i32(struct types **types_stack)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (argcheck_generic(types_stack, VALTYPE_I32))
+ return -1;
}
- if (typeidx <= module->functypes_count) {
- PRERR(MSG_BAD_IDX("type index"));
- goto fail;
+ return 0;
+}
+
+static int _argcheck_custom(struct types **types_stack)
+{
+ return 0; /* Translation function will handle argument checks */
+}
+
+/** DEFINE RESULT TYPECHECK FUNCTIONS **/
+
+static int _rescheck_empty(struct types **types_stack)
+{
+ return 0;
+}
+
+static int rescheck_generic(struct types **types_stack, char returned)
+{
+ struct types *top;
+
+ top = malloc(sizeof(struct types));
+
+ if (!top) {
+ PRERR(MSG_ALLOC_FAIL(sizeof(struct types)));
+ return -1;
}
- *args = module->functypes[typeidx].args;
- *results = module->functypes[typeidx].results;
+ *top = (struct types) {.prev = *types_stack, .type = returned, .refs = 1};
+ *types_stack = top;
+
return 0;
+}
-fail:
- PRERR("Couldn't parse blocktype\n");
- return -1;
+static int _rescheck_i32(struct types **types_stack)
+{
+ return rescheck_generic(types_stack, VALTYPE_I32);
}
-static struct target *add_target(struct module *module)
+static int _rescheck_custom(struct types **types_stack)
{
- struct target *tgt;
+ return 0;
+}
- tgt = malloc(sizeof(struct target));
+/** DEFINE TYPECHECK FUNCTION POINTER ARRAY **/
- if (!tgt) {
- PRERR(MSG_ALLOC_FAIL(sizeof(struct target)));
- return NULL;
+/* Translate complex */
+#define TC(wasm_opcode, name, argtypes, restype) \
+ [wasm_opcode] = {.argcheck = _argcheck_##argtypes, \
+ .rescheck = _rescheck_##restype},
+
+/* Translate Simple */
+#define TS(wasm_opcode, sm_instr, argtypes, restype) \
+ TC(wasm_opcode, dummy, argtypes, restype)
+
+/* Translate load/store */
+#define TLS(wasm_opcode, sm_instr, argtypes, restype) \
+ TC(wasm_opcode, dummy, argtypes, restype)
+
+struct typecheck {
+ int (*argcheck) (struct types **), (*rescheck) (struct types **);
+};
+
+static struct typecheck typecheck_routines[256] = {
+#include "translate_xmacro.h"
+};
+
+#undef TS
+#undef TLS
+#undef TC
+
+/** DEFINE CUSTOM TYPECHECK FUNCTIONS **/
+
+/*
+ * Each of these is called by its respective translate function. In some cases
+ * it was not feasible to move some instruction's typecheck to a separate
+ * function, so there are less functions here than instructions with declared
+ * "custom" typechecking.
+ */
+
+static int typecheck_call(struct translation *data, struct function *callee)
+{
+ uint32_t i;
+
+ i = callee->type->args.count;
+
+ while (i--) {
+ if (argcheck_generic(&data->types_stack,
+ callee->type->args.types[i]))
+ return -1;
}
- tgt->instr = NULL;
- tgt->prev = module->targets;
- module->targets = tgt;
- return tgt;
+ for (i = 0; i < callee->type->results.count; i++) {
+ if (rescheck_generic(&data->types_stack,
+ callee->type->results.types[i]))
+ return -1;
+ }
+
+ return 0;
}
+static int typecheck_local_get(struct translation *data, uint32_t localidx)
+{
+ uint32_t args_count = data->function->type->args.count;
+ char type = localidx < args_count ?
+ data->function->type->args.types[localidx] :
+ data->function->locals[localidx - args_count];
+
+ if (rescheck_generic(&data->types_stack, type))
+ return -1;
+
+ return 0;
+}
+
+/** DEFINE INSTRUCTION TRANSLATION FUNCTIONS **/
+
+/* Translate complex - those routines have to be defined manually */
+#define TC(wasm_opcode, name, argtypes, restype)
+
static int _translate_if(struct translation *data)
{
struct types *backed_stack;
@@ -191,7 +313,7 @@ static int _translate_if(struct translation *data)
get_type(backed_stack);
retval = translate_expr(data, &block_args, &block_results,
- &if_end_markers, &marker_found);
+ &if_end_markers, &marker_found, false);
put_type(data->types_stack);
data->types_stack = backed_stack;
@@ -208,7 +330,7 @@ static int _translate_if(struct translation *data)
ungetc(WASM_END, data->handle);
if (translate_expr(data, &block_args, &block_results,
- &else_end_markers, NULL))
+ &else_end_markers, NULL, false))
goto fail;
else_end->instr = data->function->translated_body->prev;
@@ -221,7 +343,88 @@ fail:
return -1;
}
-static int typecheck_call(struct translation *data, struct function *callee);
+static int _translate_br(struct translation *data)
+{
+ uint32_t labelidx, i;
+ struct label *label;
+ uint32_t arity, values_on_stack;
+ uint32_t shift, offset_src, offset_dst;
+ struct instruction **expr = &data->function->translated_body;
+
+ if (leb_u32(data->handle, &labelidx)) {
+ PRERR(MSG_BAD_NUM);
+ goto fail;
+ }
+
+ label = data->labels;
+ i = labelidx;
+
+ while (i--) {
+ label = label->prev;
+
+ if (!label) {
+ PRERR(MSG_BAD_IDX("labelidx"));
+ goto fail;
+ }
+ }
+
+ values_on_stack = stack_size(data->types_stack);
+ arity = label->arity ? label->arity->count : 0;
+
+ if (arity > values_on_stack) {
+ PRERR("Need %lu values on stack to branch, only have %lu\n",
+ (unsigned long) arity, (unsigned long) values_on_stack);
+ goto fail;
+ }
+
+ i = arity;
+
+ while (i--) {
+ if (argcheck_generic(&data->types_stack,
+ label->arity->types[i]))
+ goto fail;
+ }
+
+ shift = data->labels->values_on_stack + values_on_stack -
+ (label->values_on_stack + arity);
+ offset_dst = label->values_on_stack + 2;
+ offset_src = offset_dst + shift;
+
+ if (!shift)
+ goto values_moved;
+
+ if (i_load (im(STACK_FRAME_BACKUP_ADDR), expr) ||
+ i_tee ( expr) ||
+ i_load_p(im(-4 * (offset_dst - 1)), expr) ||
+ i_swap ( expr) ||
+ i_load_p(im(-4 * offset_dst), expr) ||
+ i_add_sp(im(-4 * (shift + 2)), expr))
+ goto fail;
+
+ for (i = 0; i < arity; i++) {
+ if (i == 0 && i_load(im(STACK_FRAME_BACKUP_ADDR), expr))
+ goto fail;
+
+ if (i + 1 != arity && (i_tee(expr)))
+ goto fail;
+
+ if (i_load_p(im(-4 * (offset_src + i)), expr))
+ goto fail;
+
+ if (i + 1 != arity && (i_swap(expr)))
+ goto fail;
+ }
+
+values_moved:
+ if (i_jump(ptr_after(&label->target->instr), expr))
+ goto fail;
+
+ return 0;
+
+fail:
+ PRERR("Couldn't translate br instruction\n");
+ return -1;
+}
static int _translate_call(struct translation *data)
{
@@ -358,210 +561,162 @@ static int (*translation_routines[256]) (struct translation *) = {
#undef TLS
#undef TC
-/** DEFINE ARGUMENT TYPECHECK FUNCTIONS **/
-
-static int _argcheck_empty(struct types **types_stack)
-{
- return 0;
-}
+/** REST OF THE CODE **/
-static int argcheck_generic_noremove(struct types *types_stack, char expected)
+static int translate_instr(struct translation *data, uint8_t wasm_opcode)
{
- char *name;
-
- name =
- expected == VALTYPE_F64 ? "f64" :
- expected == VALTYPE_F32 ? "f32" :
- expected == VALTYPE_I64 ? "i64" :
- "i32";
-
- if (!types_stack) {
- PRERR("Expected %s on stack, got nothing\n", name);
- return -1;
- }
+ struct typecheck *tc_routines;
- if (types_stack->type != VALTYPE_I32) {
- PRERR("Expected %s (0x%02hhx) on stack, got 0x%02hhx\n",
- name, expected, types_stack->type);
+ if (!translation_routines[wasm_opcode]) {
+ PRERR("Unknown Wasm opcode: 0x%02x\n", wasm_opcode);
return -1;
}
- return 0;
-}
-
-static int argcheck_generic(struct types **types_stack, char expected)
-{
- struct types *top_type;
+ tc_routines = typecheck_routines + wasm_opcode;
- if (argcheck_generic_noremove(*types_stack, expected))
+ if (tc_routines->argcheck(&data->types_stack) ||
+ tc_routines->rescheck(&data->types_stack) ||
+ translation_routines[wasm_opcode](data))
return -1;
- top_type = *types_stack;
- *types_stack = top_type->prev;
-
- if (*types_stack)
- get_type(*types_stack);
-
- put_type(top_type);
-
return 0;
}
-static int _argcheck_i32(struct types **types_stack)
+static int parse_blocktype(FILE *handle, struct resulttype *args,
+ struct resulttype *results, char *storage,
+ struct module *module)
{
- return argcheck_generic(types_stack, VALTYPE_I32);
-}
+ int readval;
+ uint32_t typeidx;
-static int _argcheck_i32_i32(struct types **types_stack)
-{
- int i;
+ readval = fgetc(handle);
- for (i = 0; i < 2; i++) {
- if (argcheck_generic(types_stack, VALTYPE_I32))
- return -1;
+ if (readval == EOF) {
+ PRERR(MSG_EOF);
+ return -1;
}
- return 0;
-}
-
-static int _argcheck_custom(struct types **types_stack)
-{
- return 0; /* Translation function will handle argument checks */
-}
-
-/** DEFINE RESULT TYPECHECK FUNCTIONS **/
+ if (readval == 0x40) {
+ /* Blocktype is empty (no arguments, no result values) */
+ *args = (struct resulttype) {.count = 0, .types = NULL};
+ *results = *args;
+ return 0;
+ }
-static int _rescheck_empty(struct types **types_stack)
-{
- return 0;
-}
+ /*
+ * A nonnegative array index encoded as signed number in LEB
+ * shall have 0 as the second (most significant) bit of the first byte.
+ * Otherwise, it can't be array index, but might be a simple value type.
+ */
+ if (readval & (1 << 6)) {
+ if (!is_valid_valtype(readval))
+ goto fail;
-static int rescheck_generic(struct types **types_stack, char returned)
-{
- struct types *top;
+ *args = (struct resulttype) {.count = 0, .types = NULL};
+ *storage = readval;
+ *results = (struct resulttype) {.count = 1, .types = storage};
+ return 0;
+ }
- top = malloc(sizeof(struct types));
+ /*
+ * We know for sure it's a nonnegative number, we can just use leb_u32
+ * decoding function (encoding as signed or unsigned is the same in this
+ * particular case).
+ */
+ ungetc(readval, handle);
- if (!top) {
- PRERR(MSG_ALLOC_FAIL(sizeof(struct types)));
- return -1;
+ if (leb_u32(handle, &typeidx)) {
+ PRERR(MSG_BAD_NUM);
+ goto fail;
}
- *top = (struct types) {.prev = *types_stack, .type = returned, .refs = 1};
- *types_stack = top;
+ if (typeidx <= module->functypes_count) {
+ PRERR(MSG_BAD_IDX("type index"));
+ goto fail;
+ }
+ *args = module->functypes[typeidx].args;
+ *results = module->functypes[typeidx].results;
return 0;
-}
-
-static int _rescheck_i32(struct types **types_stack)
-{
- return rescheck_generic(types_stack, VALTYPE_I32);
-}
-static int _rescheck_custom(struct types **types_stack)
-{
- return 0;
+fail:
+ PRERR("Couldn't parse blocktype\n");
+ return -1;
}
-/** DEFINE TYPECHECK FUNCTION POINTER ARRAY **/
-
-/* Translate complex */
-#define TC(wasm_opcode, name, argtypes, restype) \
- [wasm_opcode] = {.argcheck = _argcheck_##argtypes, \
- .rescheck = _rescheck_##restype},
-
-/* Translate Simple */
-#define TS(wasm_opcode, sm_instr, argtypes, restype) \
- TC(wasm_opcode, dummy, argtypes, restype)
-
-/* Translate load/store */
-#define TLS(wasm_opcode, sm_instr, argtypes, restype) \
- TC(wasm_opcode, dummy, argtypes, restype)
-
-struct typecheck {
- int (*argcheck) (struct types **), (*rescheck) (struct types **);
-};
-
-static struct typecheck typecheck_routines[256] = {
-#include "translate_xmacro.h"
-};
-
-#undef TS
-#undef TLS
-#undef TC
-
-/** DEFINE CUSTOM TYPECHECK FUNCTIONS **/
-
-static int typecheck_call(struct translation *data, struct function *callee)
+static struct target *add_target(struct module *module)
{
- uint32_t i;
-
- i = callee->type->args.count;
+ struct target *tgt;
- while (i--) {
- if (argcheck_generic(&data->types_stack,
- callee->type->args.types[i]))
- return -1;
- }
+ tgt = malloc(sizeof(struct target));
- for (i = 0; i < callee->type->results.count; i++) {
- if (rescheck_generic(&data->types_stack,
- callee->type->results.types[i]))
- return -1;
+ if (!tgt) {
+ PRERR(MSG_ALLOC_FAIL(sizeof(struct target)));
+ return NULL;
}
- return 0;
+ tgt->instr = NULL;
+ tgt->prev = module->targets;
+ module->targets = tgt;
+ return tgt;
}
-static int typecheck_local_get(struct translation *data, uint32_t localidx)
+static struct label *add_label(struct translation *data, struct target *target,
+ struct resulttype *arity)
{
- uint32_t args_count = data->function->type->args.count;
- char type = localidx < args_count ?
- data->function->type->args.types[localidx] :
- data->function->locals[localidx - args_count];
+ struct label *lbl;
- if (rescheck_generic(&data->types_stack, type))
- return -1;
+ lbl = malloc(sizeof(struct label));
- return 0;
-}
-
-/** REST OF THE CODE **/
-
-static int translate_instr(struct translation *data, uint8_t wasm_opcode)
-{
- struct typecheck *tc_routines;
-
- if (!translation_routines[wasm_opcode]) {
- PRERR("Unknown Wasm opcode: 0x%02x\n", wasm_opcode);
- return -1;
+ if (!lbl) {
+ PRERR(MSG_ALLOC_FAIL(sizeof(struct label)));
+ return NULL;
}
- tc_routines = typecheck_routines + wasm_opcode;
+ lbl->prev = data->labels;
+ lbl->target = target;
+ lbl->arity = arity;
+ lbl->values_on_stack = data->labels ? data->labels->values_on_stack : 0;
+ lbl->values_on_stack += stack_size(data->types_stack);
- if (tc_routines->argcheck(&data->types_stack) ||
- tc_routines->rescheck(&data->types_stack) ||
- translation_routines[wasm_opcode](data))
- return -1;
+ data->labels = lbl;
- return 0;
+ return lbl;
}
static int translate_expr(struct translation *data, struct resulttype *args,
struct resulttype *results,
const struct end_markers *end_markers,
- char *marker_found)
+ char *marker_found, bool continuation_at_start)
{
- struct types **tmp, *types_stack_rest;
- uint32_t i;
+ struct target *continuation;
+ struct label *label = NULL;
+ struct types **tmp, *types_stack_rest = NULL;
+ uint32_t i, rescount = results ? results->count : 0;
int wasm_opcode;
+ bool last_instruction_was_branch = false;
+ int retval = -1;
+
+ continuation = add_target(data->module);
+
+ if (!continuation)
+ goto fail;
+
+ if (continuation_at_start)
+ continuation->instr = data->function->translated_body->prev;
+
+ label = add_label(data, continuation,
+ continuation_at_start ? args : results);
+
+ if (!label)
+ goto fail;
tmp = &data->types_stack;
i = args ? args->count : 0;
while (i--) {
if (argcheck_generic_noremove(*tmp, args->types[i]))
- return -1;
+ goto fail;
tmp = &(*tmp)->prev;
}
@@ -580,19 +735,40 @@ static int translate_expr(struct translation *data, struct resulttype *args,
i = end_markers->count;
while (i--) {
- if (wasm_opcode == end_markers->codes[i]) {
- if (marker_found)
- *marker_found = wasm_opcode;
-
+ if (wasm_opcode == end_markers->codes[i])
goto block_end;
- }
}
if (translate_instr(data, wasm_opcode))
goto fail;
+
+ /* WASM_BR_TABLE will also appear here once implemented */
+ last_instruction_was_branch =
+ wasm_opcode == WASM_BR;
}
block_end:
+ if (marker_found)
+ *marker_found = wasm_opcode;
+
+ if (!continuation_at_start)
+ continuation->instr = data->function->translated_body->prev;
+
+ /*
+ * Types on stack don't seem to matter, if last instruction was an
+ * unconditional branch anyway. However, we need to make the types stack
+ * appear ok to our caller.
+ */
+ if (last_instruction_was_branch) {
+ put_type(data->types_stack);
+ data->types_stack = NULL;
+
+ for (i = 0; i < rescount; i++) {
+ if (rescheck_generic(&data->types_stack,
+ results->types[i]))
+ goto fail;
+ }
+ }
tmp = &data->types_stack;
i = results ? results->count : 0;
@@ -610,13 +786,21 @@ block_end:
}
*tmp = types_stack_rest;
- return 0;
+ types_stack_rest = NULL;
+ retval = 0;
fail:
- put_type(data->types_stack);
- data->types_stack = types_stack_rest;
+ if (label) {
+ data->labels = label->prev;
+ free(label);
+ }
- return -1;
+ if (types_stack_rest) {
+ put_type(data->types_stack);
+ data->types_stack = types_stack_rest;
+ }
+
+ return retval;
}
int translate(FILE *handle, struct function *function, struct module *module)
@@ -634,7 +818,8 @@ int translate(FILE *handle, struct function *function, struct module *module)
struct translation data = {.handle = handle,
.function = function,
.module = module,
- .types_stack = NULL};
+ .types_stack = NULL,
+ .labels = NULL};
int retval = -1;
if (locals_count + (uint64_t) args_count > STACK_TOP_ADDR * 4) {
@@ -657,7 +842,7 @@ int translate(FILE *handle, struct function *function, struct module *module)
/* actual function body */
if (translate_expr(&data, NULL, &function->type->results,
- &function_end_markers, NULL))
+ &function_end_markers, NULL, false))
goto fail;
/* function epilogue */
diff --git a/tools/translate_xmacro.h b/tools/translate_xmacro.h
index b271e75..f68f317 100644
--- a/tools/translate_xmacro.h
+++ b/tools/translate_xmacro.h
@@ -20,6 +20,7 @@ TLS(WASM_I32_STORE16, storew_p, i32_i32, empty)
* another way and only check for the i32 condition value here.
*/
TC (WASM_IF, if, i32, custom)
+TC (WASM_BR, br, custom, custom)
TC (WASM_CALL, call, custom, custom)
TC (WASM_LOCAL_GET, local_get, empty, custom)
TC (WASM_I32_CONST, const, empty, i32)
diff --git a/tools/wasm.h b/tools/wasm.h
index bf85490..0c36c11 100644
--- a/tools/wasm.h
+++ b/tools/wasm.h
@@ -25,6 +25,7 @@
#define WASM_IF 0x04
#define WASM_ELSE 0x05
#define WASM_END 0x0B
+#define WASM_BR 0x0C
#define WASM_CALL 0x10
#define WASM_LOCAL_GET 0x20