CC="gcc"
WARNINGS='-Wimplicit -Wmisleading-indentation -Wparentheses -Wsequence-point -Wreturn-type -Wshift-negative-value -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label -Wmaybe-uninitialized -Wsign-compare -Wstrict-overflow -Wduplicated-branches -Wduplicated-cond -Wtrigraphs -Waddress -Wlogical-op'
FLAGS="-g3"
-LIBS=
+LIBS="-pthread"
INCLUDES="-I include"
TARGET="libonyx_embedder.so"
C_FILES="src/wasm.c src/vm/*.c src/wasm/*.c"
C_FILES="src/ovm_cli_test.c"
TARGET=bin/ovm_cli_test
-LIBS="-L$(pwd) -lonyx_embedder -lm -Wl,-rpath=./"
+LIBS="-L$(pwd) -lonyx_embedder -pthread -lm -Wl,-rpath=./"
$CC $FLAGS $INCLUDES -o $TARGET $C_FILES $LIBS $WARNINGS
#include "bh.h"
#include <stdbool.h>
+#include <pthread.h>
typedef u8 ovm_valtype_t;
typedef i32 ovm_valnum_t;
void ovm_program_register_func(ovm_program_t *program, char *name, i32 instr, i32 param_count, i32 value_number_count);
void ovm_program_register_external_func(ovm_program_t *program, char *name, i32 param_count, i32 external_func_idx);
void ovm_program_begin_func(ovm_program_t *program, char *name, i32 param_count, i32 value_number_count);
+void ovm_program_modify_static_int(ovm_program_t *program, int arr, int idx, int new_value);
//
// Represents the running configuration and static
struct ovm_engine_t {
ovm_store_t *store;
+ pthread_mutex_t atomic_mutex;
+
i64 memory_size; // This is probably going to always be 4GiB.
void *memory;
};
#define OVM_INSTR_TYPE(instr) ((instr).full_instr >> 24)
#define OVM_INSTR_INSTR(instr) ((instr).full_instr & 0xffffff)
+#define OVMI_ATOMIC 0x00800000 // Flag an instruction as atomic
#define OVMI_NOP 0x00
#define OVMI_ADD 0x01 // %r = %a + %b
#define OVMI_TRANSMUTE_F32 0x6C // %r = *(t *) &%a (reinterpret bytes)
#define OVMI_TRANSMUTE_F64 0x6D // %r = *(t *) &%a (reinterpret bytes)
+#define OVMI_CMPXCHG 0x70 // %r = %r == %a ? %b : %r
+
//
// OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I32) == instruction for adding i32s
//
i32 instr;
};
+enum branch_patch_kind_t {
+ branch_patch_instr_a, // For patching the '.a' register of a branch instruction.
+ branch_patch_static_idx, // For patching an integer in the static integers section.
+};
+
struct branch_patch_t {
+ enum branch_patch_kind_t kind;
i32 branch_instr;
i32 label_idx;
+ i32 static_arr;
+ i32 static_idx;
};
ovm_code_builder_t ovm_code_builder_new(ovm_program_t *program, i32 param_count, i32 local_count);
void ovm_code_builder_add_imm(ovm_code_builder_t *builder, u32 ovm_type, void *imm);
void ovm_code_builder_add_branch(ovm_code_builder_t *builder, i32 label_idx);
void ovm_code_builder_add_cond_branch(ovm_code_builder_t *builder, i32 label_idx);
+void ovm_code_builder_add_branch_table(ovm_code_builder_t *builder, i32 count, i32 *label_indicies, i32 default_label_idx);
void ovm_code_builder_add_return(ovm_code_builder_t *builder);
void ovm_code_builder_add_call(ovm_code_builder_t *builder, i32 func_idx, i32 param_count, bool has_return_value);
void ovm_code_builder_add_indirect_call(ovm_code_builder_t *builder, i32 param_count, bool has_return_value);
void ovm_code_builder_add_register_set(ovm_code_builder_t *builder, i32 local_idx);
void ovm_code_builder_add_load(ovm_code_builder_t *builder, u32 ovm_type, i32 offset);
void ovm_code_builder_add_store(ovm_code_builder_t *builder, u32 ovm_type, i32 offset);
+void ovm_code_builder_add_cmpxchg(ovm_code_builder_t *builder, u32 ovm_type, i32 offset);
void ovm_code_builder_add_memory_copy(ovm_code_builder_t *builder);
void ovm_code_builder_add_memory_fill(ovm_code_builder_t *builder);
{ "cvt.f64.i32", OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I32), op_reg, op_reg },
{ "cvt.f64.i64", OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I64), op_reg, op_reg },
{ "cvt.f64.f32", OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_F32), op_reg, op_reg },
+
+ { "atomic.add.i32", OVM_TYPED_INSTR(OVMI_ADD | OVMI_ATOMIC, OVM_TYPE_I32), op_reg, op_reg, op_reg },
};
void parse_register(i32 *dest) {
branch_instr.a = -1;
branch_patch_t patch;
+ patch.kind = branch_patch_instr_a;
patch.branch_instr = bh_arr_length(builder->program->code);
patch.label_idx = label_idx;
branch_instr.b = POP_VALUE(builder);
branch_patch_t patch;
+ patch.kind = branch_patch_instr_a;
patch.branch_instr = bh_arr_length(builder->program->code);
patch.label_idx = label_idx;
ovm_program_add_instructions(builder->program, 1, &branch_instr);
}
+void ovm_code_builder_add_branch_table(ovm_code_builder_t *builder, i32 count, i32 *label_indicies, i32 default_label_idx) {
+ //
+ // Passing label indicies here is a little disingenuous, because that is not
+ // what the data will have to be. But since it is already the correct length
+ // I am using it as a subsitute.
+ int table_idx = ovm_program_register_static_ints(builder->program, count, label_indicies);
+ assert(table_idx > 0);
+
+ ovm_instr_t instrs[5] = {0};
+ int tmp_register = NEXT_VALUE(builder);
+ int index_register = POP_VALUE(builder);
+ PUSH_VALUE(builder, tmp_register);
+
+ instrs[0].full_instr = OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_I32);
+ instrs[0].r = tmp_register;
+ instrs[0].i = count;
+
+ instrs[1].full_instr = OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I32);
+ instrs[1].r = tmp_register;
+ instrs[1].a = index_register;
+ instrs[1].b = tmp_register;
+
+ instrs[2].full_instr = OVMI_BR_Z;
+ instrs[2].a = -1;
+ instrs[2].b = tmp_register;
+
+ instrs[3].full_instr = OVMI_IDX_ARR;
+ instrs[3].r = tmp_register;
+ instrs[3].a = table_idx;
+ instrs[3].b = index_register;
+
+ instrs[4].full_instr = OVMI_BRI;
+ instrs[4].a = tmp_register;
+
+ POP_VALUE(builder);
+
+ fori (i, 0, count) {
+ branch_patch_t patch;
+ patch.kind = branch_patch_static_idx;
+ patch.branch_instr = bh_arr_length(builder->program->code) + 4;
+ patch.label_idx = label_indicies[i];
+ patch.static_arr = table_idx;
+ patch.static_idx = i;
+ bh_arr_push(builder->branch_patches, patch);
+ }
+
+ branch_patch_t default_patch;
+ default_patch.kind = branch_patch_instr_a;
+ default_patch.branch_instr = bh_arr_length(builder->program->code) + 2;
+ default_patch.label_idx = default_label_idx;
+ bh_arr_push(builder->branch_patches, default_patch);
+
+ ovm_program_add_instructions(builder->program, 5, instrs);
+}
+
void ovm_code_builder_add_return(ovm_code_builder_t *builder) {
ovm_instr_t instr = {0};
instr.full_instr = OVMI_RETURN;
ovm_program_add_instructions(builder->program, 3, instrs);
}
+void ovm_code_builder_add_cmpxchg(ovm_code_builder_t *builder, u32 ovm_type, i32 offset) {
+ if (offset == 0) {
+ ovm_instr_t cmpxchg_instr = {0};
+ cmpxchg_instr.full_instr = OVM_TYPED_INSTR(OVMI_ATOMIC | OVMI_CMPXCHG, ovm_type);
+ cmpxchg_instr.b = POP_VALUE(builder);
+ cmpxchg_instr.a = POP_VALUE(builder);
+ cmpxchg_instr.r = POP_VALUE(builder);
+
+ ovm_program_add_instructions(builder->program, 1, &cmpxchg_instr);
+
+ PUSH_VALUE(builder, cmpxchg_instr.r);
+ return;
+ }
+
+ ovm_instr_t instrs[3] = {0};
+ int value_reg = POP_VALUE(builder);
+ int expected_reg = POP_VALUE(builder);
+ int addr_reg = POP_VALUE(builder);
+
+ // imm.i32 %n, offset
+ instrs[0].full_instr = OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_I32);
+ instrs[0].i = offset;
+ instrs[0].r = NEXT_VALUE(builder);
+
+ // add.i32 %n, %n, %i
+ instrs[1].full_instr = OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I32);
+ instrs[1].r = instrs[0].r;
+ instrs[1].a = instrs[0].r;
+ instrs[1].b = addr_reg;
+
+ // cmpxchg.x %m, %n
+ instrs[2].full_instr = OVM_TYPED_INSTR(OVMI_ATOMIC | OVMI_CMPXCHG, ovm_type);
+ instrs[2].r = instrs[1].r;
+ instrs[2].a = expected_reg;
+ instrs[2].b = value_reg;
+
+ ovm_program_add_instructions(builder->program, 3, instrs);
+
+ PUSH_VALUE(builder, instrs[2].r);
+}
+
void ovm_code_builder_add_memory_copy(ovm_code_builder_t *builder) {
ovm_instr_t instr = {0};
instr.full_instr = OVMI_COPY;
#include <sys/mman.h>
#include <x86intrin.h>
#include <math.h> // REMOVE THIS!!! only needed for sqrt
+#include <pthread.h>
//
// Store
return bh_arr_length(program->static_data) - 1;
}
+void ovm_program_modify_static_int(ovm_program_t *program, int arr, int idx, int new_value) {
+ if (arr >= bh_arr_length(program->static_data)) return;
+
+ ovm_static_integer_array_t array = program->static_data[arr];
+ if (idx >= array.len) return;
+
+ program->static_integers[array.start_idx + idx] = new_value;
+}
+
void ovm_program_register_func(ovm_program_t *program, char *name, i32 instr, i32 param_count, i32 value_number_count) {
ovm_func_t func;
func.kind = OVM_FUNC_INTERNAL;
static char *ovm_instr_name(i32 full_instr) {
-#define C(...) case __VA_ARGS__: return #__VA_ARGS__;
+#define C(...) \
+ case __VA_ARGS__: return #__VA_ARGS__; \
+ case __VA_ARGS__ | OVMI_ATOMIC: return "ATOMIC_" #__VA_ARGS__;
static char buf[64];
C(OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I64))
C(OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_F32))
+ C(OVM_TYPED_INSTR(OVMI_CMPXCHG, OVM_TYPE_I8))
+ C(OVM_TYPED_INSTR(OVMI_CMPXCHG, OVM_TYPE_I16))
+ C(OVM_TYPED_INSTR(OVMI_CMPXCHG, OVM_TYPE_I32))
+ C(OVM_TYPED_INSTR(OVMI_CMPXCHG, OVM_TYPE_I64))
+
default:
snprintf(buf, 64, "unknown (%d)", full_instr);
return buf;
#define VAL(loc) (state->numbered_values[(u32) (loc + state->value_number_offset)])
ovm_instr_t *code = program->code;
+ bool release_mutex_at_end = false;
while (state->pc < bh_arr_length(program->code)) {
//
// being executed. - brendanfh 2022/06/13
ovm_instr_t instr = code[state->pc++];
+ if (instr.full_instr & OVMI_ATOMIC) {
+ pthread_mutex_lock(&engine->atomic_mutex);
+ release_mutex_at_end = true;
+
+ instr.full_instr &= ~OVMI_ATOMIC;
+ }
+
switch (instr.full_instr) {
case OVMI_NOP: break;
#undef CVT
+
+#define CMPXCHG(otype, ctype) \
+ case OVM_TYPED_INSTR(OVMI_ATOMIC | OVMI_CMPXCHG, otype): \
+ if (VAL(instr.r).ctype == VAL(instr.a).ctype) { \
+ VAL(instr.r).ctype = VAL(instr.b).ctype ; \
+ } \
+ break;
+
+ CMPXCHG(OVM_TYPE_I8, i8)
+ CMPXCHG(OVM_TYPE_I16, i16)
+ CMPXCHG(OVM_TYPE_I32, i32)
+ CMPXCHG(OVM_TYPE_I64, i64)
+
+#undef CMPXCHG
+
default:
printf("ERROR:\n");
ovm_program_print_instructions(program, state->pc - 1, 1);
assert(("ILLEGAL INSTRUCTION", 0));
break;
}
+
+ if (release_mutex_at_end) {
+ pthread_mutex_unlock(&engine->atomic_mutex);
+ release_mutex_at_end = false;
+ }
}
}
ovm_program_t *program;
ovm_store_t *store;
+ int func_table_arr_idx;
+
// This will be set/reset for every code (function) entry.
ovm_code_builder_t builder;
};
fori (i, 0, (int) entry_count) {
ctx->module->elem_entries[i] = uleb128_to_uint(ctx->binary.data, &ctx->offset);
}
+
+ ctx->func_table_arr_idx = ovm_program_register_static_ints(ctx->program, entry_count, ctx->module->elem_entries);
}
static void parse_data_section(build_context *ctx) {
branch_patch_t patch = ctx->builder.branch_patches[i];
if (patch.label_idx != target.idx) continue;
- ctx->program->code[patch.branch_instr].a = target.instr - patch.branch_instr - 1;
+ int br_delta = target.instr - patch.branch_instr - 1;
+
+ switch (patch.kind) {
+ case branch_patch_instr_a:
+ ctx->program->code[patch.branch_instr].a = br_delta;
+ break;
+
+ case branch_patch_static_idx:
+ ovm_program_modify_static_int(ctx->program, patch.static_arr, patch.static_idx, br_delta);
+ break;
+ }
+
bh_arr_fastdelete(ctx->builder.branch_patches, i);
i--;
}
}
}
+static void parse_fe_instruction(build_context *ctx) {
+ int instr_num = uleb128_to_uint(ctx->binary.data, &ctx->offset);
+
+ switch (instr_num) {
+
+#define LOAD_CASE(num, type) \
+ case num : { \
+ int alignment = uleb128_to_uint(ctx->binary.data, &ctx->offset); \
+ int offset = uleb128_to_uint(ctx->binary.data, &ctx->offset); \
+ ovm_code_builder_add_load(&ctx->builder, OVMI_ATOMIC | type, offset); \
+ break; \
+ }
+
+ LOAD_CASE(0x10, OVM_TYPE_I32)
+ LOAD_CASE(0x11, OVM_TYPE_I64)
+ LOAD_CASE(0x12, OVM_TYPE_I8)
+ LOAD_CASE(0x13, OVM_TYPE_I16)
+ LOAD_CASE(0x14, OVM_TYPE_I8)
+ LOAD_CASE(0x15, OVM_TYPE_I16)
+ LOAD_CASE(0x16, OVM_TYPE_I32)
+
+#undef LOAD_CASE
+
+#define STORE_CASE(num, type) \
+ case num : { \
+ int alignment = uleb128_to_uint(ctx->binary.data, &ctx->offset); \
+ int offset = uleb128_to_uint(ctx->binary.data, &ctx->offset); \
+ ovm_code_builder_add_store(&ctx->builder, OVMI_ATOMIC | type, offset); \
+ break; \
+ }
+
+ STORE_CASE(0x17, OVM_TYPE_I32)
+ STORE_CASE(0x18, OVM_TYPE_I64)
+ STORE_CASE(0x19, OVM_TYPE_I8)
+ STORE_CASE(0x1A, OVM_TYPE_I16)
+ STORE_CASE(0x1B, OVM_TYPE_I8)
+ STORE_CASE(0x1C, OVM_TYPE_I16)
+ STORE_CASE(0x1D, OVM_TYPE_I32)
+
+#undef STORE_CASE
+
+#define CMPXCHG_CASE(num, type) \
+ case num : { \
+ int alignment = uleb128_to_uint(ctx->binary.data, &ctx->offset); \
+ int offset = uleb128_to_uint(ctx->binary.data, &ctx->offset); \
+ ovm_code_builder_add_cmpxchg(&ctx->builder, type, offset); \
+ break; \
+ }
+
+ CMPXCHG_CASE(0x48, OVM_TYPE_I32)
+ CMPXCHG_CASE(0x49, OVM_TYPE_I64)
+ CMPXCHG_CASE(0x4A, OVM_TYPE_I8)
+ CMPXCHG_CASE(0x4B, OVM_TYPE_I16)
+ CMPXCHG_CASE(0x4C, OVM_TYPE_I8)
+ CMPXCHG_CASE(0x4D, OVM_TYPE_I16)
+ CMPXCHG_CASE(0x4E, OVM_TYPE_I32)
+
+#undef CMPXCHG_CASE
+
+ default: assert(("UNHANDLED ATOMIC INSTRUCTION... SORRY :/", 0));
+ }
+}
+
static void parse_instruction(build_context *ctx) {
unsigned char instr_byte = CONSUME_BYTE(ctx);
switch (instr_byte) {
// Therefore, this "peeks" at what the next label index will be, so it can
// know what label to track for where to jump. It feels like there should be
// a better way to do this.
- ovm_code_builder_add_branch(&ctx->builder, ctx->builder.next_label_idx);
+ ovm_code_builder_add_branch(&ctx->builder, bh_arr_last(ctx->builder.label_stack).idx);
pop_label_target(ctx);
push_label_target(ctx, label_kind_block);
break;
break;
}
- case 0x0E: assert(0); {
- // TODO: Branch tables
+ case 0x0E: {
+ // Branch tables are the most complicated thing ever :/
+
+ int entry_count = uleb128_to_uint(ctx->binary.data, &ctx->offset);
+ int *entries = bh_alloc_array(bh_heap_allocator(), int, entry_count);
+ fori (i, 0, entry_count) {
+ entries[i] = uleb128_to_uint(ctx->binary.data, &ctx->offset);
+ }
+
+ int default_entry = uleb128_to_uint(ctx->binary.data, &ctx->offset);
+
+ ovm_code_builder_add_branch_table(&ctx->builder, entry_count, entries, default_entry);
break;
}
int memory_size = 65536;
ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &memory_size);
+ break;
}
case 0x40: {
case 0x44: {
double value = * (f64 *) &ctx->binary.data[ctx->offset];
- ctx->offset += 4;
+ ctx->offset += 8;
ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_F64, &value);
break;
}
case 0xC4: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_I64)); break;
case 0xFC: parse_fc_instruction(ctx); break;
+ case 0xFE: parse_fe_instruction(ctx); break;
default: assert(("UNHANDLED INSTRUCTION", 0));
}
// Set up a lot of stuff...
- i32 param_count = wasm_module_index_functype(ctx->module, i)->type.func.params.size;
+ i32 param_count = ctx->module->functypes.data[i]->type.func.params.size;
ctx->builder = ovm_code_builder_new(ctx->program, param_count, total_locals);
- ctx->builder.func_table_arr_idx = 0; // This might not be right
+ ctx->builder.func_table_arr_idx = ctx->func_table_arr_idx;
push_label_target(ctx, label_kind_func);
parse_expression(ctx);
bh_printf("exports: %b %d\n", export_name->data, export_name->size, wasm_externtype_kind(wasm_exporttype_type(exports.data[i])));
}
- ovm_program_print_instructions(module->program, 0, 150);
+ ovm_program_print_instructions(module->program, 0, bh_arr_length(module->program->code));
}
calli %4
reg.set stack_ptr, %10
+ atomic.add.i32 %0, %0, %0
return
.func sin_test 0
foo();
- if_test(10);
+ switch_test(10);
while_test(10);
}
-if_test :: (x: i32) {
+/*if_test :: (x: i32) {
if !(x > 10) {
f(10, 20);
} elseif x > 5 {
} else {
y := x * 2;
}
+}*/
+
+switch_test :: (x: i32) {
+ switch x {
+ case 10 {
+ y := 0;
+ }
+
+ case 20 {
+ z := x * 2;
+ }
+
+ case #default {
+ w := x + 2;
+ }
+ }
}
while_test :: (x: i32) {