From: Brendan Hansen Date: Mon, 5 Sep 2022 16:45:41 +0000 (-0500) Subject: added interpreter to this repo X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=95799e2976dcc733517172a0092f502612c68a5f;p=onyx.git added interpreter to this repo --- diff --git a/build.sh b/build.sh index 87c89484..9a8a169f 100755 --- a/build.sh +++ b/build.sh @@ -6,18 +6,6 @@ echo "Installing core libs" sudo mkdir -p "$CORE_DIR" sudo cp -r ./core/ "$CORE_DIR" -if [ ! -f "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" ] || true; then - echo "Copying lib$RUNTIME_LIBRARY to $CORE_DIR/lib (first install)" - - sudo mkdir -p "$CORE_DIR/lib" - sudo mkdir -p "$CORE_DIR/include" - - sudo cp "$WASMER_LIBRARY_DIR/lib$RUNTIME_LIBRARY.so" "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" - - sudo cp "shared/include/onyx_library.h" "$CORE_DIR/include/onyx_library.h" - sudo cp "$WASMER_INCLUDE_DIR/wasm.h" "$CORE_DIR/include/wasm.h" -fi - # This is a development feature to allow for quickly reinstalling core libraries # without have to recompile the entire compiler [ "$1" = "core" ] && exit 0 @@ -34,5 +22,25 @@ cd runtime ./build.sh $1 cd .. +if [ "$RUNTIME_LIBRARY" = "ovmwasm" ]; then + cd interpreter + ./build.sh $1 + cd .. +fi + +if [ ! -f "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" ] || true; then + echo "Copying lib$RUNTIME_LIBRARY to $CORE_DIR/lib (first install)" + + sudo mkdir -p "$CORE_DIR/lib" + sudo mkdir -p "$CORE_DIR/include" + + sudo cp "$WASMER_LIBRARY_DIR/lib$RUNTIME_LIBRARY.so" "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" + + sudo cp "shared/include/onyx_library.h" "$CORE_DIR/include/onyx_library.h" + sudo cp "$WASMER_INCLUDE_DIR/wasm.h" "$CORE_DIR/include/wasm.h" +fi + + + # Otherwise the prompt ends on the same line printf "\n" diff --git a/interpreter/build.sh b/interpreter/build.sh new file mode 100755 index 00000000..adbb53ce --- /dev/null +++ b/interpreter/build.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. ../settings.sh + +# FLAGS="-g3" +# FLAGS="-g3 -DOVM_VERBOSE=1" +FLAGS="-Ofast" +LIBS="-pthread" +TARGET="../shared/lib/linux_$(uname -m)/lib/libovmwasm.so" +C_FILES="src/wasm.c src/vm/*.c src/wasm/*.c src/debug/*.c" +INCLUDES="-I../shared/include -Iinclude" + +echo "Compiling libovmwasm.so" +$CC $FLAGS $INCLUDES -shared -fPIC -o $TARGET $C_FILES $LIBS $WARNINGS + diff --git a/interpreter/include/assembler.h b/interpreter/include/assembler.h new file mode 100644 index 00000000..7ea656eb --- /dev/null +++ b/interpreter/include/assembler.h @@ -0,0 +1,36 @@ +#ifndef _ASSEMBLER_H +#define _ASSEMBLER_H + +typedef enum token_type_t token_type_t; +typedef struct token_t token_t; + +enum token_type_t { + token_none, + token_newline, + + token_integer, + token_integer_long, + token_float, + token_float_double, + + token_command, + token_symbol, + token_register, + token_label, + token_comma, +}; + +struct token_t { + token_type_t type; + char *text; + int line; +}; + +token_t asm_lexer_next_token(); +token_t asm_lexer_peek_token(); +void asm_lexer_input(char *data, int size); + +extern char *token_names[]; + +#endif + diff --git a/interpreter/include/ovm_debug.h b/interpreter/include/ovm_debug.h new file mode 100644 index 00000000..ac112c2e --- /dev/null +++ b/interpreter/include/ovm_debug.h @@ -0,0 +1,304 @@ +#ifndef _OVM_DEBUG_H +#define _OVM_DEBUG_H + +#include "bh.h" +#include +#include + +typedef struct debug_loc_info_t { + u32 file_id; + u32 line; + u32 symbol_scope; +} debug_loc_info_t; + +typedef struct debug_func_info_t { + u32 func_id; + u32 file_id; + u32 line; + char *name; + b32 internal; + u32 stack_ptr_idx; + + u32 debug_op_offset; +} debug_func_info_t; + +typedef struct debug_file_info_t { + char *name; + u32 file_id; + u32 line_count; + i32 line_buffer_offset; +} debug_file_info_t; + +typedef enum debug_sym_loc_kind_t { + debug_sym_loc_register = 1, + debug_sym_loc_stack = 2, + debug_sym_loc_global = 3 +} debug_sym_loc_kind_t; + +typedef struct debug_sym_info_t { + char *name; + u32 sym_id; + debug_sym_loc_kind_t loc_kind; + u32 loc; + u32 type; +} debug_sym_info_t; + +typedef struct debug_sym_scope_t { + bh_arr(u32) symbols; + i32 parent; // -1 for root +} debug_sym_scope_t; + +typedef enum debug_type_kind_t { + debug_type_kind_primitive = 1, + debug_type_kind_modifier = 2, + debug_type_kind_structure = 3, + debug_type_kind_array = 4, + debug_type_kind_alias = 5, + debug_type_kind_function = 6, +} debug_type_kind_t; + +typedef enum debug_type_primitive_kind_t { + debug_type_primitive_kind_void = 0, + debug_type_primitive_kind_signed_integer = 1, + debug_type_primitive_kind_unsigned_integer = 2, + debug_type_primitive_kind_float = 3, + debug_type_primitive_kind_boolean = 4, + debug_type_primitive_kind_character = 5, + debug_type_primitive_kind_vector = 6, +} debug_primitive_kind_t; + +typedef struct debug_type_primitive_t { + debug_primitive_kind_t primitive_kind; +} debug_type_primitive_t; + +typedef enum debug_type_modifier_kind_t { + debug_type_modifier_kind_pointer = 1, + debug_type_modifier_kind_const = 2, + debug_type_modifier_kind_restrict = 3, +} debug_type_modifier_kind_t; + +typedef struct debug_type_modifier_t { + debug_type_modifier_kind_t modifier_kind; + u32 modified_type; +} debug_type_modifier_t; + +typedef struct debug_type_structure_member_t { + u32 offset; + u32 type; + char *name; +} debug_type_structure_member_t; + +typedef struct debug_type_structure_t { + u32 member_count; + debug_type_structure_member_t *members; +} debug_type_structure_t; + +typedef struct debug_type_array_t { + u32 count; + u32 type; +} debug_type_array_t; + +typedef enum debug_type_alias_kind_t { + debug_type_alias_kind_transparent = 1, + debug_type_alias_kind_distinct = 2 +} debug_type_alias_kind_t; + +typedef struct debug_type_alias_t { + debug_type_alias_kind_t alias_kind; + u32 aliased_type; +} debug_type_alias_t; + +typedef struct debug_type_function_t { + u32 param_count; + u32 *param_types; + u32 return_type; +} debug_type_function_t; + +typedef struct debug_type_info_t { + u32 id; + char *name; + u32 size; + debug_type_kind_t kind; + + union { + debug_type_primitive_t primitive; + debug_type_modifier_t modifier; + debug_type_structure_t structure; + debug_type_array_t array; + debug_type_alias_t alias; + debug_type_function_t function; + }; +} debug_type_info_t; + +typedef struct debug_info_t { + bh_allocator alloc; + + bool has_debug_info; + + // func index -> func info + bh_arr(debug_func_info_t) funcs; + + // reducer output -> line info + bh_arr(debug_loc_info_t) line_info; + + // instruction index -> reducer output + bh_arr(u32) instruction_reducer; + + // line index -> instruction index + bh_arr(u32) line_to_instruction; + + // file_id -> file info + bh_arr(debug_file_info_t) files; + + // sym_id -> sym info + bh_arr(debug_sym_info_t) symbols; + + // scope id -> symbol scope + bh_arr(debug_sym_scope_t) symbol_scopes; + + // type id -> type info + bh_arr(debug_type_info_t) types; +} debug_info_t; + +void debug_info_init(debug_info_t *); +void debug_info_free(debug_info_t *); +void debug_info_import_file_info(debug_info_t *, u8 *data, u32 len); +void debug_info_import_func_info(debug_info_t *, u8 *data, u32 len); +void debug_info_import_sym_info(debug_info_t *, u8 *data, u32 len); +void debug_info_import_type_info(debug_info_t *, u8 *data, u32 len); + +bool debug_info_lookup_location(debug_info_t *info, u32 instruction, debug_loc_info_t *out); +bool debug_info_lookup_file(debug_info_t *info, u32 file_id, debug_file_info_t *out); +bool debug_info_lookup_file_by_name(debug_info_t *info, char *name, debug_file_info_t *out); +bool debug_info_lookup_func(debug_info_t *info, u32 func_id, debug_func_info_t *out); + +// +// This builder is used in conjunction with code builder to output +// debug information for each instruction that is generated in OVM. +// +typedef struct debug_info_builder_t { + debug_info_t *info; + + u8 *data; + u32 reader_offset; + + u32 current_file_id; + u32 current_line; + u32 next_file_line_offset; + + i32 current_scope; + + u32 remaining_reps; + + b32 locked : 1; +} debug_info_builder_t; + +void debug_info_builder_init(debug_info_builder_t *, debug_info_t *); +void debug_info_builder_prepare(debug_info_builder_t *, u8 *); +void debug_info_builder_emit_location(debug_info_builder_t *); +void debug_info_builder_step(debug_info_builder_t *); +void debug_info_builder_begin_func(debug_info_builder_t *, i32 func_idx); +void debug_info_builder_end_func(debug_info_builder_t *); + + +typedef enum debug_exec_state_t { + debug_state_starting, + debug_state_ready, + debug_state_running, + debug_state_paused, + + debug_state_pausing, + debug_state_hit_breakpoint, +} debug_exec_state_t; + +typedef enum debug_pause_reason_t { + debug_pause_entry = 1, + debug_pause_step = 2, +} debug_pause_reason_t; + +typedef struct debug_breakpoint_t { + u32 id; + u32 instr; + u32 file_id; + u32 line; +} debug_breakpoint_t; + +typedef struct debug_thread_state_t { + u32 id; + + debug_exec_state_t state; + struct ovm_state_t *ovm_state; + + i32 run_count; + sem_t wait_semaphore; + + bool pause_at_next_line; + i32 pause_within; + i32 extra_frames_since_last_pause; + debug_pause_reason_t pause_reason; + + bh_arr(debug_breakpoint_t) breakpoints; + u32 last_breakpoint_hit; + + u32 state_change_write_fd; +} debug_thread_state_t; + +// +// This represents known state of the debugger. There should only +// be one state per running program, as it is tied to the ovm_engine_t. +// +typedef struct debug_state_t { + bh_allocator alloc; + + bh_arena tmp_arena; + bh_allocator tmp_alloc; + + debug_info_t *info; + struct ovm_engine_t *ovm_engine; + + bh_arr(debug_thread_state_t *) threads; + u32 next_thread_id; + + u32 next_breakpoint_id; + + pthread_t debug_thread; + bool debug_thread_running; + + u32 listen_socket_fd; + u32 client_fd; + + bh_buffer send_buffer; + + u32 state_change_pipes[2]; +} debug_state_t; + +void debug_host_init(debug_state_t *debug, struct ovm_engine_t *ovm_engine); +void debug_host_start(debug_state_t *debug); +void debug_host_stop(debug_state_t *debug); +u32 debug_host_register_thread(debug_state_t *debug, struct ovm_state_t *ovm_state); +debug_thread_state_t *debug_host_lookup_thread(debug_state_t *debug, u32 id); + + + +typedef struct debug_runtime_value_builder_t { + debug_state_t *state; + debug_info_t *info; + + bh_buffer output; + struct ovm_state_t *ovm_state; + struct ovm_stack_frame_t *ovm_frame; + + debug_sym_scope_t sym_scope; + debug_func_info_t func_info; + debug_file_info_t file_info; + debug_loc_info_t loc_info; + debug_sym_info_t sym_info; +} debug_runtime_value_builder_t; + +void debug_runtime_value_build_init(debug_runtime_value_builder_t *builder, bh_allocator alloc); +void debug_runtime_value_build_free(debug_runtime_value_builder_t *builder); +void debug_runtime_value_build_string(debug_runtime_value_builder_t *builder); + +void *__debug_thread_entry(void *); + +#endif diff --git a/interpreter/include/ovm_wasm.h b/interpreter/include/ovm_wasm.h new file mode 100644 index 00000000..71bb19c1 --- /dev/null +++ b/interpreter/include/ovm_wasm.h @@ -0,0 +1,260 @@ +#ifndef _OVM_WASM_H +#define _OVM_WASM_H + +#include "wasm.h" +#include "vm.h" +#include "ovm_debug.h" + +// Core Utils + +struct wasm_config_t { + bool debug_enabled; +}; + +void wasm_config_enable_debug(wasm_config_t *config, bool enabled); + +struct wasm_engine_t { + wasm_config_t *config; + + ovm_store_t *store; + ovm_engine_t *engine; +}; + +struct wasm_store_t { + wasm_engine_t *engine; + wasm_instance_t *instance; +}; + + +// Types + +struct wasm_valtype_t { + wasm_valkind_t kind; +}; + +struct wasm_functype_inner_t { + wasm_valtype_vec_t params; + wasm_valtype_vec_t results; +}; + +struct wasm_globaltype_inner_t { + wasm_valtype_t *content; + wasm_mutability_t mutability; + + wasm_val_t initial_value; +}; + +struct wasm_tabletype_inner_t { + wasm_valtype_t *element; + wasm_limits_t limits; + + i32 static_arr; +}; + +struct wasm_memorytype_inner_t { + wasm_limits_t limits; +}; + +struct wasm_externtype_t { + wasm_externkind_t kind; + union { + struct wasm_functype_inner_t func; + struct wasm_globaltype_inner_t global; + struct wasm_tabletype_inner_t table; + struct wasm_memorytype_inner_t memory; + }; +}; + +struct wasm_functype_t { wasm_externtype_t type; }; +struct wasm_globaltype_t { wasm_externtype_t type; }; +struct wasm_tabletype_t { wasm_externtype_t type; }; +struct wasm_memorytype_t { wasm_externtype_t type; }; + +struct wasm_importtype_t { + wasm_name_t module_name; + wasm_name_t import_name; + wasm_externtype_t *type; + + // + // This is only used by imported functions + // to specify which slot the function binding + // should be placed. When making a functype by + // hand, this proably will never be used. + int external_func_idx; +}; + +struct wasm_exporttype_t { + wasm_name_t name; + wasm_externtype_t *type; + int index; +}; + + +// Runtime Objects + +struct wasm_ref_t { +}; + +struct wasm_frame_t { + wasm_instance_t *instance; + int func_idx; + size_t func_offset; + size_t module_offset; +}; + +struct wasm_trap_t { + wasm_message_t msg; + wasm_frame_vec_t frames; + + wasm_store_t *store; +}; + +struct wasm_foreign_t { +}; + +struct wasm_data_t { + void *data; + unsigned int length; + unsigned int offset; + bool passive; +}; + +struct wasm_custom_section_t { + unsigned int size; + char *data; +}; + +struct wasm_module_t { + wasm_store_t *store; + + wasm_functype_vec_t type_section; + + wasm_functype_vec_t functypes; + wasm_globaltype_vec_t globaltypes; + wasm_tabletype_vec_t tabletypes; + wasm_memorytype_vec_t memorytypes; + wasm_importtype_vec_t imports; + wasm_exporttype_vec_t exports; + + int start_func_idx; + + unsigned int elem_count; + unsigned int *elem_entries; // Array of function indicies + + bool data_count_present; + unsigned int data_count; + struct wasm_data_t *data_entries; + + ovm_program_t *program; + bool valid; + + int memory_init_idx; + int memory_init_external_idx; + + Table(struct wasm_custom_section_t) custom_sections; + + debug_info_t debug_info; +}; + +struct wasm_func_inner_t { + bool env_present; + void *env; + void (*func_ptr)(); + void (*finalizer)(void *); + + const wasm_functype_t *type; +}; + +struct wasm_global_inner_t { + int register_index; + ovm_state_t *state; + ovm_engine_t *engine; + + wasm_val_t initial_value; + + const wasm_globaltype_t *type; +}; + +struct wasm_table_inner_t { + ovm_program_t *program; + ovm_engine_t *engine; + + int static_arr; + + const wasm_tabletype_t *type; +}; + +struct wasm_memory_inner_t { + ovm_engine_t* engine; + + const wasm_memorytype_t *type; +}; + +struct wasm_extern_t { + const wasm_externtype_t *type; + union { + struct wasm_func_inner_t func; + struct wasm_global_inner_t global; + struct wasm_table_inner_t table; + struct wasm_memory_inner_t memory; + }; +}; + +struct wasm_func_t { wasm_extern_t inner; }; +struct wasm_global_t { wasm_extern_t inner; }; +struct wasm_table_t { wasm_extern_t inner; }; +struct wasm_memory_t { wasm_extern_t inner; }; + +struct wasm_instance_t { + const wasm_module_t *module; + wasm_store_t *store; + + bh_arr(wasm_func_t *) funcs; + bh_arr(wasm_memory_t *) memories; + bh_arr(wasm_table_t *) tables; + bh_arr(wasm_global_t *) globals; + + wasm_extern_vec_t exports; + + ovm_state_t *state; +}; + + +bool wasm_functype_equals(wasm_functype_t *a, wasm_functype_t *b); + +wasm_functype_t *wasm_module_index_functype(wasm_module_t *module, int index); +wasm_tabletype_t *wasm_module_index_tabletype(wasm_module_t *module, int index); +wasm_globaltype_t *wasm_module_index_globaltype(wasm_module_t *module, int index); +wasm_memorytype_t *wasm_module_index_memorytype(wasm_module_t *module, int index); + + + +#define WASM_DECLARE_VEC_IMPL(type, ptr_or_none) \ + void wasm_##type##_vec_new_empty(wasm_##type##_vec_t *out) { \ + out->size = 0; \ + out->data = NULL; \ + } \ + \ + void wasm_##type##_vec_new_uninitialized(wasm_##type##_vec_t *out, size_t size) { \ + out->data = malloc(sizeof(wasm_##type##_t ptr_or_none) * size); \ + out->size = size; \ + } \ + \ + void wasm_##type##_vec_new(wasm_##type##_vec_t *out, size_t size, wasm_##type##_t ptr_or_none const data[]) { \ + out->data = malloc(sizeof(wasm_##type##_t ptr_or_none) * size); \ + out->size = size; \ + \ + fori (i, 0, (i32) size) { \ + out->data[i] = data[i]; \ + } \ + } \ + \ + void wasm_##type##_vec_copy(wasm_##type##_vec_t *out, const wasm_##type##_vec_t *in) { \ + wasm_##type##_vec_new(out, in->size, in->data); \ + } \ + \ + void wasm_##type##_vec_delete(wasm_##type##_vec_t *vec) { \ + if (vec->data) free(vec->data); \ + } + +#endif diff --git a/interpreter/include/vm.h b/interpreter/include/vm.h new file mode 100644 index 00000000..44868f87 --- /dev/null +++ b/interpreter/include/vm.h @@ -0,0 +1,345 @@ +#ifndef _ONYX_VM_H +#define _ONYX_VM_H + +#include "bh.h" +#include "ovm_debug.h" +#include +#include + +typedef u8 ovm_valtype_t; +typedef i32 ovm_valnum_t; +typedef u32 ovm_instr_kind_t; + +typedef struct ovm_store_t ovm_store_t; +typedef struct ovm_engine_t ovm_engine_t; +typedef struct ovm_program_t ovm_program_t; +typedef struct ovm_state_t ovm_state_t; +typedef struct ovm_stack_frame_t ovm_stack_frame_t; +typedef enum ovm_func_kind_t ovm_func_kind_t; +typedef struct ovm_func_t ovm_func_t; +typedef struct ovm_external_func_t ovm_external_func_t; +typedef struct ovm_linkable_func_t ovm_linkable_func_t; +typedef struct ovm_value_t ovm_value_t; +typedef struct ovm_instr_t ovm_instr_t; +typedef struct ovm_static_data_t ovm_static_data_t; +typedef struct ovm_static_integer_array_t ovm_static_integer_array_t; + + +// +// Contains storage. +struct ovm_store_t { + bh_allocator heap_allocator; + bh_atomic_arena arena; + bh_allocator arena_allocator; +}; + +ovm_store_t *ovm_store_new(); +void ovm_store_delete(ovm_store_t *store); + + +struct ovm_static_data_t { + i64 dest_addr; + void *data; + i64 length; +}; + +struct ovm_static_integer_array_t { + i32 start_idx; + i32 len; +}; + +// +// Represents a program that is runnable by the +// VM. It can be constructed incrementally as needed. +// +struct ovm_program_t { + bh_arr(ovm_instr_t) code; + bh_arr(ovm_func_t) funcs; + + // + // TODO: Document these, and rename them. + bh_arr(i32) static_integers; + bh_arr(ovm_static_integer_array_t) static_data; + + i32 register_count; + ovm_store_t *store; +}; + +ovm_program_t *ovm_program_new(ovm_store_t *store); +void ovm_program_delete(ovm_program_t *program); +void ovm_program_add_instructions(ovm_program_t *program, i32 instr_count, ovm_instr_t *instrs); +void ovm_program_print_instructions(ovm_program_t *program, i32 start_instr, i32 instr_count); +void ovm_raw_print_instructions(i32 instr_count, ovm_instr_t *instrs); + +int ovm_program_register_static_ints(ovm_program_t *program, int len, int *data); +int ovm_program_register_func(ovm_program_t *program, char *name, i32 instr, i32 param_count, i32 value_number_count); +int 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 +// data needed by the VM. This is for more "global" data. +// If multiple threads are used, only one engine is needed. +// +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; + + debug_state_t *debug; +}; + +ovm_engine_t *ovm_engine_new(ovm_store_t *store); +void ovm_engine_delete(ovm_engine_t *engine); +void ovm_engine_memory_copy(ovm_engine_t *engine, i64 target, void *data, i64 size); + +bool ovm_program_load_from_file(ovm_program_t *program, ovm_engine_t *engine, char *filename); + +// +// Represents ephemeral state / execution context. +// If multiple threads are used, multiple states are needed. +// +struct ovm_state_t { + ovm_store_t *store; + + i32 pc; + i32 value_number_offset; + + bh_arr(ovm_value_t) numbered_values; + bh_arr(ovm_value_t) params; + bh_arr(ovm_stack_frame_t) stack_frames; + bh_arr(ovm_value_t) registers; + + // + // Originally, these were stored on the ovm_program that + // this state corresponds with. However, that does not line + // up with the specifications needed by WASM. In theory, different + // running instances of the program *could* have different + // native functions linked. + bh_arr(ovm_external_func_t) external_funcs; + + debug_thread_state_t *debug; +}; + +ovm_state_t *ovm_state_new(ovm_engine_t *engine, ovm_program_t *program); +void ovm_state_delete(ovm_state_t *state); +void ovm_state_link_external_funcs(ovm_program_t *program, ovm_state_t *state, ovm_linkable_func_t *funcs); +void ovm_state_register_external_func(ovm_state_t *state, i32 idx, void (*func)(void *, ovm_value_t *, ovm_value_t *), void *data); +ovm_value_t ovm_state_register_get(ovm_state_t *state, i32 idx); +void ovm_state_register_set(ovm_state_t *state, i32 idx, ovm_value_t val); + +// +// +struct ovm_stack_frame_t { + ovm_func_t *func; + i32 value_number_count; + i32 value_number_base; + + i32 return_address; + i32 return_number_value; +}; + + +// +// Represents a function that can be executed on the VM. +// +enum ovm_func_kind_t { + OVM_FUNC_INTERNAL, + OVM_FUNC_EXTERNAL +}; + +struct ovm_func_t { + // + // This ID is used as the index into the `funcs` member on ovm_program_t + // to reference this function. It is only here for debugging and posterity. + i32 id; + ovm_func_kind_t kind; + char *name; + i32 param_count; + i32 value_number_count; + + union { + i32 start_instr; + i32 external_func_idx; + }; +}; + +struct ovm_external_func_t { + void (*native_func)(void *userdata, ovm_value_t* params, ovm_value_t* result); + void *userdata; +}; + +struct ovm_linkable_func_t { + char *name; + i32 param_count; + ovm_external_func_t func; +}; + +ovm_func_t *ovm_func_new(); +ovm_instr_t *ovm_func_add_instruction(ovm_func_t *func, ovm_instr_kind_t instr, ovm_valtype_t type); +void ovm_func_delete(ovm_func_t *func); + +ovm_value_t ovm_func_call(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t *program, i32 func_idx, + i32 param_count, ovm_value_t *params); +ovm_value_t ovm_run_code(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t *program); + +// +// Instruction encoding +// +// This engine uses a simple Three Address Code (3AC) instruction representation +// with an "infinite" value number (register) count. +// + +#define OVM_TYPE_NONE 0x00 +#define OVM_TYPE_I8 0x01 +#define OVM_TYPE_I16 0x02 +#define OVM_TYPE_I32 0x03 +#define OVM_TYPE_I64 0x04 +#define OVM_TYPE_F32 0x05 +#define OVM_TYPE_F64 0x06 +#define OVM_TYPE_V128 0x07 + +struct ovm_value_t { + ovm_valtype_t type; + union { + i8 i8; + i16 i16; + i32 i32; + i64 i64; + u8 u8; + u16 u16; + u32 u32; + u64 u64; + f32 f32; + f64 f64; + }; +}; + +struct ovm_instr_t { + u32 full_instr; + + // Destination value number. + ovm_valnum_t r; + + union { + // Input value numbers. + struct { + ovm_valnum_t a, b; + }; + + // Immediates in different types. + i32 i; + f32 f; + i64 l; + f64 d; + }; +}; + +#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_SUB 0x02 // %r = %a - %b +#define OVMI_MUL 0x03 // %r = %a * %b +#define OVMI_DIV 0x04 // %r = %a / %b +#define OVMI_DIV_S 0x05 // %r = %a / %b +#define OVMI_REM 0x06 // %r = %a % %b +#define OVMI_REM_S 0x07 // %r = %a % %b + +#define OVMI_AND 0x08 // %r = %a & %b +#define OVMI_OR 0x09 // %r = %a | %b +#define OVMI_XOR 0x0A // %r = %a ^ %b +#define OVMI_SHL 0x0B // %r = %a << %b +#define OVMI_SHR 0x0C // %r = %a >> %b +#define OVMI_SAR 0x0D // %r = %a >>> %b + +#define OVMI_IMM 0x10 // %r = i/l/f/d +#define OVMI_MOV 0x11 // %r = %a +#define OVMI_LOAD 0x12 // %r = mem[%a + %b] +#define OVMI_STORE 0x13 // mem[%r + %b] = %a +#define OVMI_COPY 0x14 // memcpy(%r, %a, %b) +#define OVMI_FILL 0x15 // memset(%r, %a, %b) +#define OVMI_REG_GET 0x16 // %r = #a +#define OVMI_REG_SET 0x17 // #r = %a +#define OVMI_IDX_ARR 0x18 // %r = (a)[%b] + +#define OVMI_LT 0x20 // %r = %a < %b +#define OVMI_LT_S 0x21 // %r = %a < %b +#define OVMI_LE 0x22 // %r = %a <= %b +#define OVMI_LE_S 0x23 // %r = %a <= %b +#define OVMI_EQ 0x24 // %r = %a == %b +#define OVMI_GE 0x25 // %r = %a >= %b +#define OVMI_GE_S 0x26 // %r = %a >= %b +#define OVMI_GT 0x27 // %r = %a > %b +#define OVMI_GT_S 0x28 // %r = %a > %b +#define OVMI_NE 0x29 // %r = %a != %b + +#define OVMI_PARAM 0x30 // push %a +#define OVMI_RETURN 0x31 // return %a +#define OVMI_CALL 0x32 // %r = a(...) +#define OVMI_CALLI 0x33 // %r = %a(...) + +#define OVMI_BR 0x40 // br pc + a // Relative branching +#define OVMI_BR_Z 0x41 // br pc + a if %b == 0 +#define OVMI_BR_NZ 0x42 // br pc + a if %b != 0 +#define OVMI_BRI 0x43 // br pc + %a // Relative branching +#define OVMI_BRI_Z 0x44 // br pc + %a if %b == 0 +#define OVMI_BRI_NZ 0x45 // br pc + %a if %b != 0 + +#define OVMI_CLZ 0x50 // %r = clz(%a) +#define OVMI_CTZ 0x51 // %r = ctr(%a) +#define OVMI_POPCNT 0x52 // %r = popcnt(%a) +#define OVMI_ROTL 0x53 // %r = rotl(%a, %b) +#define OVMI_ROTR 0x54 // %r = rotr(%a, %b) + +// These instructions are only implemented for floats. +#define OVMI_ABS 0x55 // %r = |%a| +#define OVMI_NEG 0x56 // %r = -%a +#define OVMI_CEIL 0x57 // %r = ceil(%a) +#define OVMI_FLOOR 0x58 // %r = floor(%a) +#define OVMI_TRUNC 0x59 // %r = trunc(%a) +#define OVMI_NEAREST 0x5A // %r = nearest(%a) +#define OVMI_SQRT 0x5B // %r = sqrt(%a) +#define OVMI_MIN 0x5C // %r = min(%a, %b) +#define OVMI_MAX 0x5D // %r = max(%a, %b) +#define OVMI_COPYSIGN 0x5E // %r = copysign(%a, %b) + +// For conversion operations, the "type" of the instruction is +// destination type, the type in the name is the source type. +// +// There are a couple of cast operations that are not available, +// such as unsigned conversion from 32-bit integers to floats. +#define OVMI_CVT_I8 0x60 // %r = (t) %a +#define OVMI_CVT_I8_S 0x61 // %r = (t) %a (sign aware) +#define OVMI_CVT_I16 0x62 // %r = (t) %a +#define OVMI_CVT_I16_S 0x63 // %r = (t) %a (sign aware) +#define OVMI_CVT_I32 0x64 // %r = (t) %a +#define OVMI_CVT_I32_S 0x65 // %r = (t) %a (sign aware) +#define OVMI_CVT_I64 0x66 // %r = (t) %a +#define OVMI_CVT_I64_S 0x67 // %r = (t) %a (sign aware) +#define OVMI_CVT_F32 0x68 // %r = (t) %a +#define OVMI_CVT_F32_S 0x69 // %r = (t) %a (sign aware) +#define OVMI_CVT_F64 0x6A // %r = (t) %a +#define OVMI_CVT_F64_S 0x6B // %r = (t) %a (sign aware) +#define OVMI_TRANSMUTE_I32 0x6C // %r = *(t *) &%a (reinterpret bytes) +#define OVMI_TRANSMUTE_I64 0x6D // %r = *(t *) &%a (reinterpret bytes) +#define OVMI_TRANSMUTE_F32 0x6E // %r = *(t *) &%a (reinterpret bytes) +#define OVMI_TRANSMUTE_F64 0x6F // %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 +// +#define OVM_TYPED_INSTR(instr, type) (((type) << 24) | (instr)) + + +#endif + diff --git a/interpreter/include/vm_codebuilder.h b/interpreter/include/vm_codebuilder.h new file mode 100644 index 00000000..80a062b0 --- /dev/null +++ b/interpreter/include/vm_codebuilder.h @@ -0,0 +1,90 @@ +#ifndef _OVM_CODE_BUILDER_H +#define _OVM_CODE_BUILDER_H + +#include "vm.h" +#include "ovm_debug.h" + +typedef struct ovm_code_builder_t ovm_code_builder_t; +typedef struct label_target_t label_target_t; +typedef struct branch_patch_t branch_patch_t; +typedef enum label_kind_t label_kind_t; + +// +// A new code builder will be "made" for each function +// being compiled. +struct ovm_code_builder_t { + bh_arr(i32) execution_stack; + + i32 next_label_idx; + bh_arr(label_target_t) label_stack; + bh_arr(branch_patch_t) branch_patches; + + i32 param_count, local_count; + + ovm_program_t *program; + i32 start_instr; + + i32 func_table_arr_idx; + i32 highest_value_number; + + debug_info_builder_t *debug_builder; +}; + +enum label_kind_t { + label_kind_func, + label_kind_block, + label_kind_loop, + label_kind_if, +}; + +struct label_target_t { + i32 idx; + label_kind_t kind; + 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; + bool targets_else; +}; + +ovm_code_builder_t ovm_code_builder_new(ovm_program_t *program, debug_info_builder_t *debug, i32 param_count, i32 local_count); +label_target_t ovm_code_builder_wasm_target_idx(ovm_code_builder_t *builder, i32 idx); +i32 ovm_code_builder_push_label_target(ovm_code_builder_t *builder, label_kind_t kind); +void ovm_code_builder_pop_label_target(ovm_code_builder_t *builder); +void ovm_code_builder_patch_else(ovm_code_builder_t *builder, label_target_t if_target); +void ovm_code_builder_free(ovm_code_builder_t *builder); +void ovm_code_builder_add_nop(ovm_code_builder_t *builder); +void ovm_code_builder_add_binop(ovm_code_builder_t *builder, u32 instr); +void ovm_code_builder_add_unop(ovm_code_builder_t *builder, u32 instr); +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, bool branch_if_true, bool targets_else); +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_drop_value(ovm_code_builder_t *builder); +void ovm_code_builder_add_local_get(ovm_code_builder_t *builder, i32 local_idx); +void ovm_code_builder_add_local_set(ovm_code_builder_t *builder, i32 local_idx); +void ovm_code_builder_add_local_tee(ovm_code_builder_t *builder, i32 local_idx); +void ovm_code_builder_add_register_get(ovm_code_builder_t *builder, i32 local_idx); +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_atomic_load(ovm_code_builder_t *builder, u32 ovm_type, i32 offset); +void ovm_code_builder_add_atomic_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); + +#endif diff --git a/interpreter/src/debug/debug_host.c b/interpreter/src/debug/debug_host.c new file mode 100644 index 00000000..8ae0ce19 --- /dev/null +++ b/interpreter/src/debug/debug_host.c @@ -0,0 +1,65 @@ + +#include "ovm_debug.h" +#include "vm.h" + +void debug_host_init(debug_state_t *debug, struct ovm_engine_t *ovm_engine) { + memset(debug, 0, sizeof(*debug)); + debug->alloc = bh_heap_allocator(); + debug->ovm_engine = ovm_engine; + + bh_arena_init(&debug->tmp_arena, bh_heap_allocator(), 16 * 1024); + debug->tmp_alloc = bh_arena_allocator(&debug->tmp_arena); + + debug->info = NULL; + + debug->threads = NULL; + debug->next_thread_id = 1; + bh_arr_new(debug->alloc, debug->threads, 4); + + debug->listen_socket_fd = 0; + debug->client_fd = 0; + + if (pipe(debug->state_change_pipes) != 0) { + printf("[ERROR] Failed to create thread notification pipes.\n"); + } +} + +void debug_host_start(debug_state_t *debug) { + if (debug->debug_thread_running) return; + + pthread_create(&debug->debug_thread, NULL, __debug_thread_entry, debug); +} + +void debug_host_stop(debug_state_t *debug) { + debug->debug_thread_running = false; + pthread_join(debug->debug_thread, NULL); +} + +u32 debug_host_register_thread(debug_state_t *debug, ovm_state_t *ovm_state) { + debug_thread_state_t *new_thread = bh_alloc(debug->alloc, sizeof(*new_thread)); + memset(new_thread, 0, sizeof(*new_thread)); + + new_thread->state = debug_state_starting; + new_thread->ovm_state = ovm_state; + new_thread->run_count = 0; // Start threads in stopped state. + sem_init(&new_thread->wait_semaphore, 0, 0); + + new_thread->breakpoints = NULL; + bh_arr_new(debug->alloc, new_thread->breakpoints, 8); + + u32 id = debug->next_thread_id++; + new_thread->id = id; + + new_thread->state_change_write_fd = debug->state_change_pipes[1]; + + bh_arr_push(debug->threads, new_thread); + return id; +} + +debug_thread_state_t *debug_host_lookup_thread(debug_state_t *debug, u32 id) { + bh_arr_each(debug_thread_state_t *, pthread, debug->threads) { + if ((*pthread)->id == id) return *pthread; + } + return NULL; +} + diff --git a/interpreter/src/debug/debug_info.c b/interpreter/src/debug/debug_info.c new file mode 100644 index 00000000..6f100037 --- /dev/null +++ b/interpreter/src/debug/debug_info.c @@ -0,0 +1,236 @@ + +#include "ovm_debug.h" + +void debug_info_init(debug_info_t *info) { + memset(info, 0, sizeof(*info)); + + info->alloc = bh_heap_allocator(); + info->has_debug_info = false; + bh_arr_new(info->alloc, info->funcs, 16); + bh_arr_new(info->alloc, info->line_info, 1024); + bh_arr_new(info->alloc, info->instruction_reducer, 4096); + bh_arr_new(info->alloc, info->files, 16); + bh_arr_new(info->alloc, info->line_to_instruction, 1024); + bh_arr_new(info->alloc, info->symbols, 128); + bh_arr_new(info->alloc, info->symbol_scopes, 128); +} + +void debug_info_free(debug_info_t *info) { + bh_arr_free(info->funcs); + bh_arr_free(info->line_info); + bh_arr_free(info->instruction_reducer); + + bh_arr_each(debug_file_info_t, file, info->files) { + bh_free(info->alloc, file->name); + } + bh_arr_free(info->files); +} + +void debug_info_import_file_info(debug_info_t *info, u8 *data, u32 len) { + u32 offset = 0; + info->has_debug_info = true; + + i32 count = uleb128_to_uint(data, &offset); + fori (i, 0, (i32) count) { + debug_file_info_t file_info; + file_info.line_buffer_offset = -1; + + u32 file_id = uleb128_to_uint(data, &offset); + file_info.file_id = file_id; + file_info.line_count = uleb128_to_uint(data, &offset); + + u32 name_length = uleb128_to_uint(data, &offset); + file_info.name = bh_alloc_array(info->alloc, char, name_length + 1); + memcpy(file_info.name, data + offset, name_length); + file_info.name[name_length] = 0; + offset += name_length; + + bh_arr_set_at(info->files, file_id, file_info); + } + + assert(offset == len); +} + +void debug_info_import_func_info(debug_info_t *info, u8 *data, u32 len) { + u32 offset = 0; + info->has_debug_info = true; + + i32 count = uleb128_to_uint(data, &offset); + fori (i, 0, (i32) count) { + debug_func_info_t func_info; + func_info.func_id = uleb128_to_uint(data, &offset); + func_info.file_id = uleb128_to_uint(data, &offset); + func_info.line = uleb128_to_uint(data, &offset); + + u32 name_length = uleb128_to_uint(data, &offset); + if (name_length == 0) { + func_info.name = NULL; + } else { + func_info.name = bh_alloc_array(info->alloc, char, name_length + 1); + memcpy(func_info.name, data + offset, name_length); + func_info.name[name_length] = 0; + offset += name_length; + } + + func_info.internal = data[offset++] != 0; + func_info.debug_op_offset = uleb128_to_uint(data, &offset); + func_info.stack_ptr_idx = uleb128_to_uint(data, &offset); + + uleb128_to_uint(data, &offset); + + bh_arr_set_at(info->funcs, func_info.func_id, func_info); + } + + assert(offset == len); +} + +void debug_info_import_sym_info(debug_info_t *info, u8 *data, u32 len) { + u32 offset = 0; + info->has_debug_info = true; + + i32 count = uleb128_to_uint(data, &offset); + fori (i, 0, count) { + debug_sym_info_t sym_info; + sym_info.sym_id = uleb128_to_uint(data, &offset); + + u32 name_length = uleb128_to_uint(data, &offset); + if (name_length == 0) { + sym_info.name = NULL; + } else { + sym_info.name = bh_alloc_array(info->alloc, char, name_length + 1); + memcpy(sym_info.name, data + offset, name_length); + sym_info.name[name_length] = 0; + offset += name_length; + } + + sym_info.loc_kind = uleb128_to_uint(data, &offset); + sym_info.loc = uleb128_to_uint(data, &offset); + sym_info.type = uleb128_to_uint(data, &offset); + + bh_arr_set_at(info->symbols, sym_info.sym_id, sym_info); + } + + assert(offset == len); +} + +void debug_info_import_type_info(debug_info_t *info, u8 *data, u32 len) { + u32 offset = 0; + info->has_debug_info = true; + + i32 count = uleb128_to_uint(data, &offset); + fori (i, 0, count) { + debug_type_info_t type; + type.id = uleb128_to_uint(data, &offset); + + u32 name_length = uleb128_to_uint(data, &offset); + if (name_length == 0) { + type.name = NULL; + } else { + type.name = bh_alloc_array(info->alloc, char, name_length + 1); + memcpy(type.name, data + offset, name_length); + type.name[name_length] = 0; + offset += name_length; + } + + type.size = uleb128_to_uint(data, &offset); + type.kind = uleb128_to_uint(data, &offset); + + switch (type.kind) { + case debug_type_kind_primitive: + type.primitive.primitive_kind = uleb128_to_uint(data, &offset); + break; + + case debug_type_kind_modifier: + type.modifier.modifier_kind = uleb128_to_uint(data, &offset); + type.modifier.modified_type = uleb128_to_uint(data, &offset); + break; + + case debug_type_kind_structure: + type.structure.member_count = uleb128_to_uint(data, &offset); + type.structure.members = bh_alloc_array(info->alloc, debug_type_structure_member_t, type.structure.member_count); + + fori (i, 0, type.structure.member_count) { + type.structure.members[i].offset = uleb128_to_uint(data, &offset); + type.structure.members[i].type = uleb128_to_uint(data, &offset); + + u32 name_length = uleb128_to_uint(data, &offset); + type.structure.members[i].name = bh_alloc_array(info->alloc, char, name_length + 1); + memcpy(type.structure.members[i].name, data + offset, name_length); + type.structure.members[i].name[name_length] = 0; + offset += name_length; + } + break; + + case debug_type_kind_array: + type.array.count = uleb128_to_uint(data, &offset); + type.array.type = uleb128_to_uint(data, &offset); + break; + + case debug_type_kind_alias: + type.alias.alias_kind = uleb128_to_uint(data, &offset); + type.alias.aliased_type = uleb128_to_uint(data, &offset); + break; + + case debug_type_kind_function: + type.function.param_count = uleb128_to_uint(data, &offset); + type.function.param_types = bh_alloc_array(info->alloc, u32, type.function.param_count); + + fori (i, 0, type.function.param_count) { + type.function.param_types[i] = uleb128_to_uint(data, &offset); + } + + type.function.return_type = uleb128_to_uint(data, &offset); + break; + + // Error handling + default: assert(("Unrecognized type kind", 0)); + } + + bh_arr_set_at(info->types, type.id, type); + } + + assert(offset == len); +} + +bool debug_info_lookup_location(debug_info_t *info, u32 instruction, debug_loc_info_t *out) { + if (!info || !info->has_debug_info) return false; + + if (instruction > (u32) bh_arr_length(info->instruction_reducer)) return false; + i32 loc = info->instruction_reducer[instruction]; + if (loc < 0) return false; + + *out = info->line_info[loc]; + return true; +} + +bool debug_info_lookup_file(debug_info_t *info, u32 file_id, debug_file_info_t *out) { + if (!info || !info->has_debug_info) return false; + + if (file_id > (u32) bh_arr_length(info->files)) return false; + *out = info->files[file_id]; + return true; +} + +// +// For now, this is going to compare the strings exactly. In the future, it might be a good +// to do a levenschtein distance or something, so the full path isn't needed. +bool debug_info_lookup_file_by_name(debug_info_t *info, char *name, debug_file_info_t *out) { + if (!info || !info->has_debug_info) return false; + + bh_arr_each(debug_file_info_t, file, info->files) { + if (!strcmp(file->name, name)) { + *out = *file; + return true; + } + } + + return false; +} + +bool debug_info_lookup_func(debug_info_t *info, u32 func_id, debug_func_info_t *out) { + if (!info || !info->has_debug_info) return false; + + if (func_id > (u32) bh_arr_length(info->funcs)) return false; + *out = info->funcs[func_id]; + return true; +} \ No newline at end of file diff --git a/interpreter/src/debug/debug_info_builder.c b/interpreter/src/debug/debug_info_builder.c new file mode 100644 index 00000000..13c33dfa --- /dev/null +++ b/interpreter/src/debug/debug_info_builder.c @@ -0,0 +1,158 @@ + + +#include "ovm_debug.h" + + +void debug_info_builder_init(debug_info_builder_t *builder, debug_info_t *info) { + memset(builder, 0, sizeof(*builder)); + + builder->info = info; +} + +void debug_info_builder_prepare(debug_info_builder_t *builder, u8 *data) { + builder->data = data; + builder->reader_offset = 0; + builder->current_file_id = 0; + builder->current_line = 0; + builder->current_scope = -1; + builder->next_file_line_offset = 0; + builder->remaining_reps = 0; +} + +static i32 debug_info_builder_push_scope(debug_info_builder_t *builder) { + debug_sym_scope_t scope; + scope.symbols = NULL; + bh_arr_new(builder->info->alloc, scope.symbols, 4); + scope.parent = builder->current_scope; + + i32 scope_id = bh_arr_length(builder->info->symbol_scopes); + builder->current_scope = scope_id; + + bh_arr_push(builder->info->symbol_scopes, scope); + return scope_id; +} + +static i32 debug_info_builder_pop_scope(debug_info_builder_t *builder) { + if (builder->current_scope == -1) return -1; + + debug_sym_scope_t *scope = &builder->info->symbol_scopes[builder->current_scope]; + builder->current_scope = scope->parent; + return builder->current_scope; +} + +static void debug_info_builder_add_symbol(debug_info_builder_t *builder, u32 sym_id) { + if (builder->current_scope == -1) return; + + debug_sym_scope_t *scope = &builder->info->symbol_scopes[builder->current_scope]; + bh_arr_push(scope->symbols, sym_id); +} + +static void debug_info_builder_parse(debug_info_builder_t *builder) { + u32 count = 0; + + while (!builder->locked) { + u8 instr = builder->data[builder->reader_offset++]; + switch (instr & 0b11000000) { + case 0b00000000: + instr &= 0b00111111; + switch (instr) { + case 0: builder->locked = 1; break; // Early return! + case 1: + builder->current_file_id = uleb128_to_uint(builder->data, &builder->reader_offset); + builder->current_line = uleb128_to_uint(builder->data, &builder->reader_offset); + break; + + case 2: + debug_info_builder_push_scope(builder); + break; + + case 3: + debug_info_builder_pop_scope(builder); + break; + + case 4: + debug_info_builder_add_symbol( + builder, + uleb128_to_uint(builder->data, &builder->reader_offset)); + break; + } + break; + + case 0b01000000: + count = instr & 0x3f; + builder->current_line += count + 1; + builder->remaining_reps = 1; + return; + + case 0b10000000: + count = instr & 0x3f; + builder->current_line -= count + 1; + builder->remaining_reps = 1; + return; + + case 0b11000000: + count = instr & 0x3f; + builder->remaining_reps = count + 1; + return; + } + } +} + +void debug_info_builder_step(debug_info_builder_t *builder) { + if (builder->data == NULL) return; + + while (builder->remaining_reps == 0 && !builder->locked) { + debug_info_builder_parse(builder); + + debug_loc_info_t info; + info.file_id = builder->current_file_id; + info.line = builder->current_line; + info.symbol_scope = builder->current_scope; + bh_arr_push(builder->info->line_info, info); + + debug_file_info_t *file_info = &builder->info->files[info.file_id]; + if (file_info->line_buffer_offset == -1) { + file_info->line_buffer_offset = builder->next_file_line_offset; + builder->next_file_line_offset += file_info->line_count; + } + + i32 line_index = file_info->line_buffer_offset + builder->current_line; + u32 target = bh_arr_length(builder->info->instruction_reducer); + + if (line_index >= bh_arr_capacity(builder->info->line_to_instruction) || + builder->info->line_to_instruction[line_index] == 0) { + bh_arr_set_at(builder->info->line_to_instruction, line_index, target); + } + } + + if (builder->locked) return; + + assert(builder->remaining_reps); + builder->remaining_reps -= 1; + return; +} + +void debug_info_builder_emit_location(debug_info_builder_t *builder) { + bh_arr_push(builder->info->instruction_reducer, bh_arr_length(builder->info->line_info) - 1); +} + +void debug_info_builder_begin_func(debug_info_builder_t *builder, i32 func_idx) { + if (!builder->data) return; + if (func_idx >= bh_arr_length(builder->info->funcs)) return; + + debug_func_info_t *func_info = &builder->info->funcs[func_idx]; + + builder->reader_offset = func_info->debug_op_offset; + assert(builder->data[builder->reader_offset] == 2); + assert(builder->data[builder->reader_offset+1] == 1); + builder->remaining_reps = 0; + builder->locked = 0; +} + +void debug_info_builder_end_func(debug_info_builder_t *builder) { + if (!builder->data) return; + + assert(!builder->locked); + debug_info_builder_step(builder); + assert(builder->locked); +} diff --git a/interpreter/src/debug/debug_runtime_values.c b/interpreter/src/debug/debug_runtime_values.c new file mode 100644 index 00000000..2edde566 --- /dev/null +++ b/interpreter/src/debug/debug_runtime_values.c @@ -0,0 +1,279 @@ + +#include "ovm_debug.h" +#include "vm.h" + +static char write_buf[4096]; + +#define WRITE(str) do { \ + bh_buffer_write_string(&builder->output, str); \ + } while (0); + +#define WRITE_FORMAT(format, ...) do { \ + u32 len = snprintf(write_buf, 4096, format, __VA_ARGS__); \ + bh_buffer_append(&builder->output, write_buf, len); \ + } while (0); + +static bool lookup_register_in_frame(ovm_state_t *state, ovm_stack_frame_t *frame, u32 reg, ovm_value_t *out) { + + u32 val_num_base; + if (frame == &bh_arr_last(state->stack_frames)) { + val_num_base = state->value_number_offset; + } else { + val_num_base = (frame + 1)->value_number_base; + } + + *out = state->numbered_values[val_num_base + reg]; + return true; +} + +static void append_value_from_memory_with_type(debug_runtime_value_builder_t *builder, void *base, u32 type_id) { + debug_type_info_t *type = &builder->info->types[type_id]; + + switch (type->kind) { + case debug_type_kind_primitive: + switch (type->primitive.primitive_kind) { + case debug_type_primitive_kind_void: WRITE("void"); break; + case debug_type_primitive_kind_signed_integer: + switch (type->size) { + case 1: WRITE_FORMAT("%hhd", *(i8 *) base); break; + case 2: WRITE_FORMAT("%hd", *(i16 *) base); break; + case 4: WRITE_FORMAT("%d", *(i32 *) base); break; + case 8: WRITE_FORMAT("%ld", *(i64 *) base); break; + default: WRITE("(err)"); break; + } + break; + + case debug_type_primitive_kind_unsigned_integer: + switch (type->size) { + case 1: WRITE_FORMAT("%hhu", *(u8 *) base); break; + case 2: WRITE_FORMAT("%hu", *(u16 *) base); break; + case 4: WRITE_FORMAT("%u", *(u32 *) base); break; + case 8: WRITE_FORMAT("%lu", *(u64 *) base); break; + default: WRITE("(err)"); break; + } + break; + + case debug_type_primitive_kind_float: + switch (type->size) { + case 4: WRITE_FORMAT("%f", *(f32 *) base); break; + case 8: WRITE_FORMAT("%f", *(f64 *) base); break; + default: WRITE("(err)"); break; + } + break; + + case debug_type_primitive_kind_boolean: + if ((*(u8 *) base) != 0) { WRITE("true"); } + else { WRITE("false"); } + break; + + default: + WRITE("(err)"); + } + break; + + case debug_type_kind_modifier: + switch (type->modifier.modifier_kind) { + case debug_type_modifier_kind_pointer: + switch (type->size) { + case 4: WRITE_FORMAT("0x%x", *(u32 *) base); break; + case 8: WRITE_FORMAT("0x%lx", *(u64 *) base); break; + default: WRITE("(err)"); break; + } + break; + + default: + append_value_from_memory_with_type(builder, base, type->modifier.modified_type); + break; + } + break; + + case debug_type_kind_alias: + append_value_from_memory_with_type(builder, base, type->alias.aliased_type); + break; + + case debug_type_kind_function: + WRITE_FORMAT("func[%d]", *(u32 *) base); + break; + + case debug_type_kind_structure: { + WRITE("{ "); + + fori (i, 0, (i32) type->structure.member_count) { + if (i != 0) WRITE(", "); + + WRITE_FORMAT("%s=", type->structure.members[i].name); + + u32 offset = type->structure.members[i].offset; + u32 type_id = type->structure.members[i].type; + append_value_from_memory_with_type(builder, bh_pointer_add(base, offset), type_id); + } + + WRITE(" }"); + break; + } + + case debug_type_kind_array: { + WRITE("["); + + debug_type_info_t *elem_type = &builder->info->types[type->array.type]; + fori (i, 0, (i32) type->array.count) { + if (i != 0) WRITE(", "); + + append_value_from_memory_with_type(builder, bh_pointer_add(base, i * elem_type->size), elem_type->id); + } + + WRITE("]"); + break; + } + + default: WRITE("(unknown)"); break; + } +} + +static void append_ovm_value_with_type(debug_runtime_value_builder_t *builder, ovm_value_t value, u32 type_id) { + debug_type_info_t *type = &builder->info->types[type_id]; + + switch (type->kind) { + case debug_type_kind_primitive: + switch (type->primitive.primitive_kind) { + case debug_type_primitive_kind_void: WRITE("void"); break; + case debug_type_primitive_kind_signed_integer: + switch (type->size) { + case 1: WRITE_FORMAT("%hhd", value.i8); break; + case 2: WRITE_FORMAT("%hd", value.i16); break; + case 4: WRITE_FORMAT("%d", value.i32); break; + case 8: WRITE_FORMAT("%ld", value.i64); break; + default: WRITE("(err)"); break; + } + break; + + case debug_type_primitive_kind_unsigned_integer: + switch (type->size) { + case 1: WRITE_FORMAT("%hhu", value.u8); break; + case 2: WRITE_FORMAT("%hu", value.u16); break; + case 4: WRITE_FORMAT("%u", value.u32); break; + case 8: WRITE_FORMAT("%lu", value.u64); break; + default: WRITE("(err)"); break; + } + break; + + case debug_type_primitive_kind_float: + switch (type->size) { + case 4: WRITE_FORMAT("%f", value.f32); break; + case 8: WRITE_FORMAT("%f", value.f64); break; + default: WRITE("(err)"); break; + } + break; + + case debug_type_primitive_kind_boolean: + if (value.u64 != 0) { WRITE("true"); } + else { WRITE("false"); } + break; + + default: + WRITE("(err)"); + } + break; + + case debug_type_kind_modifier: + switch (type->modifier.modifier_kind) { + case debug_type_modifier_kind_pointer: + switch (type->size) { + case 4: WRITE_FORMAT("0x%x", value.u32); break; + case 8: WRITE_FORMAT("0x%lx", value.u64); break; + default: WRITE("(err)"); break; + } + break; + + default: + append_ovm_value_with_type(builder, value, type->modifier.modified_type); + break; + } + break; + + case debug_type_kind_alias: + append_ovm_value_with_type(builder, value, type->alias.aliased_type); + break; + + case debug_type_kind_function: + WRITE_FORMAT("func[%d]", value.u32); + break; + + case debug_type_kind_array: { + void *base = bh_pointer_add(builder->state->ovm_engine->memory, value.u32); + append_value_from_memory_with_type(builder, base, type_id); + break; + } + + default: WRITE("(unknown)"); break; + } +} + +static void append_value_from_stack(debug_runtime_value_builder_t *builder, u32 offset, u32 type_id) { + ovm_value_t stack_ptr; + if (!lookup_register_in_frame(builder->ovm_state, builder->ovm_frame, builder->func_info.stack_ptr_idx, &stack_ptr)) { + WRITE("(no stack ptr)"); + return; + } + + void *base = bh_pointer_add(builder->state->ovm_engine->memory, stack_ptr.u32 + offset); + + append_value_from_memory_with_type(builder, base, type_id); +} + +static void append_value_from_register(debug_runtime_value_builder_t *builder, u32 reg, u32 type_id) { + ovm_value_t value; + + debug_type_info_t *type = &builder->info->types[type_id]; + if (type->kind == debug_type_kind_structure) { + WRITE("{ "); + + fori (i, 0, (i32) type->structure.member_count) { + if (i != 0) WRITE(", "); + + WRITE_FORMAT("%s=", type->structure.members[i].name); + + if (!lookup_register_in_frame(builder->ovm_state, builder->ovm_frame, reg + i, &value)) { + WRITE("(err)") + continue; + } + + append_ovm_value_with_type(builder, value, type->structure.members[i].type); + } + + WRITE(" }"); + return; + } + + if (!lookup_register_in_frame(builder->ovm_state, builder->ovm_frame, reg, &value)) { + WRITE("(err)") + return; + } + + append_ovm_value_with_type(builder, value, type_id); +} + + + + +void debug_runtime_value_build_init(debug_runtime_value_builder_t *builder, bh_allocator alloc) { + bh_buffer_init(&builder->output, alloc, 1024); +} + +void debug_runtime_value_build_free(debug_runtime_value_builder_t *builder) { + bh_buffer_free(&builder->output); +} + +void debug_runtime_value_build_string(debug_runtime_value_builder_t *builder) { + if (builder->sym_info.loc_kind == debug_sym_loc_register) { + append_value_from_register(builder, builder->sym_info.loc, builder->sym_info.type); + return; + } + + if (builder->sym_info.loc_kind == debug_sym_loc_stack) { + append_value_from_stack(builder, builder->sym_info.loc, builder->sym_info.type); + return; + } + + WRITE("(location unknown)"); +} diff --git a/interpreter/src/debug/debug_thread.c b/interpreter/src/debug/debug_thread.c new file mode 100644 index 00000000..ba0fa285 --- /dev/null +++ b/interpreter/src/debug/debug_thread.c @@ -0,0 +1,544 @@ + +#include "ovm_debug.h" +#include "vm.h" + +#include +#include +#include +#include +#include + + +#define CMD_NOP 0 +#define CMD_RES 1 +#define CMD_PAUSE 2 +#define CMD_BRK 3 +#define CMD_CLR_BRK 4 +#define CMD_STEP 5 +#define CMD_TRACE 6 +#define CMD_THREADS 7 +#define CMD_VARS 8 + +#define EVT_NOP 0 +#define EVT_BRK_HIT 1 +#define EVT_PAUSE 2 +#define EVT_NEW_THREAD 3 // Not Implemented +#define EVT_RESPONSE 0xffffffff + +struct msg_parse_ctx_t { + char *data; + unsigned int offset; + unsigned int bytes_read; +}; + +static void send_response_header(debug_state_t *debug, unsigned int message_number) { + unsigned int RESPONSE_HEADER = EVT_RESPONSE; + bh_buffer_write_u32(&debug->send_buffer, EVT_RESPONSE); + bh_buffer_write_u32(&debug->send_buffer, message_number); +} + +static void send_string(debug_state_t *debug, const char *str) { + if (!str) { + bh_buffer_write_u32(&debug->send_buffer, 0); + } else { + unsigned int len = strlen(str); + bh_buffer_write_u32(&debug->send_buffer, len); + bh_buffer_append(&debug->send_buffer, str, len); + } +} + +static void send_bytes(debug_state_t *debug, const char *bytes, unsigned int len) { + bh_buffer_write_u32(&debug->send_buffer, len); + bh_buffer_append(&debug->send_buffer, bytes, len); +} + +static void send_int(debug_state_t *debug, unsigned int x) { + bh_buffer_write_u32(&debug->send_buffer, x); +} + +static void send_bool(debug_state_t *debug, bool x) { + bool v = x ? 1 : 0; + bh_buffer_write_byte(&debug->send_buffer, v); +} + +static int parse_int(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { + int i = *(unsigned int *) &ctx->data[ctx->offset]; + ctx->offset += sizeof(unsigned int); + return i; +} + +static char *parse_bytes(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { + int len = parse_int(debug, ctx); + + char *buf = bh_alloc_array(debug->tmp_alloc, char, len); + memcpy(buf, &ctx->data[ctx->offset], len); + ctx->offset += len; + + return buf; +} + +static char *parse_string(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { + int len = parse_int(debug, ctx); + + char *buf = bh_alloc_array(debug->tmp_alloc, char, len + 1); + memcpy(buf, &ctx->data[ctx->offset], len); + ctx->offset += len; + + buf[len] = 0; + return buf; +} + +static void resume_thread(debug_thread_state_t *thread) { + thread->run_count = -1; + sem_post(&thread->wait_semaphore); +} + +static void get_stack_frame_location(debug_state_t *debug, + debug_func_info_t *func_info, debug_file_info_t *file_info, debug_loc_info_t *loc_info, + debug_thread_state_t *thread, ovm_stack_frame_t *frame) { + + ovm_func_t *func = frame->func; + + assert(debug_info_lookup_func(debug->info, func->id, func_info)); + + u32 instr; + if (frame == &bh_arr_last(thread->ovm_state->stack_frames)) { + instr = thread->ovm_state->pc; + } else { + instr = (frame + 1)->return_address; + } + + while (!debug_info_lookup_location(debug->info, instr, loc_info)) + instr++; + + assert(debug_info_lookup_file(debug->info, loc_info->file_id, file_info)); +} + +static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { +#define ON_THREAD(tid) \ + bh_arr_each(debug_thread_state_t *, thread, debug->threads) \ + if ((*thread)->id == tid) + + u32 msg_id = parse_int(debug, ctx); + u32 command_id = parse_int(debug, ctx); + + // printf("[INFO ] Recv command: %d\n", command_id); + + switch (command_id) { + case CMD_NOP: { + send_response_header(debug, msg_id); + break; + } + + case CMD_RES: { + u32 thread_id = parse_int(debug, ctx); + + bool resumed_a_thread = false; + + // Release the thread(s) + bh_arr_each(debug_thread_state_t *, thread, debug->threads) { + if (thread_id == 0xffffffff || (*thread)->id == thread_id) { + resume_thread(*thread); + resumed_a_thread = true; + } + } + + send_response_header(debug, msg_id); + send_bool(debug, resumed_a_thread); + break; + } + + case CMD_PAUSE: { + u32 thread_id = parse_int(debug, ctx); + + ON_THREAD(thread_id) { + (*thread)->run_count = 0; + } + + send_response_header(debug, msg_id); + break; + } + + case CMD_BRK: { + char *filename = parse_string(debug, ctx); + unsigned int line = parse_int(debug, ctx); + + // + // TODO: This translation logic will have to be abstracted + debug_file_info_t file_info; + bool file_found = debug_info_lookup_file_by_name(debug->info, filename, &file_info); + if (!file_found) { + goto brk_send_error; + } + + if (line > file_info.line_count) { + goto brk_send_error; + } + + u32 instr; + while ((instr = debug->info->line_to_instruction[file_info.line_buffer_offset + line]) == 0) { + line += 1; + + if (line > file_info.line_count) { + goto brk_send_error; + } + } + + printf("[INFO ] Setting breakpoint at %s:%d (%xd)\n", filename, line, instr); + + debug_breakpoint_t bp; + bp.id = debug->next_breakpoint_id++; + bp.instr = instr; + bp.file_id = file_info.file_id; + bp.line = line; + bh_arr_each(debug_thread_state_t *, thread, debug->threads) { + bh_arr_push((*thread)->breakpoints, bp); + } + + send_response_header(debug, msg_id); + send_bool(debug, true); + send_int(debug, bp.id); // TODO: This should be a unique breakpoint ID + send_int(debug, line); + break; + + brk_send_error: + send_response_header(debug, msg_id); + send_bool(debug, false); + send_int(debug, -1); + send_int(debug, 0); + break; + } + + case CMD_CLR_BRK: { + char *filename = parse_string(debug, ctx); + + debug_file_info_t file_info; + bool file_found = debug_info_lookup_file_by_name(debug->info, filename, &file_info); + if (!file_found) { + goto clr_brk_send_error; + } + + bh_arr_each(debug_thread_state_t *, thread, debug->threads) { + bh_arr_each(debug_breakpoint_t, bp, (*thread)->breakpoints) { + if (bp->file_id == file_info.file_id) { + // This is kind of hacky but it does successfully delete + // a single element from the array and move the iterator. + bh_arr_fastdelete((*thread)->breakpoints, bp - (*thread)->breakpoints); + bp--; + } + } + } + + send_response_header(debug, msg_id); + send_bool(debug, true); + break; + + clr_brk_send_error: + send_response_header(debug, msg_id); + send_bool(debug, false); + break; + } + + case CMD_STEP: { + u32 granularity = parse_int(debug, ctx); + u32 thread_id = parse_int(debug, ctx); + + if (granularity == 1) { + ON_THREAD(thread_id) { + (*thread)->pause_at_next_line = true; + (*thread)->pause_within = -1; + resume_thread(*thread); + } + } + + if (granularity == 2) { + ON_THREAD(thread_id) { + (*thread)->run_count = 1; + resume_thread(*thread); + } + } + + if (granularity == 3) { + ON_THREAD(thread_id) { + ovm_stack_frame_t *last_frame = &bh_arr_last((*thread)->ovm_state->stack_frames); + (*thread)->pause_at_next_line = true; + (*thread)->pause_within = last_frame->func->id; + (*thread)->extra_frames_since_last_pause = 0; + resume_thread(*thread); + } + } + + if (granularity == 4) { + ON_THREAD(thread_id) { + if (bh_arr_length((*thread)->ovm_state->stack_frames) == 1) { + (*thread)->pause_within = -1; + } else { + ovm_stack_frame_t *last_frame = &bh_arr_last((*thread)->ovm_state->stack_frames); + (*thread)->pause_within = (last_frame - 1)->func->id; + } + + (*thread)->pause_at_next_line = true; + (*thread)->extra_frames_since_last_pause = 0; + resume_thread(*thread); + } + } + + send_response_header(debug, msg_id); + break; + } + + case CMD_TRACE: { + unsigned int thread_id = parse_int(debug, ctx); + + debug_thread_state_t *thread = NULL; + bh_arr_each(debug_thread_state_t *, pthread, debug->threads) { + if ((*pthread)->id == thread_id) { + thread = *pthread; + break; + } + } + + if (thread == NULL) { + send_response_header(debug, msg_id); + send_int(debug, 0); + break; + } + + bh_arr(ovm_stack_frame_t) frames = thread->ovm_state->stack_frames; + + send_response_header(debug, msg_id); + send_int(debug, bh_arr_length(frames)); + + bh_arr_rev_each(ovm_stack_frame_t, frame, frames) { + debug_func_info_t func_info; + debug_file_info_t file_info; + debug_loc_info_t loc_info; + + get_stack_frame_location(debug, &func_info, &file_info, &loc_info, thread, frame); + + send_string(debug, func_info.name); + send_string(debug, file_info.name); + send_int(debug, loc_info.line); + } + + break; + } + + case CMD_THREADS: { + bh_arr(debug_thread_state_t *) threads = debug->threads; + + send_response_header(debug, msg_id); + send_int(debug, bh_arr_length(threads)); + + char buf[128]; + bh_arr_each(debug_thread_state_t *, thread, threads) { + send_int(debug, (*thread)->id); + + snprintf(buf, 128, "thread #%d", (*thread)->id); + send_string(debug, buf); + } + + break; + } + + case CMD_VARS: { + i32 stack_frame = parse_int(debug, ctx); + u32 thread_id = parse_int(debug, ctx); + + ON_THREAD(thread_id) { + bh_arr(ovm_stack_frame_t) frames = (*thread)->ovm_state->stack_frames; + if (stack_frame >= bh_arr_length(frames)) { + goto vars_error; + } + + ovm_stack_frame_t *frame = &frames[bh_arr_length(frames) - 1 - stack_frame]; + + debug_func_info_t func_info; + debug_file_info_t file_info; + debug_loc_info_t loc_info; + + get_stack_frame_location(debug, &func_info, &file_info, &loc_info, *thread, frame); + + send_response_header(debug, msg_id); + + i32 symbol_scope = loc_info.symbol_scope; + while (symbol_scope != -1) { + debug_sym_scope_t sym_scope = debug->info->symbol_scopes[symbol_scope]; + + bh_arr_each(u32, sym_id, sym_scope.symbols) { + debug_sym_info_t *sym = &debug->info->symbols[*sym_id]; + + send_int(debug, 0); + send_string(debug, sym->name); + + debug_runtime_value_builder_t builder; + builder.state = debug; + builder.info = debug->info; + builder.ovm_state = (*thread)->ovm_state; + builder.ovm_frame = frame; + builder.sym_scope = sym_scope; + builder.func_info = func_info; + builder.file_info = file_info; + builder.loc_info = loc_info; + builder.sym_info = *sym; + + debug_runtime_value_build_init(&builder, bh_heap_allocator()); + debug_runtime_value_build_string(&builder); + send_bytes(debug, builder.output.data, builder.output.length); + debug_runtime_value_build_free(&builder); + + debug_type_info_t *type = &debug->info->types[sym->type]; + send_string(debug, type->name); + } + + symbol_scope = sym_scope.parent; + } + + send_int(debug, 1); + return; + } + + vars_error: + send_response_header(debug, msg_id); + send_int(debug, 1); + break; + } + } +} + +static void process_message(debug_state_t *debug, char *msg, unsigned int bytes_read) { + struct msg_parse_ctx_t ctx; + ctx.data = msg; + ctx.offset = 0; + ctx.bytes_read = bytes_read; + + while (ctx.offset < ctx.bytes_read) { + u32 msg_id = *(u32 *) &ctx.data[ctx.offset]; + assert(msg_id != 0xffffffff); + + process_command(debug, &ctx); + } +} + +void *__debug_thread_entry(void * data) { + debug_state_t *debug = data; + debug->debug_thread_running = true; + + // Set up socket listener + // Wait for initial connection/handshake before entering loop. + + debug->listen_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + struct sockaddr_un local_addr, remote_addr; + local_addr.sun_family = AF_UNIX; + strcpy(local_addr.sun_path, "/tmp/ovm-debug.0000"); // TODO: Make this dynamic so mulitple servers can exist at a time. + unlink(local_addr.sun_path); // TODO: Remove this line for the same reason. + int len = strlen(local_addr.sun_path) + sizeof(local_addr.sun_family); + bind(debug->listen_socket_fd, (struct sockaddr *)&local_addr, len); + + // + // Currently, there can only be 1 connected debugger instance at a time. + listen(debug->listen_socket_fd, 1); + + len = sizeof(struct sockaddr_un); + debug->client_fd = accept(debug->listen_socket_fd, (void * restrict)&remote_addr, &len); + + close(debug->listen_socket_fd); + + // Disable blocking reads and write in the client socket + // Alternatively, a MSG_DONTWAIT could be used below + fcntl(debug->client_fd, F_SETFL, O_NONBLOCK); + fcntl(debug->state_change_pipes[0], F_SETFL, O_NONBLOCK); + + printf("[INFO ] Client connected\n"); + + bh_buffer_init(&debug->send_buffer, bh_heap_allocator(), 1024); + + struct pollfd poll_fds[2]; + poll_fds[0].fd = debug->state_change_pipes[0]; + poll_fds[0].events = POLLIN; + poll_fds[1].fd = debug->client_fd; + poll_fds[1].events = POLLIN; + + char command[4096]; + while (debug->debug_thread_running) { + poll(poll_fds, 2, 1000); + + // + // Try to read commands from the client. + // If an error was returned, bail out of this thread. + i32 bytes_read = recv(debug->client_fd, command, 4096, 0); + if (bytes_read == -1) { + switch (errno) { + case EAGAIN: break; + + case ECONNRESET: + printf("[ERROR] OVM Debugger connection closed by peer.\n"); + debug->debug_thread_running = false; + break; + + default: + printf("[ERROR] OVM Debugger crashed when reading from UNIX socket.\n"); + debug->debug_thread_running = false; + break; + } + } + + // + // If data was returned, process the commands that are inside of it. + if (bytes_read > 0) { + process_message(debug, command, bytes_read); + } + + // + // Check the state of the running program and report any changes. + + char buf; + int dummy = read(debug->state_change_pipes[0], &buf, 1); + + bh_arr_each(debug_thread_state_t *, thread, debug->threads) { + if ((*thread)->state == debug_state_hit_breakpoint) { + (*thread)->state = debug_state_paused; + + i32 instr = -1, bp_id = (*thread)->last_breakpoint_hit; + bh_arr_each(debug_breakpoint_t, bp, (*thread)->breakpoints) { + if (bp->id == bp_id) { + instr = bp->instr; + break; + } + } + + if (instr == -1) continue; + + debug_loc_info_t loc_info; + debug_info_lookup_location(debug->info, instr, &loc_info); + + debug_file_info_t file_info; + debug_info_lookup_file(debug->info, loc_info.file_id, &file_info); + + send_int(debug, EVT_BRK_HIT); + send_int(debug, bp_id); + send_int(debug, (*thread)->id); + } + + if ((*thread)->state == debug_state_pausing) { + (*thread)->state = debug_state_paused; + + send_int(debug, EVT_PAUSE); + send_int(debug, (*thread)->id); + send_int(debug, (*thread)->pause_reason); + } + } + + send(debug->client_fd, debug->send_buffer.data, debug->send_buffer.length, 0); + bh_buffer_clear(&debug->send_buffer); + + bh_arena_clear(&debug->tmp_arena); + } + + close(debug->client_fd); + printf("[INFO ] Session closed\n"); + + unlink(local_addr.sun_path); + return NULL; +} diff --git a/interpreter/src/ovm_cli_test.c b/interpreter/src/ovm_cli_test.c new file mode 100644 index 00000000..98caf1ef --- /dev/null +++ b/interpreter/src/ovm_cli_test.c @@ -0,0 +1,53 @@ +#include "vm.h" + +#include + +void print_result(void *data, ovm_value_t *params, ovm_value_t *result) { + switch (params[0].type) { + case OVM_TYPE_I32: printf("Result: %d\n", params[0].i32); break; + case OVM_TYPE_F32: printf("Result: %f\n", params[0].f32); break; + } +} + +void c_call_1f64(void *data, ovm_value_t *params, ovm_value_t *result) { + result->type = OVM_TYPE_F32; + result->f32 = (f32) ((f64 (*)(f64)) data)(params[0].f32); +} + +int main(int argc, char *argv[]) { + + static ovm_linkable_func_t native_funcs[] = { + { "dummy", 0, { print_result, NULL } }, + { "print", 1, { print_result, NULL } }, + { "sin", 1, { c_call_1f64, sin } }, + { NULL }, + }; + + ovm_store_t *store = ovm_store_new(); + ovm_program_t *prog = ovm_program_new(store); + ovm_engine_t *engine = ovm_engine_new(store); + ovm_state_t *state = ovm_state_new(engine, prog); + + ovm_program_load_from_file(prog, engine, argv[1]); + ovm_program_print_instructions(prog, 0, bh_arr_length(prog->code)); + + static int func_table[] = { 0, 1, 6 }; + ovm_program_register_static_ints(prog, 3, func_table); + + ovm_state_link_external_funcs(prog, state, native_funcs); + + state->pc = 0; + ovm_value_t values[] = { + { .type = OVM_TYPE_I32, .i32 = 1 }, + { .type = OVM_TYPE_I32, .i32 = 2 }, + }; + ovm_value_t result = ovm_func_call(engine, state, prog, 5, 0, values); + // printf("%d %d\n", result.type, result.i32); + + ovm_state_delete(state); + ovm_engine_delete(engine); + ovm_program_delete(prog); + ovm_store_delete(store); + + return 0; +} diff --git a/interpreter/src/vm/code_builder.c b/interpreter/src/vm/code_builder.c new file mode 100644 index 00000000..ec84b473 --- /dev/null +++ b/interpreter/src/vm/code_builder.c @@ -0,0 +1,555 @@ + +#include "vm_codebuilder.h" +#include "ovm_debug.h" + +// #define BUILDER_DEBUG + +#if defined(BUILDER_DEBUG) + #define POP_VALUE(b) (bh_arr_length((b)->execution_stack) == 0 ? (assert(("invalid value pop", 0)), 0) : bh_arr_pop((b)->execution_stack)) +#else + #define POP_VALUE(b) bh_arr_pop((b)->execution_stack) +#endif + +#define PUSH_VALUE(b, r) (bh_arr_push((b)->execution_stack, r)) + +static inline int NEXT_VALUE(ovm_code_builder_t *b) { +#if defined(BUILDER_DEBUG) + b->highest_value_number += 1; + return b->highest_value_number - 1; + +#else + if (bh_arr_length(b->execution_stack) == 0) { + return b->param_count + b->local_count; + } + + b->highest_value_number = bh_max(b->highest_value_number, bh_arr_last(b->execution_stack) + 1); + return bh_arr_last(b->execution_stack) + 1; +#endif +} + +ovm_code_builder_t ovm_code_builder_new(ovm_program_t *program, debug_info_builder_t *debug, i32 param_count, i32 local_count) { + ovm_code_builder_t builder; + builder.param_count = param_count; + builder.local_count = local_count; + builder.start_instr = bh_arr_length(program->code); + builder.program = program; + + builder.execution_stack = NULL; + bh_arr_new(bh_heap_allocator(), builder.execution_stack, 32); + + builder.next_label_idx = 0; + builder.label_stack = NULL; + builder.branch_patches = NULL; + bh_arr_new(bh_heap_allocator(), builder.label_stack, 32); + bh_arr_new(bh_heap_allocator(), builder.branch_patches, 32); + + builder.highest_value_number = param_count + local_count; + + builder.debug_builder = debug; + + return builder; +} + +void ovm_code_builder_free(ovm_code_builder_t *builder) { + bh_arr_free(builder->execution_stack); + bh_arr_free(builder->label_stack); + bh_arr_free(builder->branch_patches); +} + +label_target_t ovm_code_builder_wasm_target_idx(ovm_code_builder_t *builder, i32 idx) { + i32 walker = bh_arr_length(builder->label_stack) - 1 - idx; + assert(walker >= 0); + return builder->label_stack[walker]; +} + +i32 ovm_code_builder_push_label_target(ovm_code_builder_t *builder, label_kind_t kind) { + label_target_t target; + target.kind = kind; + target.idx = builder->next_label_idx++; + target.instr = -1; + + if (kind == label_kind_loop) { + target.instr = bh_arr_length(builder->program->code); + } + + bh_arr_push(builder->label_stack, target); + + return target.idx; +} + +void ovm_code_builder_pop_label_target(ovm_code_builder_t *builder) { + label_target_t target = bh_arr_pop(builder->label_stack); + if (target.instr == -1) { + target.instr = bh_arr_length(builder->program->code); + } + + fori (i, 0, bh_arr_length(builder->branch_patches)) { + branch_patch_t patch = builder->branch_patches[i]; + if (patch.label_idx != target.idx) continue; + + int br_delta = target.instr - patch.branch_instr - 1; + + switch (patch.kind) { + case branch_patch_instr_a: + builder->program->code[patch.branch_instr].a = br_delta; + break; + + case branch_patch_static_idx: + ovm_program_modify_static_int(builder->program, patch.static_arr, patch.static_idx, br_delta); + break; + } + + bh_arr_fastdelete(builder->branch_patches, i); + i--; + } +} + +void ovm_code_builder_patch_else(ovm_code_builder_t *builder, label_target_t if_target) { + assert(if_target.kind == label_kind_if); + + fori (i, 0, bh_arr_length(builder->branch_patches)) { + branch_patch_t patch = builder->branch_patches[i]; + if (patch.label_idx != if_target.idx) continue; + if (!patch.targets_else) continue; + + int br_delta = bh_arr_length(builder->program->code) - patch.branch_instr - 1; + assert(patch.kind == branch_patch_instr_a); + + builder->program->code[patch.branch_instr].a = br_delta; + + bh_arr_fastdelete(builder->branch_patches, i); + return; + } +} + +void ovm_code_builder_add_nop(ovm_code_builder_t *builder) { + ovm_instr_t nop = {0}; + nop.full_instr = OVMI_NOP; + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &nop); +} + +void ovm_code_builder_add_binop(ovm_code_builder_t *builder, u32 instr) { + i32 right = POP_VALUE(builder); + i32 left = POP_VALUE(builder); + i32 result = NEXT_VALUE(builder); + + ovm_instr_t binop; + binop.full_instr = instr; + binop.r = result; + binop.a = left; + binop.b = right; + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &binop); + PUSH_VALUE(builder, result); +} + +void ovm_code_builder_add_imm(ovm_code_builder_t *builder, u32 ovm_type, void *imm) { + ovm_instr_t imm_instr = {0}; + imm_instr.full_instr = OVM_TYPED_INSTR(OVMI_IMM, ovm_type); + imm_instr.r = NEXT_VALUE(builder); + + switch (ovm_type) { + case OVM_TYPE_I8: imm_instr.i = (u32) *(u8 *) imm; break; + case OVM_TYPE_I16: imm_instr.i = (u32) *(u16 *) imm; break; + case OVM_TYPE_I32: imm_instr.i = *(u32 *) imm; break; + case OVM_TYPE_I64: imm_instr.l = *(u64 *) imm; break; + case OVM_TYPE_F32: imm_instr.f = *(f32 *) imm; break; + case OVM_TYPE_F64: imm_instr.d = *(f64 *) imm; break; + default: assert(("bad ovm type for add_imm", 0)); + } + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &imm_instr); + PUSH_VALUE(builder, imm_instr.r); +} + +void ovm_code_builder_add_unop(ovm_code_builder_t *builder, u32 instr) { + i32 operand = POP_VALUE(builder); + + ovm_instr_t unop = {0}; + unop.full_instr = instr; + unop.r = NEXT_VALUE(builder); + unop.a = operand; + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &unop); + PUSH_VALUE(builder, unop.r); +} + +void ovm_code_builder_add_branch(ovm_code_builder_t *builder, i32 label_idx) { + ovm_instr_t branch_instr = {0}; + branch_instr.full_instr = OVMI_BR; + 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; + patch.targets_else = false; + + bh_arr_push(builder->branch_patches, patch); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &branch_instr); +} + +void ovm_code_builder_add_cond_branch(ovm_code_builder_t *builder, i32 label_idx, bool branch_if_true, bool targets_else) { + ovm_instr_t branch_instr = {0}; + if (branch_if_true) { + branch_instr.full_instr = OVMI_BR_NZ; + } else { + branch_instr.full_instr = OVMI_BR_Z; + } + + branch_instr.a = -1; + 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; + patch.targets_else = targets_else; + + bh_arr_push(builder->branch_patches, patch); + + debug_info_builder_emit_location(builder->debug_builder); + 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 substitute. + int table_idx = ovm_program_register_static_ints(builder->program, count, label_indicies); + + 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_LT, 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; + patch.targets_else = false; + 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; + default_patch.targets_else = false; + bh_arr_push(builder->branch_patches, default_patch); + + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + 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; + + i32 values_on_stack = bh_arr_length(builder->execution_stack); + assert(values_on_stack == 0 || values_on_stack == 1); + + if (values_on_stack == 1) { + instr.a = POP_VALUE(builder); + } + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); +} + +static void ovm_code_builder_add_params(ovm_code_builder_t *builder, i32 param_count) { + i32 *flipped_params = alloca(param_count * sizeof(i32)); + + fori (i, 0, param_count) { + flipped_params[i] = POP_VALUE(builder); + } + + fori (i, 0, param_count) { + ovm_instr_t param_instr = {0}; + param_instr.full_instr = OVMI_PARAM; + param_instr.a = flipped_params[param_count - 1 - i]; + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, ¶m_instr); + } +} + +void ovm_code_builder_add_call(ovm_code_builder_t *builder, i32 func_idx, i32 param_count, bool has_return_value) { + ovm_code_builder_add_params(builder, param_count); + + ovm_instr_t call_instr = {0}; + call_instr.full_instr = OVMI_CALL; + call_instr.a = func_idx; + call_instr.r = -1; + + if (has_return_value) { + call_instr.r = NEXT_VALUE(builder); + } + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &call_instr); + + if (has_return_value) { + PUSH_VALUE(builder, call_instr.r); + } +} + +void ovm_code_builder_add_indirect_call(ovm_code_builder_t *builder, i32 param_count, bool has_return_value) { + ovm_instr_t call_instrs[2] = {0}; + + // idxarr %k, table, %j + call_instrs[0].full_instr = OVMI_IDX_ARR; + call_instrs[0].r = NEXT_VALUE(builder); + call_instrs[0].a = builder->func_table_arr_idx; + call_instrs[0].b = POP_VALUE(builder); + + call_instrs[1].full_instr = OVMI_CALLI; + call_instrs[1].a = call_instrs[0].r; + call_instrs[1].r = -1; + + ovm_code_builder_add_params(builder, param_count); + + if (has_return_value) { + call_instrs[1].r = NEXT_VALUE(builder); + } + + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 2, call_instrs); + + if (has_return_value) { + PUSH_VALUE(builder, call_instrs[1].r); + } +} + +void ovm_code_builder_drop_value(ovm_code_builder_t *builder) { + POP_VALUE(builder); +} + +void ovm_code_builder_add_local_get(ovm_code_builder_t *builder, i32 local_idx) { + ovm_instr_t instr = {0}; + instr.full_instr = OVMI_MOV; + instr.r = NEXT_VALUE(builder); + instr.a = local_idx; // This makes the assumption that the params will be in + // the lower "address space" of the value numbers. This + // will be true for web assembly, because that's how it + // it was spec'd; but in the future for other things, + // this will be incorrect. + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); + + PUSH_VALUE(builder, instr.r); +} + +void ovm_code_builder_add_local_set(ovm_code_builder_t *builder, i32 local_idx) { + ovm_instr_t instr = {0}; + instr.full_instr = OVMI_MOV; + instr.r = local_idx; // This makes the assumption that the params will be in + // the lower "address space" of the value numbers. This + // will be true for web assembly, because that's how it + // it was spec'd; but in the future for other things, + // this will be incorrect. + instr.a = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); +} + +void ovm_code_builder_add_local_tee(ovm_code_builder_t *builder, i32 local_idx) { + ovm_instr_t instr = {0}; + instr.full_instr = OVMI_MOV; + instr.r = local_idx; // This makes the assumption that the params will be in + // the lower "address space" of the value numbers. This + // will be true for web assembly, because that's how it + // it was spec'd; but in the future for other things, + // this will be incorrect. + instr.a = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); + + PUSH_VALUE(builder, instr.a); +} + +void ovm_code_builder_add_register_get(ovm_code_builder_t *builder, i32 reg_idx) { + ovm_instr_t instr = {0}; + instr.full_instr = OVMI_REG_GET; + instr.r = NEXT_VALUE(builder); + instr.a = reg_idx; + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); + + PUSH_VALUE(builder, instr.r); +} + +void ovm_code_builder_add_register_set(ovm_code_builder_t *builder, i32 reg_idx) { + ovm_instr_t instr = {0}; + instr.full_instr = OVMI_REG_SET; + instr.r = reg_idx; + instr.a = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); +} + +void ovm_code_builder_add_load(ovm_code_builder_t *builder, u32 ovm_type, i32 offset) { + ovm_instr_t load_instr = {0}; + load_instr.full_instr = OVM_TYPED_INSTR(OVMI_LOAD, ovm_type); + load_instr.b = offset; + load_instr.a = POP_VALUE(builder); + load_instr.r = NEXT_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &load_instr); + + PUSH_VALUE(builder, load_instr.r); +} + +void ovm_code_builder_add_store(ovm_code_builder_t *builder, u32 ovm_type, i32 offset) { + ovm_instr_t store_instr = {0}; + store_instr.full_instr = OVM_TYPED_INSTR(OVMI_STORE, ovm_type); + store_instr.b = offset; + store_instr.a = POP_VALUE(builder); + store_instr.r = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &store_instr); + return; +} + +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); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &cmpxchg_instr); + + PUSH_VALUE(builder, cmpxchg_instr.r); + return; + } + + // TODO: explain the ordering here. + + ovm_instr_t instrs[3] = {0}; + // 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); + + int value_reg = POP_VALUE(builder); + int expected_reg = POP_VALUE(builder); + int addr_reg = POP_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 = addr_reg; + instrs[1].b = instrs[0].r; + + // 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; + + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + debug_info_builder_emit_location(builder->debug_builder); + 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; + instr.b = POP_VALUE(builder); + instr.a = POP_VALUE(builder); + instr.r = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); +} + +void ovm_code_builder_add_memory_fill(ovm_code_builder_t *builder) { + ovm_instr_t instr = {0}; + instr.full_instr = OVMI_FILL; + instr.b = POP_VALUE(builder); + instr.a = POP_VALUE(builder); + instr.r = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &instr); +} + +// +// CopyNPaste from _add_load +void ovm_code_builder_add_atomic_load(ovm_code_builder_t *builder, u32 ovm_type, i32 offset) { + ovm_instr_t load_instr = {0}; + load_instr.full_instr = OVM_TYPED_INSTR(OVMI_ATOMIC | OVMI_LOAD, ovm_type); + load_instr.b = offset; + load_instr.a = POP_VALUE(builder); + load_instr.r = NEXT_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &load_instr); + + PUSH_VALUE(builder, load_instr.r); +} + +// +// CopyNPaste from _add_store +void ovm_code_builder_add_atomic_store(ovm_code_builder_t *builder, u32 ovm_type, i32 offset) { + ovm_instr_t store_instr = {0}; + store_instr.full_instr = OVM_TYPED_INSTR(OVMI_ATOMIC | OVMI_STORE, ovm_type); + store_instr.b = offset; + store_instr.a = POP_VALUE(builder); + store_instr.r = POP_VALUE(builder); + + debug_info_builder_emit_location(builder->debug_builder); + ovm_program_add_instructions(builder->program, 1, &store_instr); +} diff --git a/interpreter/src/vm/program_loader.c b/interpreter/src/vm/program_loader.c new file mode 100644 index 00000000..f3162d39 --- /dev/null +++ b/interpreter/src/vm/program_loader.c @@ -0,0 +1,126 @@ +#include "vm.h" + +// +// I'm very lazy and silly, so this code make the drastic assumption that the +// endianness of the machine that the file was built on and the machine the +// VM is running on are the SAME. If this is not the case, the integers very +// well could be f-ed up. Might be worth switching all integers to use ntohl, +// so at least that part is consistent... -brendanfh 06/13/2022 +// + +// +// I wish this didn't take the engine as a parameter... but currently the memory +// is stored on the engine. So unless the data section elements can be aggregated +// into an array to be applied later, this is best I got... +// +// ALSO, I wish this didn't have to take a state... but because native_funcs are stored +// on the state, this is also the best I got... +bool ovm_program_load_from_file(ovm_program_t *program, ovm_engine_t *engine, char *filename) { + bh_file file; + bh_file_error error = bh_file_open(&file, filename); + if (error != BH_FILE_ERROR_NONE) { + fprintf(stderr, "Failed to open '%s' for reading.\n", filename); + return false; + } + + char magic[4], version[4]; + bh_file_read(&file, magic, 4); + bh_file_read(&file, version, 4); + + if (strncmp(magic, "OVMI", 4) || strncmp(version, "\x00\x00\x00\x01", 4)) { + fprintf(stderr, "Mismatched version/magic number in '%s'.\n", filename); + return false; + } + + // + // Code section + // Just copy in the bytes directly. + // What's validation anyway? + // + i32 entry_count; + bh_file_read(&file, &entry_count, sizeof(i32)); + bh_arr_insert_end(program->code, entry_count); + bh_file_read(&file, program->code, entry_count * sizeof(ovm_instr_t)); + + // + // Data section + // + bh_file_read(&file, &entry_count, sizeof(i32)); + fori (i, 0, entry_count) { + i32 offset, size; + bh_file_read(&file, &offset, sizeof(i32)); + bh_file_read(&file, &size, sizeof(i32)); + + assert(engine); + assert(engine->memory); + bh_file_read(&file, ((u8 *) engine->memory) + offset, size); + } + + // + // Native link section + // + i32 next_external_func_idx = 0; + bh_file_read(&file, &entry_count, sizeof(i32)); + fori (i, 0, entry_count) { + i32 param_count, name_len; + bh_file_read(&file, ¶m_count, sizeof(i32)); + bh_file_read(&file, &name_len, sizeof(i32)); + + char *name_buf = bh_alloc_array(program->store->arena_allocator, char, name_len); + bh_file_read(&file, name_buf, name_len); + + ovm_program_register_external_func(program, name_buf, param_count, next_external_func_idx++); + } + + // + // Func section + // + bh_file_read(&file, &entry_count, sizeof(i32)); + fori (i, 0, entry_count) { + i32 start_instr, param_count, value_number_count; + bh_file_read(&file, &start_instr, sizeof(i32)); + bh_file_read(&file, ¶m_count, sizeof(i32)); + bh_file_read(&file, &value_number_count, sizeof(i32)); + + ovm_program_register_func(program, "LOADED", start_instr, param_count, value_number_count); + } + + // + // Register section + // + bh_file_read(&file, &entry_count, sizeof(i32)); + fori (i, 0, entry_count) { + i32 param_count, name_len; + bh_file_read(&file, ¶m_count, sizeof(i32)); + bh_file_read(&file, &name_len, sizeof(i32)); + + char *name_buf = bh_alloc_array(program->store->arena_allocator, char, name_len); + bh_file_read(&file, name_buf, name_len); + + // For now, these are just ignored... + } + program->register_count = entry_count; + + return true; +} + +void ovm_state_link_external_funcs(ovm_program_t *program, ovm_state_t *state, ovm_linkable_func_t *funcs) { + bh_arr_each(ovm_func_t, f, program->funcs) { + if (f->kind == OVM_FUNC_INTERNAL) continue; + + ovm_linkable_func_t *func = funcs; + while (func->name) { + if (!strcmp(f->name, func->name) && f->param_count == func->param_count) { + ovm_state_register_external_func(state, f->external_func_idx, func->func.native_func, func->func.userdata); + break; + } + + func++; + } + + if (!state->external_funcs[f->external_func_idx].native_func) { + fprintf(stderr, "Failed to link to native function '%s'.\n", f->name); + exit(1); + } + } +} diff --git a/interpreter/src/vm/vm.c b/interpreter/src/vm/vm.c new file mode 100644 index 00000000..28a42627 --- /dev/null +++ b/interpreter/src/vm/vm.c @@ -0,0 +1,1276 @@ +#include "vm.h" + +#include +#include +#include // REMOVE THIS!!! only needed for sqrt +#include + +#ifdef OVM_DEBUG +#define ovm_assert(c) assert((c)) +#else +#define ovm_assert(c) +#endif + + +static inline void ovm_print_val(ovm_value_t val) { + switch (val.type) { + case OVM_TYPE_I32: printf("i32[%d]", val.i32); break; + case OVM_TYPE_I64: printf("i64[%ld]", val.i64); break; + case OVM_TYPE_F32: printf("f32[%f]", val.f32); break; + case OVM_TYPE_F64: printf("f64[%lf]", val.f64); break; + } +} + + +// +// Store +ovm_store_t *ovm_store_new() { + ovm_store_t *store = malloc(sizeof(*store)); + + store->heap_allocator = bh_heap_allocator(); + + bh_atomic_arena_init(&store->arena, store->heap_allocator, 1 << 20); + store->arena_allocator = bh_atomic_arena_allocator(&store->arena); + + return store; +} + +void ovm_store_delete(ovm_store_t *store) { + bh_atomic_arena_free(&store->arena); + free(store); +} + + +// +// Program +ovm_program_t *ovm_program_new(ovm_store_t *store) { + ovm_program_t *program = bh_alloc_item(store->heap_allocator, ovm_program_t); + program->store = store; + program->register_count = 0; + + program->funcs = NULL; + program->code = NULL; + program->static_integers = NULL; + program->static_data = NULL; + bh_arr_new(store->heap_allocator, program->funcs, 16); + bh_arr_new(store->heap_allocator, program->code, 1024); + bh_arr_new(store->heap_allocator, program->static_integers, 128); + bh_arr_new(store->heap_allocator, program->static_data, 128); + + return program; +} + +void ovm_program_delete(ovm_program_t *program) { + bh_arr_free(program->funcs); + bh_arr_free(program->code); + bh_arr_free(program->static_integers); + bh_arr_free(program->static_data); + + bh_free(program->store->heap_allocator, program); +} + +int ovm_program_register_static_ints(ovm_program_t *program, int len, int *data) { + ovm_static_integer_array_t new_entry; + new_entry.start_idx = bh_arr_length(program->static_integers); + new_entry.len = len; + + fori (i, 0, (int) len) { + bh_arr_push(program->static_integers, data[i]); + } + + bh_arr_push(program->static_data, new_entry); + 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; +} + +int 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; + func.id = bh_arr_length(program->funcs); + func.name = name; + func.start_instr = instr; + func.param_count = param_count; + func.value_number_count = value_number_count; + + bh_arr_push(program->funcs, func); + return func.id; +} + +int ovm_program_register_external_func(ovm_program_t *program, char *name, i32 param_count, i32 external_func_idx) { + ovm_func_t func; + func.kind = OVM_FUNC_EXTERNAL; + func.id = bh_arr_length(program->funcs); + func.name = name; + func.param_count = param_count; + func.external_func_idx = external_func_idx; + func.value_number_count = param_count; + + bh_arr_push(program->funcs, func); + return func.id; +} + +void ovm_program_begin_func(ovm_program_t *program, char *name, i32 param_count, i32 value_number_count) { + ovm_func_t func; + func.id = bh_arr_length(program->funcs); + func.name = name; + func.start_instr = bh_arr_length(program->code); + func.param_count = param_count; + func.value_number_count = value_number_count; + + bh_arr_push(program->funcs, func); +} + +void ovm_program_add_instructions(ovm_program_t *program, i32 instr_count, ovm_instr_t *instrs) { + fori (i, 0, instr_count) { + bh_arr_push(program->code, instrs[i]); + } +} + + +static char *ovm_instr_name(i32 full_instr) { +#define C(...) \ + case __VA_ARGS__: return #__VA_ARGS__; \ + case __VA_ARGS__ | OVMI_ATOMIC: return "ATOMIC_" #__VA_ARGS__; + + static char buf[64]; + + switch (full_instr) { + C(OVMI_NOP) + + C(OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_REM, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_REM, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_REM, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_REM, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_REM_S, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_REM_S, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_REM_S, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_REM_S, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_AND, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_AND, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_AND, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_AND, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_OR, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_OR, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_OR, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_OR, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_XOR, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_XOR, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_XOR, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_XOR, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_SHL, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_SHL, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_SHL, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_SHL, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_SHR, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_SHR, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_SHR, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_SHR, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_SAR, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_SAR, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_SAR, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_SAR, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_IMM, OVM_TYPE_F64)) + + C(OVMI_MOV) + C(OVM_TYPED_INSTR(OVMI_LOAD, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_LOAD, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_LOAD, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_LOAD, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_LOAD, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_LOAD, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_STORE, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_STORE, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_STORE, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_STORE, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_STORE, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_STORE, OVM_TYPE_F64)) + C(OVMI_COPY) + C(OVMI_FILL) + + C(OVMI_REG_GET) + C(OVMI_REG_SET) + + C(OVMI_IDX_ARR) + + C(OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_F64)) + + C(OVMI_PARAM) + C(OVMI_RETURN) + C(OVMI_CALL) + C(OVMI_CALLI) + + C(OVMI_BR) + C(OVMI_BR_Z) + C(OVMI_BR_NZ) + C(OVMI_BRI) + C(OVMI_BRI_Z) + C(OVMI_BRI_NZ) + + C(OVM_TYPED_INSTR(OVMI_CLZ, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_CLZ, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_CLZ, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_CLZ, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_CTZ, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_CTZ, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_CTZ, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_CTZ, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_POPCNT, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_POPCNT, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_POPCNT, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_POPCNT, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_ROTL, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_ROTL, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_ROTL, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_ROTL, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_ROTR, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_ROTR, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_ROTR, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_ROTR, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_ABS, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_NEG, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_CEIL, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_FLOOR, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_TRUNC, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_NEAREST, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_SQRT, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_MIN, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_MAX, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_COPYSIGN, OVM_TYPE_F32)) + + C(OVM_TYPED_INSTR(OVMI_ABS, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_NEG, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_CEIL, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_FLOOR, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_TRUNC, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_NEAREST, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_SQRT, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_MIN, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_MAX, OVM_TYPE_F64)) + C(OVM_TYPED_INSTR(OVMI_COPYSIGN, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_CVT_I8, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_CVT_I8, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_CVT_I8, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_CVT_I16, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_CVT_I16, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_CVT_I16, OVM_TYPE_I64)) + + C(OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I8)) + C(OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I16)) + C(OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F32)) + C(OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I32)) + C(OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I64)) + C(OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_F64)) + + C(OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I32)) + 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; + } +} + +void ovm_program_print_instructions(ovm_program_t *program, i32 start_instr, i32 instr_count) { + fori (i, start_instr, start_instr + instr_count) { + // + // Horribly inefficient way of checking to see if this instruction + // is the start of a function, but for now, it'll do. -brendanfh 06/13/2022 + bh_arr_each(ovm_func_t, func, program->funcs) { + if (i == func->start_instr && func->kind == OVM_FUNC_INTERNAL) { + printf("\n[%d] %s values=%d:\n", func->id, func->name, func->value_number_count); + } + } + + ovm_instr_t instr = program->code[i]; + printf("%6lx %50s | r=%02d a=%02d b=%02d i=%d f=%f l=%ld d=%lf\n", i, ovm_instr_name(instr.full_instr), instr.r, instr.a, instr.b, instr.i, instr.f, instr.l, instr.d); + } +} + +void ovm_raw_print_instructions(i32 instr_count, ovm_instr_t *instrs) { + fori (i, 0, instr_count) { + ovm_instr_t instr = instrs[i]; + printf("%6lx %50s | r=%02d a=%02d b=%02d i=%d f=%f l=%ld d=%lf\n", i, ovm_instr_name(instr.full_instr), instr.r, instr.a, instr.b, instr.i, instr.f, instr.l, instr.d); + } +} + +// +// Engine +ovm_engine_t *ovm_engine_new(ovm_store_t *store) { + ovm_engine_t *engine = bh_alloc_item(store->heap_allocator, ovm_engine_t); + + engine->store = store; + engine->memory_size = 1ull << 32; + engine->memory = mmap(NULL, engine->memory_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + pthread_mutex_init(&engine->atomic_mutex, NULL); + + engine->debug = NULL; + + return engine; +} + +void ovm_engine_delete(ovm_engine_t *engine) { + ovm_store_t *store = engine->store; + + munmap(engine->memory, engine->memory_size); + bh_free(store->heap_allocator, engine); +} + +void ovm_engine_memory_copy(ovm_engine_t *engine, i64 target, void *data, i64 size) { + ovm_assert(engine); + ovm_assert(engine->memory); + ovm_assert(data); + ovm_assert(size + target < engine->memory_size); + memcpy(((u8 *) engine->memory) + target, data, size); +} + + + +// +// State +// +// This takes in a program because it needs to know how many registers to allocate. +// Should there be another mechanism for this? or is this the most concise way? +ovm_state_t *ovm_state_new(ovm_engine_t *engine, ovm_program_t *program) { + ovm_store_t *store = engine->store; + ovm_state_t *state = bh_alloc_item(store->arena_allocator, ovm_state_t); + + state->store = store; + state->pc = 0; + state->value_number_offset = 0; + + state->numbered_values = NULL; + state->params = NULL; + state->stack_frames = NULL; + state->registers = NULL; + bh_arr_new(store->heap_allocator, state->numbered_values, 128); + bh_arr_new(store->heap_allocator, state->params, 16); + bh_arr_new(store->heap_allocator, state->stack_frames, 32); + bh_arr_new(store->heap_allocator, state->registers, program->register_count); + bh_arr_insert_end(state->registers, program->register_count); + + state->external_funcs = NULL; + bh_arr_new(store->heap_allocator, state->external_funcs, 8); + + if (engine->debug) { + u32 thread_id = debug_host_register_thread(engine->debug, state); + state->debug = debug_host_lookup_thread(engine->debug, thread_id); + } + +#ifdef OVM_VERBOSE + ovm_program_print_instructions(program, 0, bh_arr_length(program->code)); +#endif + + return state; +} + +void ovm_state_delete(ovm_state_t *state) { + ovm_store_t *store = state->store; + + bh_arr_free(state->numbered_values); + bh_arr_free(state->params); + bh_arr_free(state->stack_frames); + bh_arr_free(state->registers); + bh_arr_free(state->external_funcs); +} + +void ovm_state_register_external_func(ovm_state_t *state, i32 idx, void (*func)(void *, ovm_value_t *, ovm_value_t *), void *data) { + ovm_external_func_t external_func; + external_func.native_func = func; + external_func.userdata = data; + + bh_arr_set_at(state->external_funcs, idx, external_func); +} + +ovm_value_t ovm_state_register_get(ovm_state_t *state, i32 idx) { + ovm_assert(idx < bh_arr_length(state->registers)); + + return state->registers[idx]; +} + +void ovm_state_register_set(ovm_state_t *state, i32 idx, ovm_value_t val) { + if (idx >= bh_arr_length(state->registers)) return; + + state->registers[idx] = val; +} + +// +// Function calling + +static inline void ovm__func_setup_stack_frame(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t *program, i32 func_idx, i32 result_number) { + ovm_func_t *func = &program->funcs[func_idx]; + + // + // Push a stack frame + ovm_stack_frame_t frame; + frame.func = func; + frame.value_number_count = func->value_number_count; + frame.value_number_base = bh_arr_length(state->numbered_values); + frame.return_address = state->pc; + frame.return_number_value = result_number; + bh_arr_push(state->stack_frames, frame); + + // + // Move the base pointer to the value numbers. + state->value_number_offset = frame.value_number_base; + + // + // Setup value numbers + bh_arr_insert_end(state->numbered_values, func->value_number_count); + + // + // Modify debug state so step over works + if (state->debug) { + state->debug->extra_frames_since_last_pause++; + } +} + +static inline ovm_stack_frame_t ovm__func_teardown_stack_frame(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t *program) { + ovm_stack_frame_t frame = bh_arr_pop(state->stack_frames); + bh_arr_fastdeleten(state->numbered_values, frame.value_number_count); + + if (bh_arr_length(state->stack_frames) == 0) { + state->value_number_offset = 0; + } else { + state->value_number_offset = bh_arr_last(state->stack_frames).value_number_base; + } + + if (state->debug) { + state->debug->extra_frames_since_last_pause--; + if (state->debug->extra_frames_since_last_pause < 0) { + state->debug->pause_within = -1; + } + } + + return frame; +} + +ovm_value_t ovm_func_call(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t *program, i32 func_idx, i32 param_count, ovm_value_t *params) { + ovm_func_t func = program->funcs[func_idx]; + ovm_assert(func.value_number_count >= func.param_count); + + switch (func.kind) { + case OVM_FUNC_INTERNAL: { + ovm__func_setup_stack_frame(engine, state, program, func_idx, 0); + + fori (i, 0, param_count) { + state->numbered_values[i + state->value_number_offset] = params[i]; + } + + state->pc = func.start_instr; + ovm_value_t result = ovm_run_code(engine, state, program); + + return result; + } + + case OVM_FUNC_EXTERNAL: { + ovm__func_setup_stack_frame(engine, state, program, func_idx, 0); + + ovm_value_t result = {0}; + ovm_external_func_t external_func = state->external_funcs[func.external_func_idx]; + external_func.native_func(external_func.userdata, params, &result); + + ovm__func_teardown_stack_frame(engine, state, program); + return result; + } + + default: return (ovm_value_t) {}; + } +} + +static inline double __ovm_abs(double f) { + return f >= 0 ? f : -f; +} + +static inline double __ovm_floor(double f) { + if (f < 0) { + return (double) (((long long) f) - 1); + } else { + return (double) (long long) f; + } +} + +static inline double __ovm_ceil(double f) { + if (f - (long long) f == 0) { + return (double) (long long) f; + } else { + return __ovm_floor(f) + 1; + } +} + +static inline double __ovm_trunc(double f) { + return (double) (long long) f; +} + +static inline double __ovm_nearest(double f) { + if (f > 0 && f <= 0.5) { + return +0; + } + + if (f >= -0.5 && f < 0) { + return -0; + } + + if (f - __ovm_floor(f) < 0.5) return __ovm_floor(f); + else return __ovm_ceil(f); +} + +static inline double __ovm_copysign(a, b) double a, b; { + if ((a > 0 && b > 0) || (a < 0 && b < 0)) return a; + return -a; +} + + +ovm_value_t ovm_run_code(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t *program) { + ovm_assert(engine); + ovm_assert(state); + ovm_assert(program); + +#define VAL(loc) state->numbered_values[(u32) (loc + state->value_number_offset)] + + ovm_instr_t *code = program->code; + bool release_mutex_at_end = false; + + ovm_value_t tmp_val; + + while (state->pc < bh_arr_length(program->code)) { + // This will become the line change detection + // debug_loc_info_t loc_info; + // debug_info_lookup_location(engine->debug->info, state->pc, &loc_info); + // if (loc_info.file_id != last_file || loc_info.line != last_line) { + // last_file = loc_info.file_id; + // last_line = loc_info.line; + + // debug_file_info_t file_info; + // debug_info_lookup_file(engine->debug->info, last_file, &file_info); + // + // printf("(%d, %d) %s:%d\n", last_file, last_line, file_info.name, last_line); + // } + +#ifdef OVM_VERBOSE + ovm_program_print_instructions(program, state->pc, 1); +#endif + + tmp_val.type = OVM_TYPE_NONE; + tmp_val.u64 = 0; + + // Check if breakpoints are hit + if (state->debug) { + if (state->debug->run_count == 0) { + state->debug->state = debug_state_pausing; + state->debug->pause_reason = debug_pause_entry; // This is not always due to entry... + goto should_wait; + } + + if (state->debug->pause_at_next_line) { + if (state->debug->pause_within == -1 || state->debug->pause_within == bh_arr_last(state->stack_frames).func->id) { + + debug_loc_info_t l1, l2; + debug_info_lookup_location(engine->debug->info, state->pc - 1, &l1); + debug_info_lookup_location(engine->debug->info, state->pc, &l2); + + if (l1.file_id != l2.file_id || l1.line != l2.line) { + state->debug->pause_at_next_line = false; + state->debug->pause_reason = debug_pause_step; + state->debug->state = debug_state_pausing; + goto should_wait; + } + } + } + + bh_arr_each(debug_breakpoint_t, bp, state->debug->breakpoints) { + if (bp->instr == (u32) state->pc) { + state->debug->state = debug_state_hit_breakpoint; + state->debug->last_breakpoint_hit = bp->id; + goto should_wait; + } + } + + goto shouldnt_wait; + + should_wait: + assert(write(state->debug->state_change_write_fd, "1", 1)); + sem_wait(&state->debug->wait_semaphore); + state->debug->state = debug_state_running; + + shouldnt_wait: + if (state->debug->run_count > 0) state->debug->run_count--; + } + + // + // Incrementing the program counter here. + // All instructions that compute something relative + // to the program counter have to know that the program + // counter will refer to the instruction AFTER the one + // 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; + +#define OVM_OP(i, t, op, ctype) \ + case OVM_TYPED_INSTR(i, t): \ + ovm_assert(VAL(instr.a).type == t && VAL(instr.b).type == t); \ + tmp_val.type = t; \ + tmp_val.ctype = VAL(instr.a).ctype op VAL(instr.b).ctype; \ + VAL(instr.r) = tmp_val; \ + break; + + OVM_OP(OVMI_ADD, OVM_TYPE_I8 , +, i8) + OVM_OP(OVMI_ADD, OVM_TYPE_I16, +, i16) + OVM_OP(OVMI_ADD, OVM_TYPE_I32, +, i32) + OVM_OP(OVMI_ADD, OVM_TYPE_I64, +, i64) + OVM_OP(OVMI_ADD, OVM_TYPE_F32, +, f32) + OVM_OP(OVMI_ADD, OVM_TYPE_F64, +, f64) + + OVM_OP(OVMI_SUB, OVM_TYPE_I8 , -, i8) + OVM_OP(OVMI_SUB, OVM_TYPE_I16, -, i16) + OVM_OP(OVMI_SUB, OVM_TYPE_I32, -, i32) + OVM_OP(OVMI_SUB, OVM_TYPE_I64, -, i64) + OVM_OP(OVMI_SUB, OVM_TYPE_F32, -, f32) + OVM_OP(OVMI_SUB, OVM_TYPE_F64, -, f64) + + OVM_OP(OVMI_MUL, OVM_TYPE_I8 , *, i8) + OVM_OP(OVMI_MUL, OVM_TYPE_I16, *, i16) + OVM_OP(OVMI_MUL, OVM_TYPE_I32, *, i32) + OVM_OP(OVMI_MUL, OVM_TYPE_I64, *, i64) + OVM_OP(OVMI_MUL, OVM_TYPE_F32, *, f32) + OVM_OP(OVMI_MUL, OVM_TYPE_F64, *, f64) + + OVM_OP(OVMI_DIV, OVM_TYPE_I8 , /, u8) + OVM_OP(OVMI_DIV, OVM_TYPE_I16, /, u16) + OVM_OP(OVMI_DIV, OVM_TYPE_I32, /, u32) + OVM_OP(OVMI_DIV, OVM_TYPE_I64, /, u64) + OVM_OP(OVMI_DIV, OVM_TYPE_F32, /, f32) + OVM_OP(OVMI_DIV, OVM_TYPE_F64, /, f64) + + OVM_OP(OVMI_DIV_S, OVM_TYPE_I8 , /, i8) + OVM_OP(OVMI_DIV_S, OVM_TYPE_I16, /, i16) + OVM_OP(OVMI_DIV_S, OVM_TYPE_I32, /, i32) + OVM_OP(OVMI_DIV_S, OVM_TYPE_I64, /, i64) + OVM_OP(OVMI_DIV_S, OVM_TYPE_F32, /, f32) + OVM_OP(OVMI_DIV_S, OVM_TYPE_F64, /, f64) + + OVM_OP(OVMI_REM, OVM_TYPE_I8 , %, u8) + OVM_OP(OVMI_REM, OVM_TYPE_I16, %, u16) + OVM_OP(OVMI_REM, OVM_TYPE_I32, %, u32) + OVM_OP(OVMI_REM, OVM_TYPE_I64, %, u64) + + OVM_OP(OVMI_REM_S, OVM_TYPE_I8 , %, i8) + OVM_OP(OVMI_REM_S, OVM_TYPE_I16, %, i16) + OVM_OP(OVMI_REM_S, OVM_TYPE_I32, %, i32) + OVM_OP(OVMI_REM_S, OVM_TYPE_I64, %, i64) + + OVM_OP(OVMI_AND, OVM_TYPE_I8 , &, u8) + OVM_OP(OVMI_AND, OVM_TYPE_I16, &, u16) + OVM_OP(OVMI_AND, OVM_TYPE_I32, &, u32) + OVM_OP(OVMI_AND, OVM_TYPE_I64, &, u64) + + OVM_OP(OVMI_OR, OVM_TYPE_I8 , |, u8) + OVM_OP(OVMI_OR, OVM_TYPE_I16, |, u16) + OVM_OP(OVMI_OR, OVM_TYPE_I32, |, u32) + OVM_OP(OVMI_OR, OVM_TYPE_I64, |, u64) + + OVM_OP(OVMI_XOR, OVM_TYPE_I8 , ^, u8) + OVM_OP(OVMI_XOR, OVM_TYPE_I16, ^, u16) + OVM_OP(OVMI_XOR, OVM_TYPE_I32, ^, u32) + OVM_OP(OVMI_XOR, OVM_TYPE_I64, ^, u64) + + OVM_OP(OVMI_SHL, OVM_TYPE_I8 , <<, u8) + OVM_OP(OVMI_SHL, OVM_TYPE_I16, <<, u16) + OVM_OP(OVMI_SHL, OVM_TYPE_I32, <<, u32) + OVM_OP(OVMI_SHL, OVM_TYPE_I64, <<, u64) + + OVM_OP(OVMI_SHR, OVM_TYPE_I8 , >>, u8) + OVM_OP(OVMI_SHR, OVM_TYPE_I16, >>, u16) + OVM_OP(OVMI_SHR, OVM_TYPE_I32, >>, u32) + OVM_OP(OVMI_SHR, OVM_TYPE_I64, >>, u64) + + OVM_OP(OVMI_SAR, OVM_TYPE_I8 , >>, i8) + OVM_OP(OVMI_SAR, OVM_TYPE_I16, >>, i16) + OVM_OP(OVMI_SAR, OVM_TYPE_I32, >>, i32) + OVM_OP(OVMI_SAR, OVM_TYPE_I64, >>, i64) + +#undef OVM_OP + +#define OVM_OP(i, t, func, ctype) \ + case OVM_TYPED_INSTR(i, t): \ + ovm_assert(VAL(instr.a).type == t && VAL(instr.b).type == t); \ + tmp_val.type = t; \ + tmp_val.ctype = func( VAL(instr.a).ctype, VAL(instr.b).ctype ); \ + VAL(instr.r) = tmp_val; \ + break; + + OVM_OP(OVMI_ROTL, OVM_TYPE_I8 , __rolb, u8) + OVM_OP(OVMI_ROTL, OVM_TYPE_I16, __rolw, u16) + OVM_OP(OVMI_ROTL, OVM_TYPE_I32, __rold, u32) + OVM_OP(OVMI_ROTL, OVM_TYPE_I64, __rolq, u64) + + OVM_OP(OVMI_ROTR, OVM_TYPE_I8 , __rorb, u8) + OVM_OP(OVMI_ROTR, OVM_TYPE_I16, __rorw, u16) + OVM_OP(OVMI_ROTR, OVM_TYPE_I32, __rord, u32) + OVM_OP(OVMI_ROTR, OVM_TYPE_I64, __rorq, u64) + + OVM_OP(OVMI_MIN, OVM_TYPE_F32, bh_min, f32) + OVM_OP(OVMI_MAX, OVM_TYPE_F32, bh_max, f32) + + OVM_OP(OVMI_MIN, OVM_TYPE_F64, bh_min, f64) + OVM_OP(OVMI_MAX, OVM_TYPE_F64, bh_max, f64) + +#undef OVM_OP + +#define OVM_OP(i, t, op, ctype) \ + case OVM_TYPED_INSTR(i, t): \ + ovm_assert(VAL(instr.a).type == t); \ + tmp_val.type = t; \ + tmp_val.ctype = (ctype) op (VAL(instr.a).ctype); \ + VAL(instr.r) = tmp_val; \ + break; + + OVM_OP(OVMI_CLZ, OVM_TYPE_I8 , __builtin_clz, u8) + OVM_OP(OVMI_CLZ, OVM_TYPE_I16, __builtin_clz, u16) + OVM_OP(OVMI_CLZ, OVM_TYPE_I32, __builtin_clz, u32) + OVM_OP(OVMI_CLZ, OVM_TYPE_I64, __builtin_clz, u64) + + OVM_OP(OVMI_CTZ, OVM_TYPE_I8 , __builtin_ctz, u8) + OVM_OP(OVMI_CTZ, OVM_TYPE_I16, __builtin_ctz, u16) + OVM_OP(OVMI_CTZ, OVM_TYPE_I32, __builtin_ctz, u32) + OVM_OP(OVMI_CTZ, OVM_TYPE_I64, __builtin_ctz, u64) + + OVM_OP(OVMI_POPCNT, OVM_TYPE_I8 , __builtin_popcount, u8) + OVM_OP(OVMI_POPCNT, OVM_TYPE_I16, __builtin_popcount, u16) + OVM_OP(OVMI_POPCNT, OVM_TYPE_I32, __builtin_popcount, u32) + OVM_OP(OVMI_POPCNT, OVM_TYPE_I64, __builtin_popcount, u64) + + OVM_OP(OVMI_ABS, OVM_TYPE_F32, __ovm_abs, f32); + OVM_OP(OVMI_NEG, OVM_TYPE_F32, -, f32); + OVM_OP(OVMI_CEIL, OVM_TYPE_F32, __ovm_ceil, f32); + OVM_OP(OVMI_FLOOR, OVM_TYPE_F32, __ovm_floor, f32); + OVM_OP(OVMI_TRUNC, OVM_TYPE_F32, __ovm_trunc, f32); + OVM_OP(OVMI_NEAREST, OVM_TYPE_F32, __ovm_nearest, f32); + OVM_OP(OVMI_SQRT, OVM_TYPE_F32, sqrt, f32); // TODO: REMOVE THE NEED FOR libm!!! + + OVM_OP(OVMI_ABS, OVM_TYPE_F64, __ovm_abs, f64); + OVM_OP(OVMI_NEG, OVM_TYPE_F64, -, f64); + OVM_OP(OVMI_CEIL, OVM_TYPE_F64, __ovm_ceil, f64); + OVM_OP(OVMI_FLOOR, OVM_TYPE_F64, __ovm_floor, f64); + OVM_OP(OVMI_TRUNC, OVM_TYPE_F64, __ovm_trunc, f64); + OVM_OP(OVMI_NEAREST, OVM_TYPE_F64, __ovm_nearest, f64); + OVM_OP(OVMI_SQRT, OVM_TYPE_F64, sqrt, f64); // TODO: REMOVE THE NEED FOR libm!!! + +#undef OVM_OP + +#define OVM_OP(i, t, op, ctype, cast_type) \ + case OVM_TYPED_INSTR(i, t): \ + ovm_assert(VAL(instr.a).type == t && VAL(instr.b).type == t); \ + tmp_val.type = OVM_TYPE_I32; \ + tmp_val.i32 = ((VAL(instr.a).ctype op VAL(instr.b).ctype)) ? 1 : 0; \ + VAL(instr.r) = tmp_val; \ + break; + + OVM_OP(OVMI_LT, OVM_TYPE_I8 , <, u8, u8) + OVM_OP(OVMI_LT, OVM_TYPE_I16, <, u16, u16) + OVM_OP(OVMI_LT, OVM_TYPE_I32, <, u32, u32) + OVM_OP(OVMI_LT, OVM_TYPE_I64, <, u64, u64) + OVM_OP(OVMI_LT, OVM_TYPE_F32, <, f32, f32) + OVM_OP(OVMI_LT, OVM_TYPE_F64, <, f64, f32) + + OVM_OP(OVMI_LT_S, OVM_TYPE_I8 , <, i8, i8) + OVM_OP(OVMI_LT_S, OVM_TYPE_I16, <, i16, i16) + OVM_OP(OVMI_LT_S, OVM_TYPE_I32, <, i32, i32) + OVM_OP(OVMI_LT_S, OVM_TYPE_I64, <, i64, i64) + OVM_OP(OVMI_LT_S, OVM_TYPE_F32, <, f32, f32) + OVM_OP(OVMI_LT_S, OVM_TYPE_F64, <, f64, f32) + + OVM_OP(OVMI_LE, OVM_TYPE_I8 , <=, u8, u8) + OVM_OP(OVMI_LE, OVM_TYPE_I16, <=, u16, u16) + OVM_OP(OVMI_LE, OVM_TYPE_I32, <=, u32, u32) + OVM_OP(OVMI_LE, OVM_TYPE_I64, <=, u64, u64) + OVM_OP(OVMI_LE, OVM_TYPE_F32, <=, f32, f32) + OVM_OP(OVMI_LE, OVM_TYPE_F64, <=, f64, f64) + + OVM_OP(OVMI_LE_S, OVM_TYPE_I8 , <=, i8, i8) + OVM_OP(OVMI_LE_S, OVM_TYPE_I16, <=, i16, i16) + OVM_OP(OVMI_LE_S, OVM_TYPE_I32, <=, i32, i32) + OVM_OP(OVMI_LE_S, OVM_TYPE_I64, <=, i64, i64) + OVM_OP(OVMI_LE_S, OVM_TYPE_F32, <=, f32, f32) + OVM_OP(OVMI_LE_S, OVM_TYPE_F64, <=, f64, f64) + + OVM_OP(OVMI_EQ, OVM_TYPE_I8 , ==, i8, i8) + OVM_OP(OVMI_EQ, OVM_TYPE_I16, ==, i16, i16) + OVM_OP(OVMI_EQ, OVM_TYPE_I32, ==, i32, i32) + OVM_OP(OVMI_EQ, OVM_TYPE_I64, ==, i64, i64) + OVM_OP(OVMI_EQ, OVM_TYPE_F32, ==, f32, f32) + OVM_OP(OVMI_EQ, OVM_TYPE_F64, ==, f64, f64) + + OVM_OP(OVMI_GE, OVM_TYPE_I8 , >=, u8, u8) + OVM_OP(OVMI_GE, OVM_TYPE_I16, >=, u16, u16) + OVM_OP(OVMI_GE, OVM_TYPE_I32, >=, u32, u32) + OVM_OP(OVMI_GE, OVM_TYPE_I64, >=, u64, u64) + OVM_OP(OVMI_GE, OVM_TYPE_F32, >=, f32, f32) + OVM_OP(OVMI_GE, OVM_TYPE_F64, >=, f64, f64) + + OVM_OP(OVMI_GE_S, OVM_TYPE_I8 , >=, i8, i8) + OVM_OP(OVMI_GE_S, OVM_TYPE_I16, >=, i16, i16) + OVM_OP(OVMI_GE_S, OVM_TYPE_I32, >=, i32, i32) + OVM_OP(OVMI_GE_S, OVM_TYPE_I64, >=, i64, i64) + OVM_OP(OVMI_GE_S, OVM_TYPE_F32, >=, f32, f32) + OVM_OP(OVMI_GE_S, OVM_TYPE_F64, >=, f64, f64) + + OVM_OP(OVMI_GT, OVM_TYPE_I8 , >, u8, u8) + OVM_OP(OVMI_GT, OVM_TYPE_I16, >, u16, u16) + OVM_OP(OVMI_GT, OVM_TYPE_I32, >, u32, u32) + OVM_OP(OVMI_GT, OVM_TYPE_I64, >, u64, u64) + OVM_OP(OVMI_GT, OVM_TYPE_F32, >, f32, f32) + OVM_OP(OVMI_GT, OVM_TYPE_F64, >, f64, f64) + + OVM_OP(OVMI_GT_S, OVM_TYPE_I8 , >, i8, i8) + OVM_OP(OVMI_GT_S, OVM_TYPE_I16, >, i16, i16) + OVM_OP(OVMI_GT_S, OVM_TYPE_I32, >, i32, i32) + OVM_OP(OVMI_GT_S, OVM_TYPE_I64, >, i64, i64) + OVM_OP(OVMI_GT_S, OVM_TYPE_F32, >, f32, f32) + OVM_OP(OVMI_GT_S, OVM_TYPE_F64, >, f64, f64) + + OVM_OP(OVMI_NE, OVM_TYPE_I8 , !=, i8, i8) + OVM_OP(OVMI_NE, OVM_TYPE_I16, !=, i16, i16) + OVM_OP(OVMI_NE, OVM_TYPE_I32, !=, i32, i32) + OVM_OP(OVMI_NE, OVM_TYPE_I64, !=, i64, i64) + OVM_OP(OVMI_NE, OVM_TYPE_F32, !=, f32, f32) + OVM_OP(OVMI_NE, OVM_TYPE_F64, !=, f64, f64) + +#undef OVM_OP + +#define OVM_IMM(t, dtype, stype) \ + case OVM_TYPED_INSTR(OVMI_IMM, t): \ + VAL(instr.r).type = t; \ + VAL(instr.r).u64 = 0; \ + VAL(instr.r).dtype = instr.stype; \ + break; + + OVM_IMM(OVM_TYPE_I8, i8, i) + OVM_IMM(OVM_TYPE_I16, i16, i) + OVM_IMM(OVM_TYPE_I32, i32, i) + OVM_IMM(OVM_TYPE_I64, i64, l) + OVM_IMM(OVM_TYPE_F32, f32, f) + OVM_IMM(OVM_TYPE_F64, f64, d) + +#undef OVM_IMM + + case OVMI_MOV: + VAL(instr.r) = VAL(instr.a); + +#ifdef OVM_VERBOSE + printf("$%d = %lx\n", instr.r, VAL(instr.r).u64); +#endif + + break; + +#define OVM_LOAD(type_, stype) \ + case OVM_TYPED_INSTR(OVMI_LOAD, type_): {\ + ovm_assert(VAL(instr.a).type == OVM_TYPE_I32); \ + tmp_val.type = type_; \ + tmp_val.stype = * (stype *) &((u8 *) engine->memory)[VAL(instr.a).u32 + (u32) instr.b]; \ + VAL(instr.r) = tmp_val; \ + break; \ + } + + OVM_LOAD(OVM_TYPE_I8, i8) + OVM_LOAD(OVM_TYPE_I16, i16) + OVM_LOAD(OVM_TYPE_I32, i32) + OVM_LOAD(OVM_TYPE_I64, i64) + OVM_LOAD(OVM_TYPE_F32, f32) + OVM_LOAD(OVM_TYPE_F64, f64) + +#undef OVM_LOAD + +#define OVM_STORE(type_, stype) \ + case OVM_TYPED_INSTR(OVMI_STORE, type_): \ + ovm_assert(VAL(instr.r).type == OVM_TYPE_I32); \ + *(stype *) &((u8 *) engine->memory)[VAL(instr.r).u32 + (u32) instr.b] = VAL(instr.a).stype; \ + break; + + OVM_STORE(OVM_TYPE_I8, i8) + OVM_STORE(OVM_TYPE_I16, i16) + OVM_STORE(OVM_TYPE_I32, i32) + OVM_STORE(OVM_TYPE_I64, i64) + OVM_STORE(OVM_TYPE_F32, f32) + OVM_STORE(OVM_TYPE_F64, f64) + +#undef OVM_STORE + + case OVMI_COPY: { + u32 dest = VAL(instr.r).u32; + u32 src = VAL(instr.a).u32; + u32 count = VAL(instr.b).u32; + + u8 *base = engine->memory; + memmove(&base[dest], &base[src], count); + break; + } + + case OVMI_FILL: { + i32 dest = VAL(instr.r).i32; + u8 byte = VAL(instr.a).u8; + i32 count = VAL(instr.b).i32; + + u8 *base = engine->memory; + memset(&base[dest], byte, count); + break; + } + + case OVMI_REG_GET: { + VAL(instr.r) = state->registers[instr.a]; + break; + } + + case OVMI_REG_SET: { + state->registers[instr.r] = VAL(instr.a); + break; + } + + case OVMI_IDX_ARR: { + ovm_static_integer_array_t data_elem = program->static_data[instr.a]; + ovm_assert(VAL(instr.b).u32 < (u32) data_elem.len); + + tmp_val.type = OVM_TYPE_I32; + tmp_val.i32 = program->static_integers[data_elem.start_idx + VAL(instr.b).u32]; + VAL(instr.r) = tmp_val; + break; + } + + case OVMI_PARAM: + bh_arr_push(state->params, VAL(instr.a)); + break; + + case OVMI_RETURN: { + ovm_value_t val = VAL(instr.a); + ovm_stack_frame_t frame = ovm__func_teardown_stack_frame(engine, state, program); + state->pc = frame.return_address; + + if (bh_arr_length(state->stack_frames) == 0) { + return val; + } + + ovm_func_t *new_func = bh_arr_last(state->stack_frames).func; + if (new_func->kind == OVM_FUNC_EXTERNAL) { + return val; + } + + if (frame.return_number_value >= 0) { + VAL(frame.return_number_value) = val; + } + +#ifdef OVM_VERBOSE + printf("Returning from %s to %s: ", frame.func->name, bh_arr_last(state->stack_frames).func->name); + ovm_print_val(val); + printf("\n\n"); +#endif + + break; + } + +#define OVM_CALL_CODE(func_idx) \ + i32 fidx = func_idx; \ + ovm_func_t *func = &program->funcs[fidx]; \ + i32 extra_params = bh_arr_length(state->params) - func->param_count; \ + ovm_assert(extra_params >= 0); \ + ovm__func_setup_stack_frame(engine, state, program, fidx, instr.r); \ + if (func->kind == OVM_FUNC_INTERNAL) { \ + fori (i, 0, func->param_count) { \ + VAL(i) = state->params[i + extra_params]; \ + } \ + bh_arr_fastdeleten(state->params, func->param_count); \ + \ + state->pc = func->start_instr; \ + } else { \ + ovm_value_t result = {0}; \ + ovm_external_func_t external_func = state->external_funcs[func->external_func_idx]; \ + external_func.native_func(external_func.userdata, &state->params[extra_params], &result); \ + bh_arr_fastdeleten(state->params, func->param_count); \ + \ + ovm__func_teardown_stack_frame(engine, state, program); \ + \ + if (instr.r >= 0) { \ + VAL(instr.r) = result; \ + } \ + } + + case OVMI_CALL: { + OVM_CALL_CODE(instr.a); + break; + } + + case OVMI_CALLI: { + OVM_CALL_CODE(VAL(instr.a).i32); + break; + } + +#undef OVM_CALL_CODE + + case OVMI_BR: state->pc += instr.a; break; + case OVMI_BRI: state->pc += VAL(instr.a).i32; break; + case OVMI_BR_NZ: if (VAL(instr.b).i32 != 0) state->pc += instr.a; break; + case OVMI_BRI_NZ: if (VAL(instr.b).i32 != 0) state->pc += VAL(instr.a).i32; break; + case OVMI_BR_Z: if (VAL(instr.b).i32 == 0) state->pc += instr.a; break; + case OVMI_BRI_Z: if (VAL(instr.b).i32 == 0) state->pc += VAL(instr.a).i32; break; + + +#define CVT(stype, dtype, otype, ctype) \ + tmp_val.type = otype; \ + tmp_val.dtype = (ctype) VAL(instr.a).stype; \ + VAL(instr.r) = tmp_val; \ + break + + case OVM_TYPED_INSTR(OVMI_CVT_I8, OVM_TYPE_I16): CVT(u8, u16, OVM_TYPE_I16, u16); + case OVM_TYPED_INSTR(OVMI_CVT_I8, OVM_TYPE_I32): CVT(u8, u32, OVM_TYPE_I32, u32); + case OVM_TYPED_INSTR(OVMI_CVT_I8, OVM_TYPE_I64): CVT(u8, u64, OVM_TYPE_I64, u64); + case OVM_TYPED_INSTR(OVMI_CVT_I8_S, OVM_TYPE_I16): CVT(i8, i16, OVM_TYPE_I16, i16); + case OVM_TYPED_INSTR(OVMI_CVT_I8_S, OVM_TYPE_I32): CVT(i8, i32, OVM_TYPE_I32, i32); + case OVM_TYPED_INSTR(OVMI_CVT_I8_S, OVM_TYPE_I64): CVT(i8, i64, OVM_TYPE_I64, i64); + + case OVM_TYPED_INSTR(OVMI_CVT_I16, OVM_TYPE_I8): CVT(u16, u8, OVM_TYPE_I8, u8); + case OVM_TYPED_INSTR(OVMI_CVT_I16, OVM_TYPE_I32): CVT(u16, u32, OVM_TYPE_I32, u32); + case OVM_TYPED_INSTR(OVMI_CVT_I16, OVM_TYPE_I64): CVT(u16, u64, OVM_TYPE_I64, u64); + case OVM_TYPED_INSTR(OVMI_CVT_I16_S, OVM_TYPE_I8): CVT(i16, i8, OVM_TYPE_I8, i8); + case OVM_TYPED_INSTR(OVMI_CVT_I16_S, OVM_TYPE_I32): CVT(i16, i32, OVM_TYPE_I32, i32); + case OVM_TYPED_INSTR(OVMI_CVT_I16_S, OVM_TYPE_I64): CVT(i16, i64, OVM_TYPE_I64, i64); + + case OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I8): CVT(u32, u8, OVM_TYPE_I8, u8); + case OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I16): CVT(u32, u16, OVM_TYPE_I16, u16); + case OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I64): CVT(u32, u64, OVM_TYPE_I64, u64); + case OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_I8): CVT(i32, i8, OVM_TYPE_I8, i8); + case OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_I16): CVT(i32, i16, OVM_TYPE_I16, i16); + case OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_I64): CVT(i32, i64, OVM_TYPE_I64, i64); + + case OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_F32): CVT(u32, f32, OVM_TYPE_F32, f32); + case OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_F32): CVT(i32, f32, OVM_TYPE_F32, f32); + case OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_F64): CVT(u32, f64, OVM_TYPE_F64, f64); + case OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_F64): CVT(i32, f64, OVM_TYPE_F64, f64); + + case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I8): CVT(u64, u8, OVM_TYPE_I8, u8); + case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I16): CVT(u64, u16, OVM_TYPE_I16, u16); + case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I32): CVT(u64, u32, OVM_TYPE_I32, u32); + case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_I8): CVT(i64, i8, OVM_TYPE_I8, i8); + case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_I16): CVT(i64, i16, OVM_TYPE_I16, i16); + case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_I32): CVT(i64, i32, OVM_TYPE_I32, i32); + + case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F32): CVT(u64, f32, OVM_TYPE_F32, f32); + case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_F32): CVT(i64, f32, OVM_TYPE_F32, f32); + case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F64): CVT(u64, f64, OVM_TYPE_F64, f64); + case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_F64): CVT(i64, f64, OVM_TYPE_F64, f64); + + case OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I32): CVT(f32, u32, OVM_TYPE_I32, u32); + case OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I64): CVT(f32, u64, OVM_TYPE_I64, u64); + case OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_F64): CVT(f32, f64, OVM_TYPE_F64, f64); + case OVM_TYPED_INSTR(OVMI_CVT_F32_S, OVM_TYPE_I32): CVT(f32, i32, OVM_TYPE_I32, i32); + case OVM_TYPED_INSTR(OVMI_CVT_F32_S, OVM_TYPE_I64): CVT(f32, i64, OVM_TYPE_I64, i64); + case OVM_TYPED_INSTR(OVMI_CVT_F32_S, OVM_TYPE_F64): CVT(f32, f64, OVM_TYPE_F64, f64); + + case OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I32): CVT(f64, u32, OVM_TYPE_I32, u32); + case OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I64): CVT(f64, u64, OVM_TYPE_I64, u64); + case OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_F32): CVT(f64, f32, OVM_TYPE_F32, f32); + case OVM_TYPED_INSTR(OVMI_CVT_F64_S, OVM_TYPE_I32): CVT(f64, i32, OVM_TYPE_I32, i32); + case OVM_TYPED_INSTR(OVMI_CVT_F64_S, OVM_TYPE_I64): CVT(f64, i64, OVM_TYPE_I64, i64); + case OVM_TYPED_INSTR(OVMI_CVT_F64_S, OVM_TYPE_F32): CVT(f64, f32, OVM_TYPE_F32, f32); + +#undef CVT + +#define CVT(stype, dtype, otype, ctype) \ + tmp_val.type = otype; \ + tmp_val.dtype = *(ctype *) &VAL(instr.a).stype; \ + VAL(instr.r) = tmp_val; \ + break + + case OVM_TYPED_INSTR(OVMI_TRANSMUTE_I32, OVM_TYPE_F32): CVT(u32, f32, OVM_TYPE_F32, f32); + case OVM_TYPED_INSTR(OVMI_TRANSMUTE_I64, OVM_TYPE_F64): CVT(u64, f64, OVM_TYPE_F64, f64); + case OVM_TYPED_INSTR(OVMI_TRANSMUTE_F32, OVM_TYPE_I32): CVT(f32, u32, OVM_TYPE_I32, u32); + case OVM_TYPED_INSTR(OVMI_TRANSMUTE_F64, OVM_TYPE_I64): CVT(f64, u64, OVM_TYPE_I64, u64); + +#undef CVT + + +#define CMPXCHG(otype, ctype) \ + case OVM_TYPED_INSTR(OVMI_CMPXCHG, otype): {\ + ctype *addr = (ctype *) &((u8 *) engine->memory)[VAL(instr.r).u32]; \ + \ + VAL(instr.r).u64 = 0; \ + VAL(instr.r).type = otype; \ + VAL(instr.r).ctype = *addr; \ + \ + if (*addr == VAL(instr.a).ctype) { \ + *addr = 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); + fflush(stdout); + ovm_assert(("ILLEGAL INSTRUCTION", 0)); + break; + } + + if (release_mutex_at_end) { + pthread_mutex_unlock(&engine->atomic_mutex); + release_mutex_at_end = false; + } + } + + return ((ovm_value_t) {0}); +} diff --git a/interpreter/src/wasm.c b/interpreter/src/wasm.c new file mode 100644 index 00000000..6b0da56a --- /dev/null +++ b/interpreter/src/wasm.c @@ -0,0 +1,8 @@ +#define BH_DEFINE +#define BH_NO_TABLE +#define BH_INTERNAL +#define STB_DS_IMPLEMENTATION +#include "bh.h" +#include "stb_ds.h" + +#include "ovm_wasm.h" diff --git a/interpreter/src/wasm/config.c b/interpreter/src/wasm/config.c new file mode 100644 index 00000000..07ea4741 --- /dev/null +++ b/interpreter/src/wasm/config.c @@ -0,0 +1,18 @@ + +#include "ovm_wasm.h" + +wasm_config_t *wasm_config_new() { + wasm_config_t *config = malloc(sizeof(*config)); + config->debug_enabled = false; + return config; +} + +void wasm_config_delete(wasm_config_t *config) { + free(config); +} + +void wasm_config_enable_debug(wasm_config_t *config, bool enabled) { + config->debug_enabled = enabled; +} + + diff --git a/interpreter/src/wasm/engine.c b/interpreter/src/wasm/engine.c new file mode 100644 index 00000000..db4fbeab --- /dev/null +++ b/interpreter/src/wasm/engine.c @@ -0,0 +1,42 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_engine_t *wasm_engine_new() { + wasm_engine_t *engine = wasm_engine_new_with_config(NULL); + return engine; +} + +wasm_engine_t *wasm_engine_new_with_config(wasm_config_t *config) { + ovm_store_t *store = ovm_store_new(); + + wasm_engine_t *engine = bh_alloc_item(store->heap_allocator, wasm_engine_t); + engine->config = config; + engine->store = store; + + ovm_engine_t *ovm_engine = ovm_engine_new(store); + engine->engine = ovm_engine; + + if (config && config->debug_enabled) { + // This should maybe be moved elsewhere? + debug_state_t *debug = bh_alloc_item(store->heap_allocator, debug_state_t); + engine->engine->debug = debug; + + debug_host_init(engine->engine->debug, engine->engine); + debug_host_start(engine->engine->debug); + } + + return engine; +} + +void wasm_engine_delete(wasm_engine_t *engine) { + if (engine->engine->debug) { + debug_host_stop(engine->engine->debug); + } + + ovm_store_t *store = engine->store; + ovm_engine_delete(engine->engine); + bh_free(store->heap_allocator, engine); + ovm_store_delete(store); +} + diff --git a/interpreter/src/wasm/extern.c b/interpreter/src/wasm/extern.c new file mode 100644 index 00000000..9cda46c8 --- /dev/null +++ b/interpreter/src/wasm/extern.c @@ -0,0 +1,35 @@ + + +#include "ovm_wasm.h" + + +WASM_DECLARE_VEC_IMPL(extern, *) + +wasm_externkind_t wasm_extern_kind(const wasm_extern_t* ext) { + return ext->type->kind; +} + +wasm_externtype_t* wasm_extern_type(const wasm_extern_t* ext) { + return (wasm_externtype_t *) ext->type; +} + +wasm_extern_t* wasm_func_as_extern(wasm_func_t* ext) { return (wasm_extern_t *) ext; } +wasm_extern_t* wasm_global_as_extern(wasm_global_t* ext) { return (wasm_extern_t *) ext; } +wasm_extern_t* wasm_table_as_extern(wasm_table_t* ext) { return (wasm_extern_t *) ext; } +wasm_extern_t* wasm_memory_as_extern(wasm_memory_t* ext) { return (wasm_extern_t *) ext; } + +wasm_func_t* wasm_extern_as_func(wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_FUNC ? (wasm_func_t *) ext : NULL; } +wasm_global_t* wasm_extern_as_global(wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_GLOBAL ? (wasm_global_t *) ext : NULL; } +wasm_table_t* wasm_extern_as_table(wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_TABLE ? (wasm_table_t *) ext : NULL; } +wasm_memory_t* wasm_extern_as_memory(wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_MEMORY ? (wasm_memory_t *) ext : NULL; } + +const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t* ext) { return (const wasm_extern_t *) ext; } +const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t* ext) { return (const wasm_extern_t *) ext; } +const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t* ext) { return (const wasm_extern_t *) ext; } +const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t* ext) { return (const wasm_extern_t *) ext; } + +const wasm_func_t* wasm_extern_as_func_const(const wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_FUNC ? (const wasm_func_t *) ext : NULL; } +const wasm_global_t* wasm_extern_as_global_const(const wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_GLOBAL ? (const wasm_global_t *) ext : NULL; } +const wasm_table_t* wasm_extern_as_table_const(const wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_TABLE ? (const wasm_table_t *) ext : NULL; } +const wasm_memory_t* wasm_extern_as_memory_const(const wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_MEMORY ? (const wasm_memory_t *) ext : NULL; } + diff --git a/interpreter/src/wasm/frame.c b/interpreter/src/wasm/frame.c new file mode 100644 index 00000000..26bfb04c --- /dev/null +++ b/interpreter/src/wasm/frame.c @@ -0,0 +1,37 @@ + +#include "ovm_wasm.h" + + +void wasm_frame_delete(wasm_frame_t *frame) { + // Don't have to free the frame because it was allocated on the + // arena allocator. +} + +WASM_DECLARE_VEC_IMPL(frame, *) + +wasm_frame_t *wasm_frame_copy(const wasm_frame_t* frame) { + if (!frame) return NULL; + assert(frame->instance); + + wasm_frame_t *new_frame = bh_alloc_item(frame->instance->store->engine->store->arena_allocator, wasm_frame_t); + memcpy(new_frame, frame, sizeof(*frame)); + + return new_frame; +} + +wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame) { + return frame->instance; +} + +u32 wasm_frame_func_index(const wasm_frame_t *frame) { + return frame->func_idx; +} + +size_t wasm_frame_func_offset(const wasm_frame_t *frame) { + return frame->func_offset; +} + +size_t wasm_frame_module_offset(const wasm_frame_t *frame) { + return frame->module_offset; +} + diff --git a/interpreter/src/wasm/func.c b/interpreter/src/wasm/func.c new file mode 100644 index 00000000..bd3fad89 --- /dev/null +++ b/interpreter/src/wasm/func.c @@ -0,0 +1,61 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_func_t *wasm_func_new(wasm_store_t *store, const wasm_functype_t *type, wasm_func_callback_t callback) { + wasm_func_t *func = bh_alloc(store->engine->store->arena_allocator, sizeof(*func)); + func->inner.type = wasm_functype_as_externtype_const(type); + func->inner.func.type = type; + func->inner.func.env_present = false; + func->inner.func.env = NULL; + func->inner.func.func_ptr = (void (*)()) callback; + func->inner.func.finalizer = NULL; + + return func; +} + +wasm_func_t *wasm_func_new_with_env(wasm_store_t *store, const wasm_functype_t *type, + wasm_func_callback_with_env_t callback, void *env, void (*finalizer)(void *)) { + + wasm_func_t *func = bh_alloc(store->engine->store->arena_allocator, sizeof(*func)); + func->inner.type = wasm_functype_as_externtype_const(type); + func->inner.func.type = type; + func->inner.func.env_present = true; + func->inner.func.env = env; + func->inner.func.func_ptr = (void (*)()) callback; + func->inner.func.finalizer = finalizer; + + return func; +} + +wasm_functype_t *wasm_func_type(const wasm_func_t *func) { + return (wasm_functype_t *) func->inner.func.type; +} + +size_t wasm_func_param_arity(const wasm_func_t *func) { + // Wow this is gross... + return func->inner.func.type->type.func.params.size; +} + +size_t wasm_func_result_arity(const wasm_func_t *func) { + // Wow this is gross... + return func->inner.func.type->type.func.results.size; +} + +wasm_trap_t *wasm_func_call(const wasm_func_t *func, const wasm_val_vec_t *args, wasm_val_vec_t *results) { + if (func->inner.func.env_present) { + wasm_func_callback_with_env_t cb = (wasm_func_callback_with_env_t) func->inner.func.func_ptr; + wasm_trap_t *trap = cb(func->inner.func.env, args, results); + + if (func->inner.func.finalizer) { + func->inner.func.finalizer(func->inner.func.env); + } + + return trap; + + } else { + wasm_func_callback_t cb = (wasm_func_callback_t) func->inner.func.func_ptr; + wasm_trap_t *trap = cb(args, results); + return trap; + } +} diff --git a/interpreter/src/wasm/global.c b/interpreter/src/wasm/global.c new file mode 100644 index 00000000..81370eb7 --- /dev/null +++ b/interpreter/src/wasm/global.c @@ -0,0 +1,29 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_global_t *wasm_global_new(wasm_store_t *store, const wasm_globaltype_t *type, const wasm_val_t *initial) { + wasm_global_t *global = bh_alloc(store->engine->store->arena_allocator, sizeof(*global)); + global->inner.type = wasm_globaltype_as_externtype_const(type); + global->inner.global.register_index = -1; + global->inner.global.engine = NULL; + + if (initial) { + global->inner.global.initial_value = *initial; + } + + return global; +} + +wasm_globaltype_t *wasm_global_type(const wasm_global_t *global) { + return (wasm_globaltype_t *) global->inner.global.type; +} + +void wasm_global_get(const wasm_global_t *global, wasm_val_t *value) { + assert(("unimplemented", 0)); +} + +void wasm_global_set(wasm_global_t *global, const wasm_val_t *value) { + assert(("unimplemented", 0)); +} + diff --git a/interpreter/src/wasm/instance.c b/interpreter/src/wasm/instance.c new file mode 100644 index 00000000..4a998f67 --- /dev/null +++ b/interpreter/src/wasm/instance.c @@ -0,0 +1,349 @@ + + +#include "ovm_wasm.h" +#include "vm.h" +#include + +static_assert(sizeof(ovm_value_t) == sizeof(wasm_val_t)); + +typedef struct wasm_ovm_binding wasm_ovm_binding; +struct wasm_ovm_binding { + int func_idx; + ovm_engine_t *engine; + ovm_state_t *state; + ovm_program_t *program; +}; + +typedef struct ovm_wasm_binding ovm_wasm_binding; +struct ovm_wasm_binding { + int param_count; + int result_count; + wasm_func_t *func; + wasm_val_vec_t param_buffer; +}; + +#define WASM_TO_OVM(w, o) { \ + (o).u64 = 0;\ + switch ((w).kind) { \ + case WASM_I32: \ + (o).type = OVM_TYPE_I32; \ + (o).i32 = (w).of.i32; \ + break; \ + \ + case WASM_I64: \ + (o).type = OVM_TYPE_I64; \ + (o).i64 = (w).of.i64; \ + break; \ + \ + case WASM_F32: \ + (o).type = OVM_TYPE_F32; \ + (o).f32 = (w).of.f32; \ + break; \ + \ + case WASM_F64: \ + (o).type = OVM_TYPE_F64; \ + (o).f64 = (w).of.f64; \ + break; \ + \ + default: assert(("invalid wasm value type for conversion", 0)); \ + } } + +#define OVM_TO_WASM(o, w) { \ + (w).of.i64 = 0;\ + switch ((o).type) { \ + case OVM_TYPE_I8: \ + (w).kind = WASM_I32; \ + (w).of.i32 = (i32) (o).i8; \ + break; \ + \ + case OVM_TYPE_I16: \ + (w).kind = WASM_I32; \ + (w).of.i32 = (i32) (o).i16; \ + break; \ + \ + case OVM_TYPE_I32: \ + (w).kind = WASM_I32; \ + (w).of.i32 = (i32) (o).i32; \ + break; \ + \ + case OVM_TYPE_I64: \ + (w).kind = WASM_I64; \ + (w).of.i64 = (o).i64; \ + break; \ + \ + case OVM_TYPE_F32: \ + (w).kind = WASM_F32; \ + (w).of.f32 = (o).f32; \ + break; \ + \ + case OVM_TYPE_F64: \ + (w).kind = WASM_F64; \ + (w).of.f64 = (o).f64; \ + break; \ + \ + default: \ + printf("INVALID: %d\n", (o).type); \ + assert(("invalid ovm value type for conversion", 0)); \ + } } + +static wasm_trap_t *wasm_to_ovm_func_call_binding(void *vbinding, const wasm_val_vec_t *args, wasm_val_vec_t *res) { + wasm_ovm_binding *binding = (wasm_ovm_binding *) vbinding; + + ovm_value_t *vals = alloca(sizeof(*vals) * args->size); + fori (i, 0, (int) args->size) { + WASM_TO_OVM(args->data[i], vals[i]); + } + + ovm_value_t ovm_res = ovm_func_call(binding->engine, binding->state, binding->program, binding->func_idx, args->size, vals); + if (!res || res->size == 0) return NULL; + + OVM_TO_WASM(ovm_res, res->data[0]); + + return NULL; +} + +static void ovm_to_wasm_func_call_binding(void *env, ovm_value_t* params, ovm_value_t *res) { + ovm_wasm_binding *binding = (ovm_wasm_binding *) env; + + fori (i, 0, binding->param_count) { + OVM_TO_WASM(params[i], binding->param_buffer.data[i]); + } + + wasm_val_t return_value; + wasm_val_vec_t wasm_results; + wasm_results.data = &return_value; + wasm_results.size = binding->result_count; + + wasm_trap_t *trap = wasm_func_call(binding->func, &binding->param_buffer, &wasm_results); + assert(!trap); + + if (binding->result_count > 0) { + assert(wasm_results.data[0].kind == binding->func->inner.type->func.results.data[0]->kind); + WASM_TO_OVM(return_value, *res); + } +} + +static void wasm_memory_init(void *env, ovm_value_t* params, ovm_value_t *res) { + wasm_instance_t *instr = (wasm_instance_t *) env; + + assert(params[0].type == OVM_TYPE_I32); + assert(params[1].type == OVM_TYPE_I32); + assert(params[2].type == OVM_TYPE_I32); + assert(params[3].type == OVM_TYPE_I32); + + ovm_engine_memory_copy(instr->store->engine->engine, params[0].i32, instr->module->data_entries[params[3].i32].data, params[2].i32); +} + +static void prepare_instance(wasm_instance_t *instance, const wasm_extern_vec_t *imports) { + ovm_store_t *ovm_store = instance->store->engine->store; + ovm_engine_t *ovm_engine = instance->store->engine->engine; + ovm_state_t *ovm_state = instance->state; + ovm_program_t *ovm_program = instance->module->program; + + // + // Place imports in their corresponding "bucket" + fori (i, 0, (int) imports->size) { + assert(instance->module->imports.data[i]->type->kind == imports->data[i]->type->kind); + + switch (wasm_extern_kind(imports->data[i])) { + case WASM_EXTERN_FUNC: { + wasm_importtype_t *importtype = instance->module->imports.data[i]; + struct wasm_functype_inner_t *functype = &importtype->type->func; + + if (!wasm_functype_equals( + wasm_externtype_as_functype(importtype->type), + wasm_externtype_as_functype((wasm_externtype_t *) imports->data[i]->type))) { + assert(("MISMATCHED FUNCTION TYPE", 0)); + } + + wasm_func_t *func = wasm_extern_as_func(imports->data[i]); + bh_arr_push(instance->funcs, func); + + ovm_wasm_binding *binding = bh_alloc(ovm_store->arena_allocator, sizeof(*binding)); + binding->param_count = functype->params.size; + binding->result_count = functype->results.size; + binding->func = func; + binding->param_buffer.data = bh_alloc(ovm_store->arena_allocator, sizeof(wasm_val_t) * binding->param_count); + binding->param_buffer.size = binding->param_count; + + ovm_state_register_external_func(ovm_state, importtype->external_func_idx, ovm_to_wasm_func_call_binding, binding); + break; + } + + case WASM_EXTERN_MEMORY: { + wasm_memory_t *memory = wasm_extern_as_memory(imports->data[i]); + bh_arr_push(instance->memories, memory); + + memory->inner.memory.engine = ovm_engine; + break; + } + + case WASM_EXTERN_GLOBAL: { + wasm_global_t *global = wasm_extern_as_global(imports->data[i]); + + global->inner.global.engine = ovm_engine; + global->inner.global.state = ovm_state; + global->inner.global.register_index = bh_arr_length(instance->globals); + + ovm_value_t val = {0}; + WASM_TO_OVM(global->inner.global.initial_value, val); + ovm_state_register_set(ovm_state, global->inner.global.register_index, val); + + bh_arr_push(instance->globals, global); + break; + } + + case WASM_EXTERN_TABLE: { + wasm_table_t *table = wasm_extern_as_table(imports->data[i]); + table->inner.table.engine = ovm_engine; + table->inner.table.program = ovm_program; + table->inner.table.static_arr = instance->module->imports.data[i]->type->table.static_arr; + + bh_arr_push(instance->tables, table); + break; + } + } + } + + ovm_state_register_external_func(ovm_state, instance->module->memory_init_external_idx, wasm_memory_init, instance); + + // + // Create function objects + fori (i, 0, (int) instance->module->functypes.size) { + wasm_ovm_binding *binding = bh_alloc(instance->store->engine->store->arena_allocator, sizeof(*binding)); + binding->engine = ovm_engine; + binding->func_idx = bh_arr_length(instance->funcs); + binding->program = ovm_program; + binding->state = ovm_state; + + wasm_func_t *func = wasm_func_new_with_env(instance->store, instance->module->functypes.data[i], + wasm_to_ovm_func_call_binding, binding, NULL); + + bh_arr_push(instance->funcs, func); + } + + // + // Create memory objects + fori (i, 0, (int) instance->module->memorytypes.size) { + wasm_memory_t *memory = wasm_memory_new(instance->store, instance->module->memorytypes.data[i]); + memory->inner.memory.engine = ovm_engine; + + bh_arr_push(instance->memories, memory); + } + + // + // Create table objects + fori (i, 0, (int) instance->module->tabletypes.size) { + wasm_table_t *table = wasm_table_new(instance->store, instance->module->tabletypes.data[i], NULL); + table->inner.table.engine = ovm_engine; + table->inner.table.program = ovm_program; + table->inner.table.static_arr = instance->module->tabletypes.data[i]->type.table.static_arr; + + bh_arr_push(instance->tables, table); + } + + // + // Create global objects + fori (i, 0, (int) instance->module->globaltypes.size) { + wasm_global_t *global = wasm_global_new(instance->store, instance->module->globaltypes.data[i], + &instance->module->globaltypes.data[i]->type.global.initial_value); + + global->inner.global.engine = ovm_engine; + global->inner.global.state = ovm_state; + global->inner.global.register_index = bh_arr_length(instance->globals); + + ovm_value_t val = {0}; + WASM_TO_OVM(global->inner.global.initial_value, val); + ovm_state_register_set(ovm_state, global->inner.global.register_index, val); + + bh_arr_push(instance->globals, global); + } + + + // + // Initialize all non-passive data segments + fori (i, 0, (int) instance->module->data_count) { + struct wasm_data_t *datum = &instance->module->data_entries[i]; + if (datum->passive) continue; + + ovm_engine_memory_copy(ovm_engine, datum->offset, datum->data, datum->length); + } + + wasm_extern_vec_new_uninitialized(&instance->exports, instance->module->exports.size); + fori (i, 0, (int) instance->module->exports.size) { + wasm_exporttype_t *externtype = instance->module->exports.data[i]; + + switch (externtype->type->kind) { + case WASM_EXTERN_FUNC: { + wasm_func_t *func = instance->funcs[externtype->index]; + instance->exports.data[i] = wasm_func_as_extern(func); + break; + } + + case WASM_EXTERN_MEMORY: { + wasm_memory_t *memory = instance->memories[externtype->index]; + instance->exports.data[i] = wasm_memory_as_extern(memory); + break; + } + + case WASM_EXTERN_GLOBAL: { + wasm_global_t *global = instance->globals[externtype->index]; + instance->exports.data[i] = wasm_global_as_extern(global); + break; + } + + case WASM_EXTERN_TABLE: { + wasm_table_t *table = instance->tables[externtype->index]; + instance->exports.data[i] = wasm_table_as_extern(table); + break; + } + } + } +} + +wasm_instance_t *wasm_instance_new(wasm_store_t *store, const wasm_module_t *module, + const wasm_extern_vec_t *imports, wasm_trap_t **trap) { + + wasm_instance_t *instance = bh_alloc(store->engine->store->heap_allocator, sizeof(*instance)); + instance->store = store; + instance->module = module; + + if (store->instance) { + bh_printf("A WASM store should only be used for a single instance!\n"); + return NULL; + } + + store->instance = instance; + + instance->funcs = NULL; + instance->memories = NULL; + instance->tables = NULL; + instance->globals = NULL; + bh_arr_new(store->engine->store->heap_allocator, instance->funcs, module->functypes.size); + bh_arr_new(store->engine->store->heap_allocator, instance->memories, 1); + bh_arr_new(store->engine->store->heap_allocator, instance->tables, 1); + bh_arr_new(store->engine->store->heap_allocator, instance->globals, module->globaltypes.size); + + instance->state = ovm_state_new(store->engine->engine, module->program); + + prepare_instance(instance, imports); + + if (trap) *trap = NULL; + + return instance; +} + +void wasm_instance_delete(wasm_instance_t *instance) { + bh_arr_free(instance->funcs); + bh_arr_free(instance->memories); + bh_arr_free(instance->globals); + bh_arr_free(instance->tables); + + wasm_extern_vec_delete(&instance->exports); + ovm_state_delete(instance->state); + bh_free(instance->store->engine->store->heap_allocator, instance); +} + +void wasm_instance_exports(const wasm_instance_t *instance, wasm_extern_vec_t *out) { + *out = instance->exports; +} diff --git a/interpreter/src/wasm/memory.c b/interpreter/src/wasm/memory.c new file mode 100644 index 00000000..38445793 --- /dev/null +++ b/interpreter/src/wasm/memory.c @@ -0,0 +1,42 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_memory_t *wasm_memory_new(wasm_store_t *store, const wasm_memorytype_t *type) { + wasm_memory_t *memory = bh_alloc(store->engine->store->arena_allocator, sizeof(*store)); + memory->inner.type = wasm_memorytype_as_externtype_const(type); + memory->inner.memory.type = type; + memory->inner.memory.engine = NULL; + + return memory; +} + +wasm_memorytype_t *wasm_memory_type(const wasm_memory_t *memory) { + return (wasm_memorytype_t *) memory->inner.memory.type; +} + +byte_t *wasm_memory_data(wasm_memory_t *memory) { + assert(memory && memory->inner.memory.engine); + return memory->inner.memory.engine->memory; +} + +size_t wasm_memory_data_size(const wasm_memory_t *memory) { + assert(memory && memory->inner.memory.engine); + return memory->inner.memory.engine->memory_size; +} + +wasm_memory_pages_t wasm_memory_size(const wasm_memory_t *memory) { + assert(memory && memory->inner.memory.engine); + return memory->inner.memory.engine->memory_size / MEMORY_PAGE_SIZE; +} + +bool wasm_memory_grow(wasm_memory_t *memory, wasm_memory_pages_t pages) { + // + // This will always fail, as initially the VM is created with + // a 4GiB mmap, so growing it will not be an option. If that + // changes and a dynamically allocated solution is used, then + // this can change. I don't see that changing however, as I will + // never need to use this on 32-bit systems, and that would be the + // only case that I would not like to try to mmap 4 gigs. + return false; +} diff --git a/interpreter/src/wasm/module.c b/interpreter/src/wasm/module.c new file mode 100644 index 00000000..8e469a67 --- /dev/null +++ b/interpreter/src/wasm/module.c @@ -0,0 +1,101 @@ + + +#include "ovm_wasm.h" +#include "vm_codebuilder.h" +#include "stb_ds.h" + +#include "./module_parsing.h" + +static bool module_build(wasm_module_t *module, const wasm_byte_vec_t *binary) { + wasm_engine_t *engine = module->store->engine; + module->program = ovm_program_new(engine->store); + + build_context ctx; + ctx.binary = *binary; + ctx.offset = 8; // Skip the magic bytes and version + ctx.module = module; + ctx.program = module->program; + ctx.store = engine->store; + ctx.next_external_func_idx = 0; + + debug_info_builder_init(&ctx.debug_builder, &module->debug_info); + sh_new_arena(module->custom_sections); + + while (ctx.offset < binary->size) { + parse_section(&ctx); + } + + // TODO: This is not correct when the module imports a global. + // But Onyx does not do this, so I don't care at the moment. + module->program->register_count = module->globaltypes.size; + + return true; +} + + +#define WASM_MODULE_INDEX(k1, k2) \ + wasm_##k1##type_t *wasm_module_index_##k1##type(wasm_module_t *module, int index) { \ + fori (i, 0, (int) module->imports.size) { \ + if (module->imports.data[i]->type->kind == k2) { \ + if (index == 0) { \ + return wasm_externtype_as_##k1##type(module->imports.data[i]->type); \ + } \ + \ + index -= 1; \ + } \ + } \ + \ + if (index < (int) module->k1##types.size) { \ + return module->k1##types.data[index]; \ + } \ + \ + return NULL; \ + } + +WASM_MODULE_INDEX(func, WASM_EXTERN_FUNC) +WASM_MODULE_INDEX(memory, WASM_EXTERN_MEMORY) +WASM_MODULE_INDEX(table, WASM_EXTERN_TABLE) +WASM_MODULE_INDEX(global, WASM_EXTERN_GLOBAL) + +#undef WASM_MODULE_INDEX + + +// Ommitting the "sharable ref" crap that I don't think will +// ever be needed for a module. + + +wasm_module_t *wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary) { + wasm_module_t *module = bh_alloc(store->engine->store->arena_allocator, sizeof(*module)); + memset(module, 0, sizeof(*module)); + module->store = store; + + debug_info_init(&module->debug_info); + + if (store->engine->engine->debug) { + assert(store->engine->engine->debug->info == NULL); + store->engine->engine->debug->info = &module->debug_info; + } + + bool success = module_build(module, binary); + return module; +} + +void wasm_module_delete(wasm_module_t *module) { + ovm_program_delete(module->program); +} + +bool wasm_module_validate(wasm_store_t *store, const wasm_byte_vec_t *binary) { + // Hmmm... + return false; +} + +void wasm_module_imports(const wasm_module_t *module, wasm_importtype_vec_t *out_imports) { + *out_imports = module->imports; +} + +void wasm_module_exports(const wasm_module_t *module, wasm_exporttype_vec_t *out_exports) { + *out_exports = module->exports; +} + + + diff --git a/interpreter/src/wasm/module_parsing.h b/interpreter/src/wasm/module_parsing.h new file mode 100644 index 00000000..8eade304 --- /dev/null +++ b/interpreter/src/wasm/module_parsing.h @@ -0,0 +1,984 @@ +// vim: ft=c: + +// +// This file is not to be compile like normal. +// It is instead included in wasm/module.c +// +// Currently, this file has a lot of code that directly manipulates +// the code builder object. I would like to move this into the API +// for the code builder itself, to make it more portable and easy +// to read. + +typedef struct build_context build_context; +struct build_context { + wasm_byte_vec_t binary; + unsigned int offset; + + wasm_module_t *module; + ovm_program_t *program; + ovm_store_t *store; + + int func_table_arr_idx; + int next_external_func_idx; + + debug_info_builder_t debug_builder; + + // This will be set/reset for every code (function) entry. + ovm_code_builder_t builder; +}; + +#define PEEK_BYTE(ctx) ((ctx)->binary.data[(ctx)->offset]) +#define CONSUME_BYTE(ctx) ((ctx)->binary.data[(ctx)->offset++]) + +enum wasm_section_numbers_t { + WASM_CUSTOM_SECTION = 0, + WASM_TYPE_SECTION = 1, + WASM_IMPORT_SECTION = 2, + WASM_FUNC_SECTION = 3, + WASM_TABLE_SECTION = 4, + WASM_MEMORY_SECTION = 5, + WASM_GLOBAL_SECTION = 6, + WASM_EXPORT_SECTION = 7, + WASM_START_SECTION = 8, + WASM_ELEM_SECTION = 9, + WASM_CODE_SECTION = 10, + WASM_DATA_SECTION = 11, + WASM_DATAC_SECTION = 12, +}; + +static inline wasm_valkind_t parse_valtype(build_context *ctx) { + switch (CONSUME_BYTE(ctx)) { + case 0x7f: return WASM_I32; + case 0x7e: return WASM_I64; + case 0x7d: return WASM_F32; + case 0x7c: return WASM_F64; + case 0x7b: assert(("SIMD values are not currently supported", 0)); + case 0x70: return WASM_FUNCREF; + case 0x6F: return WASM_ANYREF; + default: assert(("Invalid valtype.", 0)); + } +} + +static void parse_custom_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int end_of_section = ctx->offset + section_size; + + struct wasm_custom_section_t cs; + + char name[256]; + unsigned int name_len = uleb128_to_uint(ctx->binary.data, &ctx->offset); + if (name_len < sizeof(name) - 1) { + strncpy(name, &((char *) ctx->binary.data)[ctx->offset], name_len); + name[name_len] = '\0'; + + ctx->offset += name_len; + cs.size = end_of_section - ctx->offset; + + unsigned int data_size = end_of_section - ctx->offset; + cs.data = bh_alloc_array(ctx->store->heap_allocator, char, data_size); + memcpy(cs.data, &((char *) ctx->binary.data)[ctx->offset], data_size); + + shput(ctx->module->custom_sections, name, cs); + + if (!strcmp(name, "ovm_debug_files")) { + debug_info_import_file_info(ctx->debug_builder.info, cs.data, cs.size); + } + + if (!strcmp(name, "ovm_debug_funcs")) { + debug_info_import_func_info(ctx->debug_builder.info, cs.data, cs.size); + } + + if (!strcmp(name, "ovm_debug_ops")) { + debug_info_builder_prepare(&ctx->debug_builder, cs.data); + } + + if (!strcmp(name, "ovm_debug_syms")) { + debug_info_import_sym_info(ctx->debug_builder.info, cs.data, cs.size); + } + + if (!strcmp(name, "ovm_debug_types")) { + debug_info_import_type_info(ctx->debug_builder.info, cs.data, cs.size); + } + } + + ctx->offset = end_of_section; +} + +static void parse_type_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int type_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_functype_vec_new_uninitialized(&ctx->module->type_section, type_count); + + fori (i, 0, (int) type_count) { + assert(CONSUME_BYTE(ctx) == 0x60); // @ReportError + + unsigned int param_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + wasm_valtype_vec_t param_types; + wasm_valtype_vec_new_uninitialized(¶m_types, param_count); + fori (p, 0, (int) param_count) { + param_types.data[p] = wasm_valtype_new(parse_valtype(ctx)); + } + + unsigned int result_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + wasm_valtype_vec_t result_types; + wasm_valtype_vec_new_uninitialized(&result_types, result_count); + fori (p, 0, (int) result_count) { + result_types.data[p] = wasm_valtype_new(parse_valtype(ctx)); + } + + wasm_functype_t *functype = wasm_functype_new(¶m_types, &result_types); + ctx->module->type_section.data[i] = functype; + } +} + +static wasm_limits_t parse_limits(build_context *ctx) { + bool maximum_present = CONSUME_BYTE(ctx) == 0x01; + + wasm_limits_t limits; + limits.min = uleb128_to_uint(ctx->binary.data, &ctx->offset); + if (maximum_present) { + limits.max = uleb128_to_uint(ctx->binary.data, &ctx->offset); + } else { + limits.max = wasm_limits_max_default; + } + + return limits; +} + +static wasm_tabletype_t *parse_tabletype(build_context *ctx) { + assert(CONSUME_BYTE(ctx) == 0x70); // @ReportError + + wasm_limits_t limits = parse_limits(ctx); + wasm_tabletype_t *tt = wasm_tabletype_new(wasm_valtype_new(WASM_FUNCREF), &limits); + return tt; +} + +static wasm_memorytype_t *parse_memorytype(build_context *ctx) { + wasm_limits_t limits = parse_limits(ctx); + wasm_memorytype_t *mt = wasm_memorytype_new(&limits); + return mt; +} + +static wasm_globaltype_t *parse_globaltype(build_context *ctx) { + wasm_valtype_t *valtype = wasm_valtype_new(parse_valtype(ctx)); + bool mutable = CONSUME_BYTE(ctx) == 0x01; + + wasm_globaltype_t *gt = wasm_globaltype_new(valtype, mutable); + return gt; +} + +static void parse_import_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int import_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_importtype_vec_new_uninitialized(&ctx->module->imports, import_count); + + fori (i, 0, (int) import_count) { + unsigned int mod_name_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + wasm_byte_vec_t module_name; + wasm_byte_vec_new_uninitialized(&module_name, mod_name_size); + fori (n, 0, mod_name_size) module_name.data[n] = CONSUME_BYTE(ctx); + + unsigned int import_name_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + wasm_byte_vec_t import_name; + wasm_byte_vec_new_uninitialized(&import_name, import_name_size); + fori (n, 0, import_name_size) import_name.data[n] = CONSUME_BYTE(ctx); + + wasm_externtype_t *import_type = NULL; + switch (CONSUME_BYTE(ctx)) { + case 0x00: { + unsigned int type_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + import_type = wasm_functype_as_externtype(ctx->module->type_section.data[type_idx]); + break; + } + + case 0x01: import_type = wasm_tabletype_as_externtype(parse_tabletype(ctx)); break; + case 0x02: import_type = wasm_memorytype_as_externtype(parse_memorytype(ctx)); break; + case 0x03: import_type = wasm_globaltype_as_externtype(parse_globaltype(ctx)); break; + } + + wasm_importtype_t *import = wasm_importtype_new(&module_name, &import_name, import_type); + ctx->module->imports.data[i] = import; + + if (import_type->kind == WASM_EXTERN_FUNC) { + char *external_func_name = bh_aprintf(ctx->program->store->arena_allocator, "%b.%b", + module_name.data, module_name.size, + import_name.data, import_name.size); + + int external_func_idx = ctx->next_external_func_idx++; + import->external_func_idx = external_func_idx; + + ovm_program_register_external_func(ctx->program, external_func_name, import_type->func.params.size, external_func_idx); + } + } +} + +static void parse_func_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int func_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_functype_vec_new_uninitialized(&ctx->module->functypes, func_count); + + fori (i, 0, (int) func_count) { + unsigned int index = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ctx->module->functypes.data[i] = ctx->module->type_section.data[index]; + } +} + +static void parse_table_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int table_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_tabletype_vec_new_uninitialized(&ctx->module->tabletypes, table_count); + + fori (i, 0, (int) table_count) { + ctx->module->tabletypes.data[i] = parse_tabletype(ctx); + } +} + +static void parse_memory_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int memory_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_memorytype_vec_new_uninitialized(&ctx->module->memorytypes, memory_count); + + fori (i, 0, (int) memory_count) { + ctx->module->memorytypes.data[i] = parse_memorytype(ctx); + } +} + +static void parse_global_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int global_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_globaltype_vec_new_uninitialized(&ctx->module->globaltypes, global_count); + + fori (i, 0, (int) global_count) { + wasm_globaltype_t *gt = parse_globaltype(ctx); + + switch (CONSUME_BYTE(ctx)) { + case 0x41: { + gt->type.global.initial_value.kind = WASM_I32; + gt->type.global.initial_value.of.i32 = (i32) uleb128_to_uint(ctx->binary.data, &ctx->offset); + break; + } + + case 0x42: { + gt->type.global.initial_value.kind = WASM_I64; + gt->type.global.initial_value.of.i64 = (i64) uleb128_to_uint(ctx->binary.data, &ctx->offset); + break; + } + + case 0x43: { + gt->type.global.initial_value.kind = WASM_F32; + gt->type.global.initial_value.of.f32 = *(f32 *) &ctx->binary.data[ctx->offset]; // HACK: This assumes IEEE-754 floats + ctx->offset += 4; + break; + } + + case 0x44: { + gt->type.global.initial_value.kind = WASM_F64; + gt->type.global.initial_value.of.f64 = *(f64 *) &ctx->binary.data[ctx->offset]; // HACK: This assumes IEEE-754 floats + ctx->offset += 8; + break; + } + } + + assert(CONSUME_BYTE(ctx) == 0x0b); + + ctx->module->globaltypes.data[i] = gt; + } +} + +static void parse_export_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int export_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_exporttype_vec_new_uninitialized(&ctx->module->exports, export_count); + + fori (i, 0, (int) export_count) { + unsigned int export_name_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + wasm_byte_vec_t export_name; + wasm_byte_vec_new_uninitialized(&export_name, export_name_size); + fori (n, 0, export_name_size) export_name.data[n] = CONSUME_BYTE(ctx); + + unsigned int type = CONSUME_BYTE(ctx); + unsigned int idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_externtype_t *export_type = NULL; + + switch (type) { + case 0x00: export_type = wasm_functype_as_externtype(wasm_module_index_functype(ctx->module, idx)); break; + case 0x01: export_type = wasm_tabletype_as_externtype(wasm_module_index_tabletype(ctx->module, idx)); break; + case 0x02: export_type = wasm_memorytype_as_externtype(wasm_module_index_memorytype(ctx->module, idx)); break; + case 0x03: export_type = wasm_globaltype_as_externtype(wasm_module_index_globaltype(ctx->module, idx)); break; + default: assert(0); + } + + assert(export_type); + + wasm_exporttype_t *export = wasm_exporttype_new(&export_name, export_type); + export->index = idx; + ctx->module->exports.data[i] = export; + } +} + +static void parse_start_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ctx->module->start_func_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); +} + +static void parse_elem_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int elem_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + // This is going to be a mess... + // I am only going to handle the case of a single, active, offset-0, + // element entry. This is all that Onyx uses and will probably ever + // use. + assert(elem_count == 1); + assert(CONSUME_BYTE(ctx) == 0x00); + assert(CONSUME_BYTE(ctx) == 0x41); + assert(CONSUME_BYTE(ctx) == 0x00); + assert(CONSUME_BYTE(ctx) == 0x0B); + + unsigned int entry_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ctx->module->elem_count = entry_count; + ctx->module->elem_entries = malloc(sizeof(unsigned int) * entry_count); + + 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); + + assert(ctx->module->tabletypes.size == 1); + ctx->module->tabletypes.data[0]->type.table.static_arr = ctx->func_table_arr_idx; +} + +static void parse_data_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int data_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + if (ctx->module->data_count_present) { + assert(ctx->module->data_count == data_count); + } else { + ctx->module->data_count = data_count; + } + + ctx->module->data_entries = malloc(sizeof(struct wasm_data_t) * data_count); + + fori (i, 0, (int) data_count) { + struct wasm_data_t data_entry; + data_entry.data = NULL; + data_entry.offset = 0; + data_entry.length = 0; + data_entry.passive = true; + + char data_type = CONSUME_BYTE(ctx); + if (data_type == 0x00) { + assert(CONSUME_BYTE(ctx) == 0x41); + data_entry.offset = uleb128_to_uint(ctx->binary.data, &ctx->offset); + data_entry.passive = false; + assert(CONSUME_BYTE(ctx) == 0x0B); + } + + data_entry.length = uleb128_to_uint(ctx->binary.data, &ctx->offset); + data_entry.data = bh_pointer_add(ctx->binary.data, ctx->offset); + ctx->offset += data_entry.length; + + ctx->module->data_entries[i] = data_entry; + } +} + +static void parse_data_count_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int data_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + ctx->module->data_count_present = true; + ctx->module->data_count = data_count; +} + + + +// +// Instruction building +// + +static void parse_expression(build_context *ctx); + +static void parse_fc_instruction(build_context *ctx) { + int instr_num = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + switch (instr_num) { + case 0: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I32)); break; + case 1: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I32)); break; + case 2: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I32)); break; + case 3: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I32)); break; + case 4: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I64)); break; + case 5: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I64)); break; + case 6: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I64)); break; + case 7: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I64)); break; + + case 8: { + int dataidx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + assert(CONSUME_BYTE(ctx) == 0x00); + + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &dataidx); + ovm_code_builder_add_call(&ctx->builder, ctx->module->memory_init_idx, 4, false); + break; + } + + case 10: { + assert(CONSUME_BYTE(ctx) == 0x00); + assert(CONSUME_BYTE(ctx) == 0x00); + + ovm_code_builder_add_memory_copy(&ctx->builder); + break; + } + + case 11: { + assert(CONSUME_BYTE(ctx) == 0x00); + + ovm_code_builder_add_memory_fill(&ctx->builder); + break; + } + + default: assert(("UNHANDLED FC INSTRUCTION", 0)); + } +} + +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_atomic_load(&ctx->builder, 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_atomic_store(&ctx->builder, 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) { + debug_info_builder_step(&ctx->debug_builder); + + unsigned char instr_byte = CONSUME_BYTE(ctx); + switch (instr_byte) { + case 0x00: + break; + + case 0x01: + ovm_code_builder_add_nop(&ctx->builder); + break; + + case 0x02: { + // Currently, only "void" block types are valid. + assert(CONSUME_BYTE(ctx) == 0x40); + ovm_code_builder_push_label_target(&ctx->builder, label_kind_block); + break; + } + + case 0x03: { + // Currently, only "void" block types are valid. + assert(CONSUME_BYTE(ctx) == 0x40); + ovm_code_builder_push_label_target(&ctx->builder, label_kind_loop); + break; + } + + case 0x04: { + // Currently, only "void" block types are valid. + assert(CONSUME_BYTE(ctx) == 0x40); + int if_target = ovm_code_builder_push_label_target(&ctx->builder, label_kind_if); + + // + // This uses the pattern of "branch if zero" to skip a section of + // code if the condition was not true. + ovm_code_builder_add_cond_branch(&ctx->builder, if_target, false, true); + break; + } + + case 0x05: { + label_target_t if_target = ovm_code_builder_wasm_target_idx(&ctx->builder, 0); + ovm_code_builder_add_branch(&ctx->builder, if_target.idx); + ovm_code_builder_patch_else(&ctx->builder, if_target); + break; + } + + case 0x0B: { + ovm_code_builder_pop_label_target(&ctx->builder); + break; + } + + case 0x0C: { + int label_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + label_target_t target = ovm_code_builder_wasm_target_idx(&ctx->builder, label_idx); + ovm_code_builder_add_branch(&ctx->builder, target.idx); + break; + } + + case 0x0D: { + int label_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + label_target_t target = ovm_code_builder_wasm_target_idx(&ctx->builder, label_idx); + ovm_code_builder_add_cond_branch(&ctx->builder, target.idx, true, false); + break; + } + + 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) { + int label_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + label_target_t target = ovm_code_builder_wasm_target_idx(&ctx->builder, label_idx); + entries[i] = target.idx; + } + + int default_entry_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + label_target_t target = ovm_code_builder_wasm_target_idx(&ctx->builder, default_entry_idx); + int default_entry = target.idx; + + ovm_code_builder_add_branch_table(&ctx->builder, entry_count, entries, default_entry); + break; + } + + case 0x0F: { + ovm_code_builder_add_return(&ctx->builder); + break; + } + + case 0x10: { + int func_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + wasm_functype_t *functype = wasm_module_index_functype(ctx->module, func_idx); + int param_count = functype->type.func.params.size; + + ovm_code_builder_add_call(&ctx->builder, func_idx, param_count, functype->type.func.results.size != 0); + break; + } + + case 0x11: { + int type_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + int table_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + assert(table_idx == 0); + + wasm_functype_t *functype = ctx->module->type_section.data[type_idx]; + int param_count = functype->type.func.params.size; + ovm_code_builder_add_indirect_call(&ctx->builder, param_count, functype->type.func.results.size != 0); + break; + } + + case 0x1A: { + ovm_code_builder_drop_value(&ctx->builder); + break; + } + + case 0x1B: assert(0); + + case 0x20: { + int local_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ovm_code_builder_add_local_get(&ctx->builder, local_idx); + break; + } + + case 0x21: { + int local_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ovm_code_builder_add_local_set(&ctx->builder, local_idx); + break; + } + + case 0x22: { + int local_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ovm_code_builder_add_local_tee(&ctx->builder, local_idx); + break; + } + + case 0x23: { + int global_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ovm_code_builder_add_register_get(&ctx->builder, global_idx); + break; + } + + case 0x24: { + int global_idx = uleb128_to_uint(ctx->binary.data, &ctx->offset); + ovm_code_builder_add_register_set(&ctx->builder, global_idx); + break; + } + +#define LOAD_CASE(num, type, convert, convert_op, convert_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, type, offset); \ + if (convert) ovm_code_builder_add_unop(&ctx->builder, OVM_TYPED_INSTR(convert_op, convert_type)); \ + break; \ + } + + LOAD_CASE(0x28, OVM_TYPE_I32, false, 0, 0) + LOAD_CASE(0x29, OVM_TYPE_I64, false, 0, 0) + LOAD_CASE(0x2A, OVM_TYPE_F32, false, 0, 0) + LOAD_CASE(0x2B, OVM_TYPE_F64, false, 0, 0) + + LOAD_CASE(0x2C, OVM_TYPE_I8, true, OVMI_CVT_I8_S, OVM_TYPE_I32) + LOAD_CASE(0x2D, OVM_TYPE_I8, true, OVMI_CVT_I8, OVM_TYPE_I32) + LOAD_CASE(0x2E, OVM_TYPE_I16, true, OVMI_CVT_I16_S, OVM_TYPE_I32) + LOAD_CASE(0x2F, OVM_TYPE_I16, true, OVMI_CVT_I16, OVM_TYPE_I32) + LOAD_CASE(0x30, OVM_TYPE_I8, true, OVMI_CVT_I8_S, OVM_TYPE_I64) + LOAD_CASE(0x31, OVM_TYPE_I8, true, OVMI_CVT_I8, OVM_TYPE_I64) + LOAD_CASE(0x32, OVM_TYPE_I16, true, OVMI_CVT_I16_S, OVM_TYPE_I64) + LOAD_CASE(0x33, OVM_TYPE_I16, true, OVMI_CVT_I16, OVM_TYPE_I64) + LOAD_CASE(0x34, OVM_TYPE_I32, true, OVMI_CVT_I32_S, OVM_TYPE_I64) + LOAD_CASE(0x35, OVM_TYPE_I32, true, OVMI_CVT_I32, OVM_TYPE_I64) + +#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, type, offset); \ + break; \ + } + + STORE_CASE(0x36, OVM_TYPE_I32); + STORE_CASE(0x37, OVM_TYPE_I64); + STORE_CASE(0x38, OVM_TYPE_F32); + STORE_CASE(0x39, OVM_TYPE_F64); + + STORE_CASE(0x3A, OVM_TYPE_I8); + STORE_CASE(0x3B, OVM_TYPE_I16); + STORE_CASE(0x3C, OVM_TYPE_I8); + STORE_CASE(0x3D, OVM_TYPE_I16); + STORE_CASE(0x3E, OVM_TYPE_I32); + +#undef STORE_CASE + + case 0x3F: { + assert(CONSUME_BYTE(ctx) == 0x00); + + int memory_size = 65535; + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &memory_size); + break; + } + + case 0x40: { + assert(CONSUME_BYTE(ctx) == 0x00); + ovm_code_builder_drop_value(&ctx->builder); + + int value = -1; + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &value); + break; + } + + case 0x41: { + long long value = leb128_to_int(ctx->binary.data, &ctx->offset); + + // NOTE: This assumes a little-endian CPU as the address is assumes + // to be the least significant byte. + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &value); + break; + } + + case 0x42: { + long long value = leb128_to_int(ctx->binary.data, &ctx->offset); + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I64, &value); + break; + } + + case 0x43: { + float value = * (f32 *) &ctx->binary.data[ctx->offset]; + ctx->offset += 4; + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_F32, &value); + break; + } + + case 0x44: { + double value = * (f64 *) &ctx->binary.data[ctx->offset]; + ctx->offset += 8; + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_F64, &value); + break; + } + + case 0x45: { + unsigned int zero = 0; + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I32, &zero); + ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I32)); + break; + } + case 0x46: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I32)); break; + case 0x47: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_I32)); break; + case 0x48: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_I32)); break; + case 0x49: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_I32)); break; + case 0x4A: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_I32)); break; + case 0x4B: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_I32)); break; + case 0x4C: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_I32)); break; + case 0x4D: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I32)); break; + case 0x4E: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_I32)); break; + case 0x4F: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_I32)); break; + + case 0x50: { + unsigned long long zero = 0; + ovm_code_builder_add_imm(&ctx->builder, OVM_TYPE_I64, &zero); + ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I64)); + break; + } + case 0x51: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_I64)); break; + case 0x52: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_I64)); break; + case 0x53: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LT_S, OVM_TYPE_I64)); break; + case 0x54: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_I64)); break; + case 0x55: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GT_S, OVM_TYPE_I64)); break; + case 0x56: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_I64)); break; + case 0x57: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LE_S, OVM_TYPE_I64)); break; + case 0x58: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_I64)); break; + case 0x59: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GE_S, OVM_TYPE_I64)); break; + case 0x5A: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_I64)); break; + + case 0x5B: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_F32)); break; + case 0x5C: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_F32)); break; + case 0x5D: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_F32)); break; + case 0x5E: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_F32)); break; + case 0x5F: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_F32)); break; + case 0x60: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_F32)); break; + + case 0x61: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_EQ, OVM_TYPE_F64)); break; + case 0x62: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_NE, OVM_TYPE_F64)); break; + case 0x63: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LT, OVM_TYPE_F64)); break; + case 0x64: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GT, OVM_TYPE_F64)); break; + case 0x65: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_LE, OVM_TYPE_F64)); break; + case 0x66: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_GE, OVM_TYPE_F64)); break; + + case 0x67: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_CLZ, OVM_TYPE_I32)); break; + case 0x68: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_CTZ, OVM_TYPE_I32)); break; + case 0x69: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_POPCNT, OVM_TYPE_I32)); break; + case 0x6A: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I32)); break; + case 0x6B: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_I32)); break; + case 0x6C: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_I32)); break; + case 0x6D: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_I32)); break; + case 0x6E: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_I32)); break; + case 0x6F: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_REM_S, OVM_TYPE_I32)); break; + case 0x70: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_REM, OVM_TYPE_I32)); break; + case 0x71: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_AND, OVM_TYPE_I32)); break; + case 0x72: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_OR, OVM_TYPE_I32)); break; + case 0x73: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_XOR, OVM_TYPE_I32)); break; + case 0x74: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SHL, OVM_TYPE_I32)); break; + case 0x75: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SAR, OVM_TYPE_I32)); break; + case 0x76: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SHR, OVM_TYPE_I32)); break; + case 0x77: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ROTL, OVM_TYPE_I32)); break; + case 0x78: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ROTR, OVM_TYPE_I32)); break; + + case 0x79: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CLZ, OVM_TYPE_I64)); break; + case 0x7A: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CTZ, OVM_TYPE_I64)); break; + case 0x7B: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_POPCNT, OVM_TYPE_I64)); break; + case 0x7C: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_I64)); break; + case 0x7D: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_I64)); break; + case 0x7E: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_I64)); break; + case 0x7F: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_DIV_S, OVM_TYPE_I64)); break; + case 0x80: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_I64)); break; + case 0x81: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_REM_S, OVM_TYPE_I64)); break; + case 0x82: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_REM, OVM_TYPE_I64)); break; + case 0x83: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_AND, OVM_TYPE_I64)); break; + case 0x84: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_OR, OVM_TYPE_I64)); break; + case 0x85: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_XOR, OVM_TYPE_I64)); break; + case 0x86: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SHL, OVM_TYPE_I64)); break; + case 0x87: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SAR, OVM_TYPE_I64)); break; + case 0x88: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SHR, OVM_TYPE_I64)); break; + case 0x89: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ROTL, OVM_TYPE_I64)); break; + case 0x8A: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ROTR, OVM_TYPE_I64)); break; + + case 0x8B: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_ABS, OVM_TYPE_F32)); break; + case 0x8C: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_NEG, OVM_TYPE_F32)); break; + case 0x8D: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CEIL, OVM_TYPE_F32)); break; + case 0x8E: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_FLOOR, OVM_TYPE_F32)); break; + case 0x8F: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_TRUNC, OVM_TYPE_F32)); break; + case 0x90: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_NEAREST, OVM_TYPE_F32)); break; + case 0x91: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_SQRT, OVM_TYPE_F32)); break; + case 0x92: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_F32)); break; + case 0x93: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_F32)); break; + case 0x94: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_F32)); break; + case 0x95: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_F32)); break; + case 0x96: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MIN, OVM_TYPE_F32)); break; + case 0x97: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MAX, OVM_TYPE_F32)); break; + case 0x98: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_COPYSIGN, OVM_TYPE_F32)); break; + + case 0x99: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_ABS, OVM_TYPE_F64)); break; + case 0x9A: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_NEG, OVM_TYPE_F64)); break; + case 0x9B: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CEIL, OVM_TYPE_F64)); break; + case 0x9C: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_FLOOR, OVM_TYPE_F64)); break; + case 0x9D: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_TRUNC, OVM_TYPE_F64)); break; + case 0x9E: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_NEAREST, OVM_TYPE_F64)); break; + case 0x9F: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_SQRT, OVM_TYPE_F64)); break; + case 0xA0: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_ADD, OVM_TYPE_F64)); break; + case 0xA1: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_SUB, OVM_TYPE_F64)); break; + case 0xA2: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MUL, OVM_TYPE_F64)); break; + case 0xA3: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_DIV, OVM_TYPE_F64)); break; + case 0xA4: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MIN, OVM_TYPE_F64)); break; + case 0xA5: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_MAX, OVM_TYPE_F64)); break; + case 0xA6: ovm_code_builder_add_binop(&ctx->builder, OVM_TYPED_INSTR(OVMI_COPYSIGN, OVM_TYPE_F64)); break; + + case 0xA7: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_I32)); break; + case 0xA8: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32_S, OVM_TYPE_I32)); break; + case 0xA9: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I32)); break; + case 0xAA: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64_S, OVM_TYPE_I32)); break; + case 0xAB: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I32)); break; + case 0xAC: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_I64)); break; + case 0xAD: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_I64)); break; + case 0xAE: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32_S, OVM_TYPE_I64)); break; + case 0xAF: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_I64)); break; + case 0xB0: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64_S, OVM_TYPE_I64)); break; + case 0xB1: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_I64)); break; + case 0xB2: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_F32)); break; + case 0xB3: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_F32)); break; + case 0xB4: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_F32)); break; + case 0xB5: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F32)); break; + case 0xB6: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F64, OVM_TYPE_F32)); break; + case 0xB7: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32_S, OVM_TYPE_F64)); break; + case 0xB8: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I32, OVM_TYPE_F64)); break; + case 0xB9: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_F64)); break; + case 0xBA: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F64)); break; + case 0xBB: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_F32, OVM_TYPE_F64)); break; + case 0xBC: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_TRANSMUTE_F32, OVM_TYPE_I32)); break; + case 0xBD: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_TRANSMUTE_F64, OVM_TYPE_I64)); break; + case 0xBE: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_TRANSMUTE_I32, OVM_TYPE_F32)); break; + case 0xBF: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_TRANSMUTE_I64, OVM_TYPE_F64)); break; + case 0xC0: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I8_S, OVM_TYPE_I32)); break; + case 0xC1: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I16_S, OVM_TYPE_I32)); break; + case 0xC2: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I8_S, OVM_TYPE_I64)); break; + case 0xC3: ovm_code_builder_add_unop (&ctx->builder, OVM_TYPED_INSTR(OVMI_CVT_I16_S, OVM_TYPE_I64)); 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)); + } +} + +static void parse_expression(build_context *ctx) { + while (bh_arr_length(ctx->builder.label_stack) > 0) { + parse_instruction(ctx); + } +} + +static void parse_code_section(build_context *ctx) { + unsigned int section_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int code_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + assert(ctx->module->functypes.size == code_count); + + ctx->module->memory_init_external_idx = ctx->next_external_func_idx++; + + // HACK HACK HACK THIS IS SUCH A BAD WAY OF DOING THIS + ctx->module->memory_init_idx = bh_arr_length(ctx->program->funcs) + code_count; + + fori (i, 0, (int) code_count) { + unsigned int code_size = uleb128_to_uint(ctx->binary.data, &ctx->offset); + unsigned int local_sections_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + + unsigned int total_locals = 0; + fori (j, 0, (int) local_sections_count) { + unsigned int local_count = uleb128_to_uint(ctx->binary.data, &ctx->offset); + wasm_valkind_t valtype = parse_valtype(ctx); + + total_locals += local_count; + } + + // Set up a lot of stuff... + + i32 func_idx = bh_arr_length(ctx->program->funcs); + i32 param_count = ctx->module->functypes.data[i]->type.func.params.size; + + debug_info_builder_begin_func(&ctx->debug_builder, func_idx); + + ctx->builder = ovm_code_builder_new(ctx->program, &ctx->debug_builder, param_count, total_locals); + ctx->builder.func_table_arr_idx = ctx->func_table_arr_idx; + + ovm_code_builder_push_label_target(&ctx->builder, label_kind_func); + parse_expression(ctx); + ovm_code_builder_add_return(&ctx->builder); + + char *func_name = bh_aprintf(bh_heap_allocator(), "wasm_loaded_%d", func_idx); + ovm_program_register_func(ctx->program, func_name, ctx->builder.start_instr, ctx->builder.param_count, ctx->builder.highest_value_number + 1); + + ovm_code_builder_free(&ctx->builder); + debug_info_builder_end_func(&ctx->debug_builder); + } + + ovm_program_register_external_func(ctx->program, "__internal_wasm_memory_init", 4, ctx->module->memory_init_external_idx); +} + + +static void parse_section(build_context *ctx) { + char section_number = CONSUME_BYTE(ctx); + + switch (section_number) { + case WASM_CUSTOM_SECTION: parse_custom_section(ctx); break; + case WASM_TYPE_SECTION: parse_type_section(ctx); break; + case WASM_IMPORT_SECTION: parse_import_section(ctx); break; + case WASM_FUNC_SECTION: parse_func_section(ctx); break; + case WASM_TABLE_SECTION: parse_table_section(ctx); break; + case WASM_MEMORY_SECTION: parse_memory_section(ctx); break; + case WASM_GLOBAL_SECTION: parse_global_section(ctx); break; + case WASM_EXPORT_SECTION: parse_export_section(ctx); break; + case WASM_START_SECTION: parse_start_section(ctx); break; + case WASM_ELEM_SECTION: parse_elem_section(ctx); break; + case WASM_CODE_SECTION: parse_code_section(ctx); break; + case WASM_DATA_SECTION: parse_data_section(ctx); break; + case WASM_DATAC_SECTION: parse_data_count_section(ctx); break; + default: assert(("bad section number", 0)); break; + } +} diff --git a/interpreter/src/wasm/ref.c b/interpreter/src/wasm/ref.c new file mode 100644 index 00000000..0ade447e --- /dev/null +++ b/interpreter/src/wasm/ref.c @@ -0,0 +1,2 @@ + +// TODO diff --git a/interpreter/src/wasm/store.c b/interpreter/src/wasm/store.c new file mode 100644 index 00000000..5c067916 --- /dev/null +++ b/interpreter/src/wasm/store.c @@ -0,0 +1,14 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_store_t *wasm_store_new(wasm_engine_t *engine) { + wasm_store_t *store = bh_alloc(engine->store->arena_allocator, sizeof(wasm_store_t)); + store->engine = engine; + store->instance = NULL; + return store; +} + +void wasm_store_delete(wasm_store_t *store) { +} + diff --git a/interpreter/src/wasm/table.c b/interpreter/src/wasm/table.c new file mode 100644 index 00000000..b878eba8 --- /dev/null +++ b/interpreter/src/wasm/table.c @@ -0,0 +1,16 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_table_t *wasm_table_new(wasm_store_t *store, const wasm_tabletype_t *type, wasm_ref_t *init) { + wasm_table_t *table = bh_alloc(store->engine->store->arena_allocator, sizeof(*table)); + table->inner.type = wasm_tabletype_as_externtype_const(type); + table->inner.table.type = type; + + return table; +} + +wasm_tabletype_t *wasm_table_type(const wasm_table_t *table) { + return (wasm_tabletype_t *) table->inner.table.type; +} + diff --git a/interpreter/src/wasm/trap.c b/interpreter/src/wasm/trap.c new file mode 100644 index 00000000..cb4ba746 --- /dev/null +++ b/interpreter/src/wasm/trap.c @@ -0,0 +1,42 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +wasm_trap_t *wasm_trap_new(wasm_store_t *store, const wasm_message_t *msg) { + wasm_trap_t *trap = bh_alloc(store->engine->store->arena_allocator, sizeof(*trap)); + trap->store = store; + trap->msg = *msg; + + // + // Generate frames + bh_arr(ovm_stack_frame_t) ovm_frames = store->instance->state->stack_frames; + int frame_count = bh_arr_length(ovm_frames); + + wasm_frame_vec_new_uninitialized(&trap->frames, frame_count); + + fori (i, 0, frame_count) { + ovm_stack_frame_t *ovm_frame = &ovm_frames[frame_count - 1 - i]; + wasm_frame_t *frame = bh_alloc(store->engine->store->arena_allocator, sizeof(*frame)); + frame->instance = store->instance; + frame->func_idx = ovm_frame->func->id; + frame->func_offset = 0; + frame->module_offset = 0; + + trap->frames.data[i] = frame; + } + + return trap; +} + +void wasm_trap_message(const wasm_trap_t *trap, wasm_message_t *out) { + *out = trap->msg; +} + +wasm_frame_t *wasm_trap_origin(const wasm_trap_t *trap) { + return trap->frames.data[0]; +} + +void wasm_trap_trace(const wasm_trap_t *trap, wasm_frame_vec_t *frames) { + frames->size = 0; + frames->data = NULL; +} diff --git a/interpreter/src/wasm/type.c b/interpreter/src/wasm/type.c new file mode 100644 index 00000000..8bc6298f --- /dev/null +++ b/interpreter/src/wasm/type.c @@ -0,0 +1,239 @@ + +#include "ovm_wasm.h" +#include "vm.h" + +// +// wasm_byte_t +// + +WASM_DECLARE_VEC_IMPL(byte, ) + +// +// wasm_valtype_t +// + +static wasm_valtype_t + valtype_i32 = { WASM_I32 }, + valtype_i64 = { WASM_I64 }, + valtype_f32 = { WASM_F32 }, + valtype_f64 = { WASM_F64 }, + valtype_anyref = { WASM_ANYREF }, + valtype_funcref = { WASM_FUNCREF }; + + +wasm_valtype_t *wasm_valtype_new(wasm_valkind_t kind) { + switch (kind) { + case WASM_I32: return &valtype_i32; + case WASM_I64: return &valtype_i64; + case WASM_F32: return &valtype_f32; + case WASM_F64: return &valtype_f64; + case WASM_ANYREF: return &valtype_anyref; + case WASM_FUNCREF: return &valtype_funcref; + default: assert(0); + } +} + +void wasm_valtype_delete(wasm_valtype_t *type) {} + +wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t *type) { + assert(type); + return type->kind; +} + +WASM_DECLARE_VEC_IMPL(valtype, *) + + +// wasm_functype_t + +wasm_functype_t *wasm_functype_new(wasm_valtype_vec_t *params, wasm_valtype_vec_t *results) { + wasm_functype_t *functype = malloc(sizeof(*functype)); + functype->type.kind = WASM_EXTERN_FUNC; + functype->type.func.params = *params; + functype->type.func.results = *results; + + return functype; +} + +void wasm_functype_delete(wasm_functype_t *functype) { + if (functype) free(functype); +} + +const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t *functype) { + return &functype->type.func.params; +} + +const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t *functype) { + return &functype->type.func.results; +} + +bool wasm_functype_equals(wasm_functype_t *a, wasm_functype_t *b) { + if (a->type.func.params.size != b->type.func.params.size) return false; + if (a->type.func.results.size != b->type.func.results.size) return false; + + fori (i, 0, (int) a->type.func.params.size) { + if (a->type.func.params.data[i]->kind != b->type.func.params.data[i]->kind) return false; + } + + fori (i, 0, (int) a->type.func.results.size) { + if (a->type.func.results.data[i]->kind != b->type.func.results.data[i]->kind) return false; + } + + return true; +} + +WASM_DECLARE_VEC_IMPL(functype, *) + + +// wasm_globaltype_t + +wasm_globaltype_t *wasm_globaltype_new(wasm_valtype_t *valtype, wasm_mutability_t mut) { + wasm_globaltype_t *globaltype = malloc(sizeof(*globaltype)); + globaltype->type.kind = WASM_EXTERN_GLOBAL; + globaltype->type.global.content = valtype; + globaltype->type.global.mutability = mut; + + return globaltype; +} + +void wasm_globaltype_delete(wasm_globaltype_t *globaltype) { + if (globaltype) free(globaltype); +} + +const wasm_valtype_t* wasm_globaltype_content(const wasm_globaltype_t *globaltype) { + return globaltype->type.global.content; +} + +wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t *globaltype) { + return globaltype->type.global.mutability; +} + +WASM_DECLARE_VEC_IMPL(globaltype, *) + + +// wasm_tabletype_t + +wasm_tabletype_t *wasm_tabletype_new(wasm_valtype_t *valtype, const wasm_limits_t *limits) { + wasm_tabletype_t *tabletype = malloc(sizeof(*tabletype)); + tabletype->type.kind = WASM_EXTERN_TABLE; + tabletype->type.table.element = valtype; + tabletype->type.table.limits = *limits; + + return tabletype; +} + +void wasm_tabletype_delete(wasm_tabletype_t *tabletype) { + if (tabletype) free(tabletype); +} + +const wasm_valtype_t* wasm_tabletype_element(const wasm_tabletype_t *tabletype) { + return tabletype->type.table.element; +} + +const wasm_limits_t* wasm_tabletype_limits(const wasm_tabletype_t *tabletype) { + return &tabletype->type.table.limits; +} + +WASM_DECLARE_VEC_IMPL(tabletype, *) + +// wasm_memorytype_t + +wasm_memorytype_t *wasm_memorytype_new(const wasm_limits_t *limits) { + wasm_memorytype_t *memorytype = malloc(sizeof(*memorytype)); + memorytype->type.kind = WASM_EXTERN_MEMORY; + memorytype->type.memory.limits = *limits; + + return memorytype; +} + +void wasm_memorytype_delete(wasm_memorytype_t *memorytype) { + if (memorytype) free(memorytype); +} + +const wasm_limits_t* wasm_memorytype_limits(const wasm_memorytype_t *memorytype) { + return &memorytype->type.memory.limits; +} + +WASM_DECLARE_VEC_IMPL(memorytype, *) + +// wasm_externtype_t + +const wasm_externkind_t wasm_externtype_kind(const wasm_externtype_t *externtype) { + return externtype->kind; +} + +WASM_DECLARE_VEC_IMPL(externtype, *) + +wasm_externtype_t* wasm_functype_as_externtype(wasm_functype_t* ext) { return (wasm_externtype_t *) ext; } +wasm_externtype_t* wasm_globaltype_as_externtype(wasm_globaltype_t* ext) { return (wasm_externtype_t *) ext; } +wasm_externtype_t* wasm_tabletype_as_externtype(wasm_tabletype_t* ext) { return (wasm_externtype_t *) ext; } +wasm_externtype_t* wasm_memorytype_as_externtype(wasm_memorytype_t* ext) { return (wasm_externtype_t *) ext; } + +wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_FUNC ? (wasm_functype_t *) ext : NULL; } +wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_GLOBAL ? (wasm_globaltype_t *) ext : NULL; } +wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_TABLE ? (wasm_tabletype_t *) ext : NULL; } +wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_MEMORY ? (wasm_memorytype_t *) ext : NULL; } + +const wasm_externtype_t* wasm_functype_as_externtype_const(const wasm_functype_t* ext) { return (const wasm_externtype_t *) ext; } +const wasm_externtype_t* wasm_globaltype_as_externtype_const(const wasm_globaltype_t* ext) { return (const wasm_externtype_t *) ext; } +const wasm_externtype_t* wasm_tabletype_as_externtype_const(const wasm_tabletype_t* ext) { return (const wasm_externtype_t *) ext; } +const wasm_externtype_t* wasm_memorytype_as_externtype_const(const wasm_memorytype_t* ext) { return (const wasm_externtype_t *) ext; } + +const wasm_functype_t* wasm_externtype_as_functype_const(const wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_FUNC ? (const wasm_functype_t *) ext : NULL; } +const wasm_globaltype_t* wasm_externtype_as_globaltype_const(const wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_GLOBAL ? (const wasm_globaltype_t *) ext : NULL; } +const wasm_tabletype_t* wasm_externtype_as_tabletype_const(const wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_TABLE ? (const wasm_tabletype_t *) ext : NULL; } +const wasm_memorytype_t* wasm_externtype_as_memorytype_const(const wasm_externtype_t* ext) { return ext->kind == WASM_EXTERN_MEMORY ? (const wasm_memorytype_t *) ext : NULL; } + + + +// wasm_importtype_t + +wasm_importtype_t *wasm_importtype_new(wasm_name_t *module, wasm_name_t* name, wasm_externtype_t *ext) { + wasm_importtype_t *importtype = malloc(sizeof(*importtype)); + importtype->module_name = *module; + importtype->import_name = *name; + importtype->type = ext; + + return importtype; +} + +void wasm_importtype_delete(wasm_importtype_t *importtype) { + if (importtype) free(importtype); +} + +const wasm_name_t* wasm_importtype_module(const wasm_importtype_t* importtype) { + return &importtype->module_name; +} + +const wasm_name_t* wasm_importtype_name(const wasm_importtype_t* importtype) { + return &importtype->import_name; +} + +const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t* importtype) { + return importtype->type; +} + +WASM_DECLARE_VEC_IMPL(importtype, *) + +// wasm_exporttype_t + +wasm_exporttype_t *wasm_exporttype_new(wasm_name_t* name, wasm_externtype_t *ext) { + wasm_exporttype_t *exporttype = malloc(sizeof(*exporttype)); + exporttype->name = *name; + exporttype->type = ext; + + return exporttype; +} + +void wasm_exporttype_delete(wasm_exporttype_t *exporttype) { + if (exporttype) free(exporttype); +} + +const wasm_name_t* wasm_exporttype_name(const wasm_exporttype_t* exporttype) { + return &exporttype->name; +} + +const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t* exporttype) { + return exporttype->type; +} + +WASM_DECLARE_VEC_IMPL(exporttype, *) diff --git a/interpreter/src/wasm/value.c b/interpreter/src/wasm/value.c new file mode 100644 index 00000000..a533c941 --- /dev/null +++ b/interpreter/src/wasm/value.c @@ -0,0 +1,24 @@ + +#include "ovm_wasm.h" + +void wasm_val_delete(wasm_val_t* v) { + // Apparently this is suppose to do nothing... +} + +void wasm_val_copy(wasm_val_t* out, const wasm_val_t* in) { + out->kind = in->kind; + switch (out->kind) { + case WASM_I32: out->of.i32 = in->of.i32; break; + case WASM_I64: out->of.i64 = in->of.i64; break; + case WASM_F32: out->of.f32 = in->of.i32; break; + case WASM_F64: out->of.f64 = in->of.f64; break; + case WASM_ANYREF: + case WASM_FUNCREF: + out->of.ref = in->of.ref; + break; + } +} + +WASM_DECLARE_VEC_IMPL(val, ) + + diff --git a/interpreter/src/wasm_cli_test.c b/interpreter/src/wasm_cli_test.c new file mode 100644 index 00000000..8903f272 --- /dev/null +++ b/interpreter/src/wasm_cli_test.c @@ -0,0 +1,51 @@ +#define BH_DEFINE +#include "bh.h" +#include "wasm.h" + +#include "ovm_wasm.h" +#include "vm.h" + + +int main(int argc, char *argv[]) { + wasm_config_t *config = wasm_config_new(); + + // TODO: Add this to public header + void wasm_config_enable_debug(wasm_config_t *, bool); + wasm_config_enable_debug(config, true); + + wasm_engine_t *engine = wasm_engine_new_with_config(config); + + wasm_store_t *store = wasm_store_new(engine); + + wasm_byte_vec_t wasm_bytes; + { + bh_file_contents contents = bh_file_read_contents(bh_heap_allocator(), argv[1]); + + wasm_bytes.size = contents.length; + wasm_bytes.data = contents.data; + } + + wasm_module_t *module = wasm_module_new(store, &wasm_bytes); + assert(module); + + wasm_importtype_vec_t imports; + wasm_module_imports(module, &imports); + + fori (i, 0, (int) imports.size) { + const wasm_name_t *module_name = wasm_importtype_module(imports.data[i]); + const wasm_name_t *import_name = wasm_importtype_name(imports.data[i]); + bh_printf("imports: %b.%b\n", module_name->data, module_name->size, import_name->data, import_name->size); + } + + + wasm_exporttype_vec_t exports; + wasm_module_exports(module, &exports); + + fori (i, 0, (int) exports.size) { + const wasm_name_t *export_name = wasm_exporttype_name(exports.data[i]); + 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, bh_arr_length(module->program->code)); +} + diff --git a/settings.sh b/settings.sh index 44d45f84..1b1ad45f 100644 --- a/settings.sh +++ b/settings.sh @@ -15,8 +15,8 @@ CC='gcc' # The architecture of your system. If your not sure, leave this alone. ARCH="$(uname -m)" -# RUNTIME_LIBRARY="ovmwasm" -RUNTIME_LIBRARY="wasmer" +RUNTIME_LIBRARY="ovmwasm" +# RUNTIME_LIBRARY="wasmer" # Where the Wasmer library files can be found. # They are bundled with the project, but if a different version is available, these can be changed. diff --git a/shared/include/bh.h b/shared/include/bh.h index 0049b269..96d6e639 100644 --- a/shared/include/bh.h +++ b/shared/include/bh.h @@ -1,6 +1,17 @@ #ifndef BH_H #define BH_H +#ifdef BH_STATIC + #define BH_DEF static +#else + #ifdef BH_INTERNAL + #define BH_DEF __attribute__((visibility("hidden"))) + #else + #define BH_DEF + #endif +#endif + + // NOTE: For lseek64 #define _LARGEFILE64_SOURCE @@ -21,6 +32,7 @@ #include #include #include + #include #endif #include @@ -168,6 +180,7 @@ u8* float_to_ieee754(f32 f, b32 reverse); u8* double_to_ieee754(f64 f, b32 reverse); u64 uleb128_to_uint(u8* bytes, i32 *byte_walker); +BH_DEF i64 leb128_to_int(u8* bytes, i32 *byte_count); @@ -261,11 +274,31 @@ typedef struct bh__arena_internal { void* data; // Not actually a pointer, just used for the offset } bh__arena_internal; -void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size); -void bh_arena_free(bh_arena* alloc); -bh_allocator bh_arena_allocator(bh_arena* alloc); -BH_ALLOCATOR_PROC(bh_arena_allocator_proc); +BH_DEF void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size); +BH_DEF void bh_arena_clear(bh_arena* alloc); +BH_DEF void bh_arena_free(bh_arena* alloc); +BH_DEF bh_allocator bh_arena_allocator(bh_arena* alloc); +BH_DEF BH_ALLOCATOR_PROC(bh_arena_allocator_proc); + + +// ATOMIC ARENA ALLOCATOR +typedef struct bh_atomic_arena { + bh_allocator backing; + ptr first_arena, current_arena; + isize size, arena_size; // in bytes + + pthread_mutex_t mutex; +} bh_atomic_arena; + +typedef struct bh__atomic_arena_internal { + ptr next_arena; + void* data; // Not actually a pointer, just used for the offset +} bh__atomic_arena_internal; +BH_DEF void bh_atomic_arena_init(bh_atomic_arena* alloc, bh_allocator backing, isize arena_size); +BH_DEF void bh_atomic_arena_free(bh_atomic_arena* alloc); +BH_DEF bh_allocator bh_atomic_arena_allocator(bh_atomic_arena* alloc); +BH_DEF BH_ALLOCATOR_PROC(bh_atomic_arena_allocator_proc); @@ -518,16 +551,17 @@ typedef struct bh_buffer { #define BH_BUFFER_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 16) #endif -void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 length); -void bh_buffer_free(bh_buffer* buffer); -void bh_buffer_clear(bh_buffer* buffer); -void bh_buffer_grow(bh_buffer* buffer, i32 length); -void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length); -void bh_buffer_concat(bh_buffer* buffer, bh_buffer other); -void bh_buffer_write_byte(bh_buffer* buffer, u8 byte); -void bh_buffer_write_u32(bh_buffer* buffer, u32 i); -void bh_buffer_write_u64(bh_buffer* buffer, u64 i); -void bh_buffer_align(bh_buffer* buffer, u32 alignment); +BH_DEF void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 length); +BH_DEF void bh_buffer_free(bh_buffer* buffer); +BH_DEF void bh_buffer_clear(bh_buffer* buffer); +BH_DEF void bh_buffer_grow(bh_buffer* buffer, i32 length); +BH_DEF void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length); +BH_DEF void bh_buffer_concat(bh_buffer* buffer, bh_buffer other); +BH_DEF void bh_buffer_write_byte(bh_buffer* buffer, u8 byte); +BH_DEF void bh_buffer_write_u32(bh_buffer* buffer, u32 i); +BH_DEF void bh_buffer_write_u64(bh_buffer* buffer, u64 i); +BH_DEF void bh_buffer_write_string(bh_buffer* buffer, char *str); +BH_DEF void bh_buffer_align(bh_buffer* buffer, u32 alignment); @@ -598,6 +632,7 @@ typedef struct bh__arr { #define bh_arr_deleten(arr, i, n) (bh__arr_deleten((void **) &(arr), sizeof(*(arr)), i, n)) #define bh_arr_fastdelete(arr, i) (arr[i] = arr[--bh__arrhead(arr)->length]) +#define bh_arr_fastdeleten(arr, n) (bh__arrhead(arr)->length -= n) #define bh_arr_each(T, var, arr) for (T* var = (arr); !bh_arr_end((arr), var); var++) #define bh_arr_rev_each(T, var, arr) for (T* var = &bh_arr_last((arr)); !bh_arr_start((arr), var); var--) @@ -1038,6 +1073,22 @@ void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size) { ((bh__arena_internal *)(alloc->first_arena))->next_arena = NULL; } +BH_DEF void bh_arena_clear(bh_arena* alloc) { + bh__arena_internal *walker = (bh__arena_internal *) alloc->first_arena; + walker = walker->next_arena; + if (walker != NULL) { + bh__arena_internal *trailer = walker; + while (walker != NULL) { + walker = walker->next_arena; + bh_free(alloc->backing, trailer); + trailer = walker; + } + } + + alloc->current_arena = alloc->first_arena; + alloc->size = sizeof(ptr); +} + void bh_arena_free(bh_arena* alloc) { bh__arena_internal *walker = (bh__arena_internal *) alloc->first_arena; bh__arena_internal *trailer = walker; @@ -1106,6 +1157,93 @@ BH_ALLOCATOR_PROC(bh_arena_allocator_proc) { } +// ATOMIC ARENA ALLOCATOR IMPLEMENTATION +BH_DEF void bh_atomic_arena_init(bh_atomic_arena* alloc, bh_allocator backing, isize arena_size) { + arena_size = bh_max(arena_size, size_of(ptr)); + ptr data = bh_alloc(backing, arena_size); + + alloc->backing = backing; + alloc->arena_size = arena_size; + alloc->size = sizeof(ptr); + alloc->first_arena = data; + alloc->current_arena = data; + pthread_mutex_init(&alloc->mutex, NULL); + + ((bh__arena_internal *)(alloc->first_arena))->next_arena = NULL; +} + +BH_DEF void bh_atomic_arena_free(bh_atomic_arena* alloc) { + bh__atomic_arena_internal *walker = (bh__atomic_arena_internal *) alloc->first_arena; + bh__atomic_arena_internal *trailer = walker; + while (walker != NULL) { + walker = walker->next_arena; + bh_free(alloc->backing, trailer); + trailer = walker; + } + + alloc->first_arena = NULL; + alloc->current_arena = NULL; + alloc->arena_size = 0; + alloc->size = 0; + pthread_mutex_destroy(&alloc->mutex); +} + +BH_DEF bh_allocator bh_atomic_arena_allocator(bh_atomic_arena* alloc) { + return (bh_allocator) { + .proc = bh_atomic_arena_allocator_proc, + .data = alloc, + }; +} + +BH_DEF BH_ALLOCATOR_PROC(bh_atomic_arena_allocator_proc) { + bh_atomic_arena* alloc_arena = (bh_atomic_arena*) data; + pthread_mutex_lock(&alloc_arena->mutex); + + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { + bh_align(size, alignment); + bh_align(alloc_arena->size, alignment); + + if (size > alloc_arena->arena_size - size_of(ptr)) { + // Size too large for the arena + break; + } + + if (alloc_arena->size + size >= alloc_arena->arena_size) { + bh__arena_internal* new_arena = (bh__arena_internal *) bh_alloc(alloc_arena->backing, alloc_arena->arena_size); + + if (new_arena == NULL) { + bh_printf_err("Arena Allocator: couldn't allocate new arena"); + break; + } + + new_arena->next_arena = NULL; + ((bh__arena_internal *)(alloc_arena->current_arena))->next_arena = new_arena; + alloc_arena->current_arena = new_arena; + alloc_arena->size = sizeof(ptr); + } + + retval = bh_pointer_add(alloc_arena->current_arena, alloc_arena->size); + alloc_arena->size += size; + } break; + + case bh_allocator_action_resize: { + // Do nothing since this is a fixed allocator + } break; + + case bh_allocator_action_free: { + // Do nothing since this allocator isn't made for freeing memory + } break; + } + + pthread_mutex_unlock(&alloc_arena->mutex); + return retval; +} + + + // SCRATCH ALLOCATOR IMPLEMENTATION @@ -1285,6 +1423,27 @@ u64 uleb128_to_uint(u8* bytes, i32 *byte_count) { return res; } +BH_DEF i64 leb128_to_int(u8* bytes, i32 *byte_count) { + i64 res = 0; + u64 shift = 0; + u64 size = 64; + + u8 byte; + do { + byte = bytes[(*byte_count)++]; + res |= (byte & 0x7f) << shift; + shift += 7; + } while ((byte & 0x80) != 0); + + if ((shift < size) && (byte & 0x40) != 0) { + i64 zero_shifted = ~ 0x0; + zero_shifted = zero_shifted << shift; + return res | zero_shifted; + } + + return res; +} + //------------------------------------------------------------------------------------- @@ -2237,6 +2396,11 @@ void bh_buffer_write_u64(bh_buffer* buffer, u64 i) { buffer->length += 8; } +BH_DEF void bh_buffer_write_string(bh_buffer* buffer, char *str) { + u32 len = strlen(str); + bh_buffer_append(buffer, (const char *) str, len); +} + void bh_buffer_align(bh_buffer* buffer, u32 alignment) { if (buffer->length % alignment != 0) { u32 difference = alignment - (buffer->length % alignment); diff --git a/shared/lib/linux_x86_64/lib/libovmwasm.so b/shared/lib/linux_x86_64/lib/libovmwasm.so index d1d39b4a..27198c91 100755 Binary files a/shared/lib/linux_x86_64/lib/libovmwasm.so and b/shared/lib/linux_x86_64/lib/libovmwasm.so differ