From 95799e2976dcc733517172a0092f502612c68a5f Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Mon, 5 Sep 2022 11:45:41 -0500 Subject: [PATCH] added interpreter to this repo --- build.sh | 32 +- interpreter/build.sh | 15 + interpreter/include/assembler.h | 36 + interpreter/include/ovm_debug.h | 304 +++++ interpreter/include/ovm_wasm.h | 260 ++++ interpreter/include/vm.h | 345 +++++ interpreter/include/vm_codebuilder.h | 90 ++ interpreter/src/debug/debug_host.c | 65 + interpreter/src/debug/debug_info.c | 236 ++++ interpreter/src/debug/debug_info_builder.c | 158 +++ interpreter/src/debug/debug_runtime_values.c | 279 ++++ interpreter/src/debug/debug_thread.c | 544 ++++++++ interpreter/src/ovm_cli_test.c | 53 + interpreter/src/vm/code_builder.c | 555 ++++++++ interpreter/src/vm/program_loader.c | 126 ++ interpreter/src/vm/vm.c | 1276 ++++++++++++++++++ interpreter/src/wasm.c | 8 + interpreter/src/wasm/config.c | 18 + interpreter/src/wasm/engine.c | 42 + interpreter/src/wasm/extern.c | 35 + interpreter/src/wasm/frame.c | 37 + interpreter/src/wasm/func.c | 61 + interpreter/src/wasm/global.c | 29 + interpreter/src/wasm/instance.c | 349 +++++ interpreter/src/wasm/memory.c | 42 + interpreter/src/wasm/module.c | 101 ++ interpreter/src/wasm/module_parsing.h | 984 ++++++++++++++ interpreter/src/wasm/ref.c | 2 + interpreter/src/wasm/store.c | 14 + interpreter/src/wasm/table.c | 16 + interpreter/src/wasm/trap.c | 42 + interpreter/src/wasm/type.c | 239 ++++ interpreter/src/wasm/value.c | 24 + interpreter/src/wasm_cli_test.c | 51 + settings.sh | 4 +- shared/include/bh.h | 192 ++- shared/lib/linux_x86_64/lib/libovmwasm.so | Bin 222344 -> 230808 bytes 37 files changed, 6636 insertions(+), 28 deletions(-) create mode 100755 interpreter/build.sh create mode 100644 interpreter/include/assembler.h create mode 100644 interpreter/include/ovm_debug.h create mode 100644 interpreter/include/ovm_wasm.h create mode 100644 interpreter/include/vm.h create mode 100644 interpreter/include/vm_codebuilder.h create mode 100644 interpreter/src/debug/debug_host.c create mode 100644 interpreter/src/debug/debug_info.c create mode 100644 interpreter/src/debug/debug_info_builder.c create mode 100644 interpreter/src/debug/debug_runtime_values.c create mode 100644 interpreter/src/debug/debug_thread.c create mode 100644 interpreter/src/ovm_cli_test.c create mode 100644 interpreter/src/vm/code_builder.c create mode 100644 interpreter/src/vm/program_loader.c create mode 100644 interpreter/src/vm/vm.c create mode 100644 interpreter/src/wasm.c create mode 100644 interpreter/src/wasm/config.c create mode 100644 interpreter/src/wasm/engine.c create mode 100644 interpreter/src/wasm/extern.c create mode 100644 interpreter/src/wasm/frame.c create mode 100644 interpreter/src/wasm/func.c create mode 100644 interpreter/src/wasm/global.c create mode 100644 interpreter/src/wasm/instance.c create mode 100644 interpreter/src/wasm/memory.c create mode 100644 interpreter/src/wasm/module.c create mode 100644 interpreter/src/wasm/module_parsing.h create mode 100644 interpreter/src/wasm/ref.c create mode 100644 interpreter/src/wasm/store.c create mode 100644 interpreter/src/wasm/table.c create mode 100644 interpreter/src/wasm/trap.c create mode 100644 interpreter/src/wasm/type.c create mode 100644 interpreter/src/wasm/value.c create mode 100644 interpreter/src/wasm_cli_test.c 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 d1d39b4aa7427d33ff1442fc1b21544cb389c190..27198c9133125fe7b68ef47d6c514696ea179de3 100755 GIT binary patch literal 230808 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�`d?-GczTLY;X=?md#*0>eJ%3Qb_+O%ZgJ=H)F<=Z zB=bFVt2-b2P?xPepd(SVP1ao|^PV-uotJbH^&U*L-K@<&QRZ2ByE~77A=AGU!r;Wg zczh+@e!NH<)B-%NCh?7MUamKMr7!(QzVI==@Q&lWUa~M||PUx;Om}U-;lqZ+g8i+|w6+^ipsBzP@mCg*W|KAH5A9 zy<ZA8qAH7R_^!~iB7v3@-y+`=yorg^+H+>fS=pFIV`#Z;Y z^A{cK4WH^u|Ei}qJ?2aQzAqf`h2Qg)|1V$qG+%gxFI?;k=lR0BeehrNg`f6?{q0@c z%iF(tAN<9>`Jmo6K5QTV-*|-=Ud$)o0zP@REaJ^y;*&?l#oqKhAHV*)@@b=wU%M)w z`kw7=-!>n=Hv9N>gpXeXXZXsW?hW5@nl~Ic)f;~B6mR&9KHl)NCws$@lf2=@(4E}l zr>}3kEc4NSmT$b+K7Lsr^1>_g@n5XBH+_U}ymGDOZqFp2eD34Z2NqxH&2LnC!x0}p z8ov26GR&L*pilmm`S|f!A3vVq8ee!CSk3ahO_@mi}?@52Jwb($zb_yj>zu_ z_1#9fg7jJH+edwuT_WRS)T@|I{SM!Q3eKbITc*B!y36!HP=3Yw0Qt93j^VqJ_+xp> zEqotU-!k>xNcn~M8`bx7h0l8R?W4ZS$j8VxR((fMP9fZv@(JIA3NITKUKT67800U) zr`*A}gz^U8p5K$-%?j^}72YG1CrHoZxWsp{qSFXPzcNMV<{X(`ruyAeeV> zw#+wHeK!VV_zv~8ov(r$IbEg;xrX{5RPC5WxxjW&9^fnF6uv^g#CJ2tJ-+J|-ELI) zi7EW#QEnssbA_K}3O|o3{M0M_JWBpYzOjmaPeo;T(h&LWtLT15nSw`of&7!sli!W1 z{z4w5jPlca56n=ypMf!t^KC={l%6=%}opFx*Zd3SKukiD%!p|KFKVubs z`Y67+BVU&5d4l{dR{dJ6_|;JTbH;Vqr83_^)!$e|l~?jW%m--aX3hinI&xu|l1mPK z%T)WFab+ufJnN%diQ;El@$q`q-WyeW532S)NWBmB^;Pt2cI2j_=lWx0x-%|6?=HiR z+$qVH;blSj74taYEmQDDDE_b&yn4kS+tfIp6_Vw8D!89J_)~Igo5IJ9ik|BgJ|Zf< zS@C}#6)*I9;!okJMA7+kH4Zl`IzOuDT(9UnNzu8lqH~_2^G3zL%M?Cij(kx4E>m** zDK#H$R_%Ujuds@!E+xW)?4n0P5DX23m>bh!`5nY*p;-VVztz6bD)VMC)DcOGNd zyq!wxJUvFH?+5GR{Uw?Idiy+FLP_seM@B6#M*Zp-8IHcnXE-BcnF*&jmgXXLSt0)5 zF%BGT-~;okTgn-Ms|>u&fGY#?VaIO(SpDW>SWB~lD9Pg1hv}3UZ#+bn)*)Knd}{Hg z;jJH$#$#&M(@aBmc%2hHlK7%azT~`+b$S(d>^`wh!Z(IZtX!*?`c+_M^B4AZir5hN zRw%_TTF8FvMszB@1tIP~%lpP}m247spwpMBhl(4RIXFO|&W*f=lm9rr&ZB|xrXttj_XN0tbQ6cKWmo? ze0lb%|0ee5P?)zSYp#ys&`bp%x#Sfn*S*l2oCmbGTT^9aI8!dO#;P{5zPK@H12G!K zt7XCoquP`u&T;<;_(tbE?=7|dA$5L(_@~cSb6$aXgQdL&w+x52MauTf#m?w{yaRQh z?5g10UO8~(54Nba9qmt?l7zO9pe!=}!W`-a;fIKKp+oU&hY zfwGSMYP}~tKvMnTxvBLIxjOWRHw2q%?uwe@+IX87SF6Alw}5oG(u_w|W&fPbd|W9G z`TDo!%A@wLP!g>=O*9w9c&j8;tJBA6pxsB-FMzs^eH66AKBnj&T_5OGyX3AboIXBq zFYt(``Y2}1lS{U=UYp{7&wUk)Z}m_bE`tdv_ss|j9bB1I@2Tm=W^S%>D_cB|h8x2B zBHHbESO8a#v-Pspn$ywj+NZ29fB-jdi6I9*Bj!--D%ZXhF;9+}SA?+5UGXKiKP$|k`76k1)*eg{gxmUDnqT6$TgndS2%+u`LXrCu;-NY*vb^0`Uv6O%KOm+~iE~N^I(SwoAM_=V$b)7T1&I5>;T>xpzV4 zfJfrE$9zcujw;HrV|{(dyc=j=gLj*2ztG{80B;4phYys4l=7_Nw zezM_U=df(}Xivje2dtHt&}G9XgbQka?rXF&e+c@HTCStJ{`vS>@K#01`p8mrxt%`0 z3YL8j*_QK+Atq$~C48ZBPy$RXwx*F2F(rmd_ER+|wN_roxpHd^XBW$v6AcJ)Ms=af z84sht`05D4cXe|{F{FK|wf$;RTTJsTidhwH9{AT4TE5Dr+9I*=UE=OA9F5h4*n z?ruknMcrqTg2-l$W@%AOlpv7EQZ_YE2^UeL;KKsQFJ{xNH-;1J-Q~QlkGrHhtRa|U z7=icYsv)ri&46bdyhyukNS#|*D;k?Vw^GZAD0Ti;Fb?&-c8(Rkx4B>bD6~G4KZ>l6 zgy-W*y?fj`2*1$>!Q7?#A?!8m(z zX)&G{Ll&w}k+L_VT6`y-X~LmUIG6~$6E}o77d)6Ax#*wO3dG3MIIX`|{7Ai`c?V?5 zj3V8<3YB;F`hVw@RE!5v+03@G19S775&>Lv#~C0!l(IiM@-y|C68B>Z$2XBA!;PGY<6>l&$?$;V@_}8qPRhp$W zEW|JKjzNjT?KOSC+J*6|p>0>-BHu7`PK$Nn`C8)?WFV`Q!%22Hn!FPO6yDZnR$70( zf;dD1b#ZV`rHK6%Vi8$chRn6c;nyhq(zS+@>CqF-SsN+MYKcTCD_bH#YC7huQOku$ zNDdr41!q=s`jnKjuVoS0faI2r6C!qMzdU`VKSY3C)71JmNq{Uz>2iKy{T&X8Vm{u`2u zXFvgp4Qjx;ON~z|#+HT@VkKMzLxwwpQqZ4+}{pv;<^Dk^q9^g~fz~dU(P$Ibgb2 z$t17E%d8C@Cbdnt~P z4Q@k2AOS?w7sy{Kdsx#uaZ7n%R*#m!gFyWD3iNqF0OysWfek=WO}J9Bx0W}cyKR3h z_-uI97L>=8B@gA5`zIhA)OV2{=T9K#zed=$iI6C!5 zirS7!{p-`z-+~wQAYuMLO8uO#GE=|xkf1)M2OxGf7P{}|jO=CJA&yML2U5S2@k+2U z0<0!HBWZp6h2b<{BT@#3;^y(@E&1l1VAsL96ScW3`{JqEsu8?o-1w&m=9nS!eliJu z2~eJ>7A#pVSP~P3cn=s(HF1g+4#sxcp9FdPAD%|=dNIpI(q_!xrc6>1A2OL7J$^CADY2hCBrp`84~h|M(z{0y;q0$gAN9A`qU zhW$tak!Xxn;el1umcvPfi=H94*nWF|Ew5NOBt>6iRj)^l4MO^geDDz^^5r7m*;-za z9aoJSg4t;^IiG#hn-3NSnvM1BM!YI|OlWYjR{@4L!$y;~q~;jB(E>PCbFv}7SH^PY zK)-(i3@y-Mx3$TRb_x?mo>MR>DB!jV;7ovx;Z40#6H{CaeoITKjo_I1OehT!^5Y8f zNL&xOEEiMY@zKD$c$$`t0V2?;U>+8>0lO9%^{Th*yYLnBa{W%IA%VmpoEz=Kv}Erm zhH>H#V&Y#Xd;>Lb*2xn2vPHhn4~Tph#NGKsdX7lnDAV5^?nys%E6(}OD5^gMVBKbv zi~K>6f3eJe%N3sdY*+Whp*0!3*Lo=qc z45PBrehLZc^^(%Fp>Ln+w6P;3{P!(!7=&q#>y-Ax#? zr;FjrLB3bl&TgV8UU2gutjCkV{(ve{g}?)h@{s*|1w*zKFg|isy;nPXg1^BiHZEjO z^A~G(6{DDC<%hgoMah{VyUbs5i>o9{U6-N8F(Esk%8itwRN$48G&$fH$j?)v0mvz= zA}K>R!8mhkacHl0{jDL0Qix`!!$8`yeQsR4>(+ATcUHuUdvU!Hy({`Ovgkup&$S~(paV$SV-4?@~KV=koa z`doN-D_W`DI}9O|&O;~sWPN?FyO$s<)Hl;5%cJwgi*8gE1N;?2%mt_4Cd^g}zOPMs+&_f;im>Ik;cl zBXRNVJJe|_?&&h9|LUwm=XV*@!<~Wpba#gi_EHoZ0E$ub*%92F28`sCyn-0``F{k;h$BHETZ;BLE#aJX9ovx-BXR^E z+hr(YGC*1L=@Iafu`OdWK$)kYq*R6dF6jy_r6$9~jWw07ijk<7UYXd5uC=VI z1r~I*z|~zXa8{=Uw3jfkPBh;1DpG>iKi}C&$F(J$mU&T?amELbB?S|Qg!X(*h)Yp9 zmZ?T*&&N|7Iq~(iSR_w0z7&<$bXxiIsC=LH!X`ADQ*D}`MY%z~24tLJwdd!ALM7`b zmdzCmA^Y+!w^GdmzE*bk!RZp&UUQM{ zMXRf5`(;M7)haj=ZNM0j#m-~EW{0#Fn-Y7z3*@9xA`L8%&=+>O1!|V{p=94DUG0g| zA$)M2?lPRmGh(Ew%W!^{5zcqI4Ck?oa9-chRs1}Z5svAC<&u+PU4% z08sXFgO;fO0ru?!8v#OYUck05Shq4{S9yTAiJ+~>-29TxN?=~`FC+vFvN|iEYcFLk z@!AKSa0d2RGFI_RRe&hv=o4Z|8GwLQSa@hspm`G(V5r8gzYKjD@(Gb?RE$wTMgquW zp;HzDlGr-{aZZ6MNuglQ=@zo?#-T1MkWeNI^hbmqSttxjQcHO=ZouV>Q4z9(U2WLP z3=LcHeg<@ao~+DR52c;`O7{g@h6X5WWGclX+Kb4K;wfH8?ENm;L6sY!$`K8n8q;)B zsQUU5RQ;Y^hLc&ofxAkJ>|cJFgcWA z%pTiSAh5u9kVtFaO{3u34aLlhuDJ@1#K^bk(e@0DDU(3Fiv8LwoFj%lf2ykn*or8C zgF@SMbTT#TzfE0Je^fs($FFVb#)y#p=4RPpHuT$ramwcPX^qr< zb*cMy05kMo0^?SpMW?b97{}5j^c*=k4d{~+<5Upk3`I$P>?mr6sag8PcO+iA0^&?B zh`as;5I^>UIR8i>IJ1b(sBU0((f-Wx>4}N6(!RI5-jj2@&3L9Ghyi`n%0}W7sZ=B!IYdMm4teBwknz~ zCV=ov%euGHgCWB%F$>FG;xkWoc8R;m7H|cZYC*jMBYDTyb|Qy>R(cJUsWwDaVb|_y zx6U?ip?Sjaw@>>CR0B_U;I$ClIrygR&bI_rLr~XaQRo4+Ux50P0+q2#`;LUu8c#_V z5z+XX^d|B?ZNz@HFw^GBIxp~)^r7*HERf|Y>D@U-rPw*<#&Iv~6jxt_onmYo_rdRR z{C&^wPsL5Xb2o}Ta z%=4y)vpGDf+nX1rI_NaZ>EvBUgo1UFGZKg^56|JS8<2EChw^AiYBQXa6eFo0&gfmU zQQ!v~LtM(8sH0Ier!#lvJ@HDUvr_`NQTI!moS}0BmE4WXPPZj%{$r5N?g**l zUa7lHo$k7B-Ppz6bqeWc|1n7Mj*yPryG}8l-Bn1P_HOBhF5;=v-S;UjNIun?6w9nz ziA-CEacvUHO4r};NxAs{J6_PypMqNha}0r_{A6`
fi_0&V69{RIZ@ua@6>3Ysiwm;t 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 Bu?+wK literal 222344 zcmeFadt6ji_dk9HLB{K#Vp3UR;S-b^lopibjDnu2p)e&aGem?WW)S=9i^2 zI8FalA~>cx!szQv`h#mG`)hoD^lvVO{we2U^+km645rVvuQ^%&l=F2*QtLjRuk}>N z&pa#77w_VqdOqT!eAfM(-+H>^XN^#n{wa!4NJhF>a`rPmqmf2eH2aJ4#iyQclgMZO zj1heLC-KuY`4{(W`u@LLUz*65_7bnq{P}oqR=lEr>h(=RKGMs7^%C8#+$8Gjsvk_< z$?S{jpn9ncvYClDq$`lNhG@D~()UP#Dc$+=FAo%Sdo|^QmicK<4|<_>?3A5bkgSBG zzwG#o#@!1yim_iXdf3XuitxfEsBj;?iY>-EmM>-dq`}AMR8dbi%R1%Y2z-eQ_|~|WVEekw6Y|$X2J#IW><_GZHUzQnqmQ|Tn z)9CnD=)md8=k^<)Hm=R~28b(*ZuO$DNA%20^5z6iO zo`HKN?o8Zt&BmS0z9~wM_`Cz3xwz-z&KETLC=lO^#Ah)+=i@HHy%6_3xEJ9r#l0AJ z8E(4n!(Gn4DM|%CD{2cFYgQR0X{#({R!?G z-0N{~z`YT7J?;kFn{aQzP1mQmKf}G10Itt*H?nVv(uB_)xOd|I2KRTkzsJ2BcQbCf z_Tc`JeGC5iDfm_S8Q*`!{TuFppts=jp!j|mpGU;^W8%~N(FVc^iTEeJpTd1wq8ZLZ z(DZ38#DX!0o31c?cE?SA!HT;VZn}Eovkz{JZKc1U5q$u@N8%nR(FRH1i6;v9Y~1JI zJ{R|3+(U3j;~t9p0^AqkrYlB#4#Vd~xZ`kNjC%y`k+|b=>(^!Yez}B+J_`6s+=(W- zo=5*o66sgtb1ZHL?s2%sH~H&`!TSea{r0zkQ3r1My=U1ebbYRZ@FUS2c;{#2d0ItDx1>#pVRM) z9kz1feIGo3`?#nt+Ryvu;(x3g{9tzVpX=82zVXdJCPrO2zJBw#vv>X+w{^zhf1W#h zP|B`1HiZ51<%|QV>o2Mei~KNpPVuCFy%ahBqO)HaxB2V&=UsZBC}YQo!I#~Az2(dB zjYs-U+i5-3@A9_jXI}bdL{HZX`R82~`&QlkJ=4$1w>_!=De_ zw(_N>>mpxIuur-pYyR@r28IvX*vn$MY3#TU)||Dg;9YBvi#~oYZd&vw-M-!X;dA%y zpH`i5xV>)W!a1I0Pt1I)*N`52{+`$P&zTwP?l^E+PMX#JRj-tN+vBf{>%ZXb0^2uD zx196AM>%V2pT2*|=HDWI3S9N}^&fGh|9cm^D@seAcsOLD zZ++cUcQ-xy>DlMMzT)gUGp?sub-9zE>r%E7OH zGcESZ9c_1(T)*>*SNgwlZ(ULEter=`NiRvg{Ot43*?!KoPu)0S=&#TH9{R}4bDy3& z>7~tk#$I@C?XcqHxqa)0BsaHxz4?#bPvrh_-;+z4uDxm1cZpXnTC*+kJMX&p57hqg zuX$I_Jh-j)4^Mr0c+dMExcQ&EU&(DLz9la9nH&CIJ>k3+F*ysWJ#ibqNxAR-^zWbj z$Ds2I{yh2S($urJ4g9*{=#Kp(KiXa}Z13pEmY4S}*f4$4^`EaC{^;?Q?E_-#(#H?z z-*)f6GmfA8<>;e-AM%Rr>{&h6eYOu*t8! z@$l34kG$tHPeJNcbC1sctoPJ0X`s=GGqTlU=dl_$MlpA{Rn>g!Lx8}Z)7LzTIIE8V>K_^4U$T)KYK&D(wq zzkS(~vrm2VQ|$xgkF^XPdFT9p{O#3(6Srqye)c{k%@gte%->MGe(&IG z|Cq9T@sQjRy9bQT2wnYmU&W*{|DE>F&bsly*vjztS9=#-lK(}5z3&ST?W~R|xa9kb zA71=X#;UsxHpE^v;jxfGgKV#ltNLSONWWK)#NM;|@0-U)rf=AtJou8Kd+t1Wb&q|P zf>p_%KXB`9<&WLa@3{}>K7I0sCq~WwvVP9)oz?pECc6;!{YZEC%`>Q&GtTLpKK$Iy z>0`T~)BMs|`QcsglMnBVe`*(UQmh%>iTp1hP)8Vb#`P%Zot6Jg7xbZB$azZ_a?*U; zS@|@Vbw>Z73;rct(BBE~yqttC>x^F11wX|Po$-ftVJBB~ArH;9os~1J z3;nO{g8!2)=pZZ6xXst^AaqtQ#WJ1IZ|j2Ivx|C@yP&6cQO>Jfw0A%kdfNtL?o3XK zmph|x>B3GZChUy=-Y)pB?xKG$>O#)GUC@VgQO=1jZ-o{;=FhKCPi+*Vo{jRq+ zL+G6^*U16UQJ2?Gp!G9hqBK_DmaXQ3c*Soz$dxbuAdvFOo ztoCz0@uA!<%1PVI%hBz>8egdV<-)Ego}jBz=s`Kku^0x*m!h23Exeo&JXxs(q;k?e z2=jEgb`m>^4tzum0a-wLi2h;d^LX6XyO}rf44tF#E zgLWO|^oi&Xx;_y79wGW&w};+BZ-u{czA-$T^0XL7ijYSiNA1F%%a(A)DLk7J0A%|7 zloKWj`q`qKLen^%Alg+X#>GX#EEMwS?aCMB*O*d4? z`?L%D)Z4p4_`|}_I?DN+@H1t?kHO8d>jz=u zLJ#(xoUZrF0Ac@jp$EO3K|&toC(hpsi%#(#9;Lb|A6Y)l`8DYF8q@2H%^N3o4@DszbnS?M$xY2 zLeF|Ry@kI@Yvtvx#(1WyMwH(y{8bnyE8|3a?f3EWb-y}NZ*L=K{2cYsl_mJg1ph^x ztUNFHTYu&R;ntO*sEFE|c7oIO`72M1U*!p2uWl#vggq=5af05D&**k2=(;`JEBsHP zn15#qc}@uaM!~P!^Bm#dmIpZFErS1QF;4Bm4s|`;A;wGLymhp13 zMLDfvT$c&|tm`vFjO#Qpt^wJliSnC;Uxj$=Y8B--n*9H9Fi^ili_POGq37MA{4(J` zp%!*!3;t#i$I|{WUB3!{s0hEO+gq}jPt(5Tj2_Oelwm?-b}sye&VPeGPMbOZ?PC1q z3i|4LPXAbx^R-SF{jS@8mgujUqQ7+e{7~3Kjj#uue}#~j}&%0lQzO}jTZESLZ4MapL&1YCHi-{kVo%dO^la_zwvTbigGR&^ky-R zk_Ej?_>*|C;r?$y|5W(ZcrlOYdYCKPTk{()Uyn226YYu+^PS%Beo^o8i#Y$S{6q2U z{zSwxx?g=m)Z0i79M@*Yc}+k~8}h5wl;{Esf@rD9$#GtIwCgq&5PU-W(q z2)kNsva6efJgvgf#)^86>h>Vw4ZWO!LY{bG&$>JTq37jd9;_4P)QEme6XR6x-yBg+ zvnWT`=QQDG;zhk9L^(TzADj7iuD49Re9>Q3!aj99j~3(FZi)kM74lRGyVB)7}BaR-KOwlrCyX%Wh&t{3r@K0lTV`f5Sf;_A6Vo@CM9c+p3UH3u{1GGCv&oLu^7LrP4UTYF^(EV{LowU<86YzTKJ7SL^=7wJ`06?>U#J|$luz= z8SfVScMH803cbN>*i|X)w#p>WFJeBjo7SzN!X8$i&-?LqQO>{h@gl~luK&HF{6^vD z0onDv81H3byzBD2gr3t(@yryV&q6Vej~C^9A>xqMeVm}jb$x}MFBjwe62X5`*j1JA ztGeC3ChVk9*vUen{|TbKX~L1v840@H68*lqJJ-V@PFAiKez;N0zYv36s^0Iy9tI0~ zPtmTL33(dDIMVIyGf_^okYAT`Fcd=V zsuF(bZ=!rUq(}5LlRxPv=Ba2gPwD#17wwv9nvZgYJx5>6_3*NbSFlZt)3S>=U6)76 z$hae~AU~tnomu40$WStzQ&KZ#XBTDPkz4G}E}D`$eqKR-_LR(7^Rjtb=P4OkC7BsH zx%rv%a_`Po@-pYmE67svvh%VE7b=AX#knOJ07$$epQy#zZlx%jGi6ZKS#vY8=FH8= z$;_Rn6uRdiZFWYU$DLi0k(;0EHj!p$7rToJ7Ap8VFFRi;D8!#a%A(9G&d#2D#VDn4 zK~b(dTgjSNP@Jvg%+7RY7B9@tLWWFUZ(*i;4#|;MFh85+pPgGo0?npR6q`?Cv)n|; z$jB@%&W3h65f9CrYZA|s&xpv5R^5^9cIW10qtIEii!(BditZ>XSdbHhIYrsoIxNb} zpPhjg%ofRWGK=RxIYqhocgU1k3*FhpOt*Q3pciGa4=R369;(Zjo4qi@U99KONd_+T zxY55knVxxWK`Wky}Bgey3#0#38J&y{~Iow6Ovv@P+E`OKmzsES~J{%&>TixO?on{GfKat^yvb)5?q81w~!73WL2QYt9{= z=YvNpC|Zcdzt%f zvhz`DHWM%}FO$0sK@&3MdGbvOW_#d@p(b#*Jg1=8jln5QnNPcN(!7F#xt_udkq}h8 z)M%5g_2FhB>%*+blh1~lsDw=mq7q|5D9+BySddARzK6|XvkN?KB`0Sdyfy0yG6L=r zn1_e|%E`Th=|#puM46PGPc95|A7&(lB?NVvu^`ty2cl6~vIbc6At?*(Dsr1Mcfg|J z4te0^d40^pGv;S!k-V4)3*8GlP4VPY#knk)n%x=488kNL&6UVX;Nv7?F1%`o%n^`PI6sO%WuV5C2q>0Mr0y9w z2L_=yRy-9Jw3i^;nbn%PC^$nXp&JY7YAIz9xGUmce7FA*`Ach&si%=D@FJkeIWEO}p8ANXy zGI9LZQi(i40R({@qFkt<4_%ePrgM|gt07T?rRgXm`z}xBykcIYFdsusZfOQIYLNTY zAZC{o78Jq3GU0ehdexF3lHNsaL74=BR#$Q<8AQk^re&a1_L&4)2nh~u6wHR0n~>^| zlY&z6e3G2-8s=1CYP?jYnqaXVbD9!6WDNFwXLU-c+~Idv=tz@C2f7p>bdn3JZZTa1 zOE;#BPKJ4~-Ty@{!$7zRuwK{wODE;ia`UfBHcS(J5Y(%#Eiy7zgHfxY?Y}A%aeY_0 zjDn1c|3diOJVSv08kbSJF2P@9GXyX)Nj*liPI@;Rt1ZgM^%K-ZC(4rw>m*g`_KvBv zpTagUSg0+Dz;(gGbA*>U1+sX08u{Wb(pZ0()AV&YwqM|EvpXrWcwrtdkvjuEo=wVO z14T}kJ(d`b=#N}@7Tu``zreazcVe8@!LbFSdRBagu%1!6=mB|}XDrgxg1wN!px0(7 zFT1!n^9~^!qZAb7B3>80Oc_Ac=gkfyN(usHI?riPf)lYpl69mTYiG1z#HVxVCM{KM zO5(N4#Gn8&Rj$t%4V@C%R4h8m;N&y0nc9hXMh=n2N%BWskY$-9?oNa{GnGqaq6ft?CCCBs1mUgQO``}qMGPIQB4W5Oz@^1 zmV1PQ83}bFfK2YhGNoiLm`*seU?ECjE;=cLZ;goU9&9+di}375JdDZ0(-BO*I=r(0 zlN;^Sm}6YF7cT`JVt0q_33g7-n6)s2bw?-36ew{rc8=&d3g7VMj|1eoJ4=>C=`0yh z5__c<0%q7`N~%^v$C=avGtUd@5V!^0&JOLH+nu=4HjxT9&6D~ zBDOd43xdxx=H<@H8d+R0@(N|5%Q^JNFF%~ zkMlzCYasGJ3s5Mdgb8~04(WO-<*_K?_zjjGQo3Fmer$tL`Cq+!O0|d-PO||2&%giC zKu`npgxLbCN;{YTb`9ajAAJ%hKzb;*3fh3&TpUfXD%k?R!_vc*0)Zo0dUxe+fuCaO zVM>L-?=l>!JR-2?oLoF%@1{H>u={MnAL^t6SXU(tW> zNWf-6SG(ij1umUm$D7*u0T?tAtqfj`5*+V(S^tV9e#5qTB6^>IQXAfi9Sw}CtjitmgtERJzXltF3}}+MV08{ z2ocMbCegXXys&8!eY{lAOo?79)jLO`dl<4|d z8%o|K(e*PmL~oYpI7(w&eu@63L51B)^ode?+a&sWiC?hpeYrtZl+_X)Mu1I(IYOe( zlK7(}daOi`mgvVMdaOj>C(+|2`r{HkQKH95bh||FCiRyp(Jz+x(JZ1qED0Pc1ix368#a0e~v`ILZTN+^eZKLi9|ms(aR*dcvX<4 zE|usR5`UFMcS-bR5y5EFfeW0911*Y zVA^Oo6j);5K@8tzVA_B<6v#C2*$huIFl`_l3OEdWF2e~1riJ^Vz)%Cz!u(L6pMlS3 z*ka%z3?DqDw?CTUJq8}i@HPX}f)(vI@P!P&Wnfwm9tx~5FfHuRegprF;Uxy9g$MfI zz!b2f{RWO>c#?rBfJXZbOo8*EK!SmXGd$G56ey$p1|G?<#lRE*qyPWZ+fRWn`rp75 z@S^_>OaUs|Z(s`SVE+cDKn(3SFaVn_Q8 zOcNs7Z{RTu4>d4NbZEbUuVvU`V4A?t{*!w9?F{cRu!G@k1|G+7t%1ig{FZ?!07m-_ zOcNp6Z{P_GFEQ{$hVL@4%5bKEDPTVom}KDV7n;cW(<#c-{G zvlxEMz_S@%Vc=|rA2o0e!%GZ&2g7$6m?j{!-@v&HPckqClCXaR&t*8l!1EX$YT!JE z`x!W&VT*wa7(Uphx4)3#JqEsu;cW&kVz}18#SFh?U^l}n3`_wx?BBri8D3)G1q|P1 z;1Y&24ZM)yNd~@~VTXb5VK~9S_cA=xz>65}XW&wXEe2l9@WEqx`^y;KW8ftWZ!_?H z4A&aCoZ+_&T*2@P16MNqsDbZic!_}@VE8TrFJ(B>z#7Ao4E!L&4g-4`PB8F83=cK1 zkKuj>ewbm4fgfS`;8DH(RSfSj@S_ZGGw@>!*Bbcm48LXI#~EH>;3pV<)WAN{5He227ZU(w+#F)!z&EDhT%sI zT+Q$j1HZ@cT?SsuaHfIZXLyo<*D>rc@COVh82CemhZ^`JhWi=#V}>mT{)FL!hxPW? zFucdWwG3}F@Op-84P3|YTL#|1@CpNOWcX1XW3`d~cUo;ED{Q?1oM*z>COpH0&G}PJ z=_w|BjR}u7;SnZ0%!JP~;ejUH$ArU7_|#(^+k3=>_nGi7Cj7k#?=a!5CcM#vKQiGp zCj5p8zhc7AoA47R>@(p?6JBJ(9uv+p;cOG0VZ!MqoNB@;CVY(vk2c{ECOpi9&okkH zCfvt_!%Xk!aGcOs|jy3;g3vsjS0VD!mpU{^CtX+3HwaA(u5b8 zu*Zb+OgP(wXP9uh38$KHiV0t1!lO-igb5Eb;qy#*pb7Ue;V=_Eh5yq{_IF22c%KRX zV#42>@D3B+YQh^$_#+cuW5REk@GB<#ya_*H!aftOG~q=i>@ndy6V5i_877==!l@>l zV#3#$@MserVZy^q_&gIHXu^F=ILw4kJ!0yA6W(XSznJj%CcMLhx0>)q6aL7A*O>4d zCj5#CKX1ZMn6S@;D@}Nj342U9&xEs0c!mk5n{cWLr@GmC(y$SCy;jJdT(S$!T;WZ}wh6%r7!q1y9{@1b&{>*2>l_tE% zggqvlXTsSgJi~<3O*qwrQ%v|86CQ2CBTRUh37==e15LP(35S{RDPKo@9Wmj3Cj5&D ze{aG&On9paZ#3bLOn8k6zhT0!nDFx^{DcYnOt{j77n!ifg!4=|+k|JBaJmVnnsADa z8$yR+o1+~I5XIIj_%ET!rS13Mh!m$*qk1c1+Q6WC?K5xCLR)Rsp~HBFWNmg#WPpCV z-Zq3bHTZhP_U3Ide=c#w{An*ksc%k+`LixZPgi}{1Rlb37GKzp^pVu&9*9RT*WS+` zz4T~rwhcihMX3{?;D4X-kA9mz;!dktt6IB%tXkXFO|{gk+fKV9QNVek0Bd_Qs=~TS z&ri>1%C7Msuj08RT`j-nL?yne+Ar?js`r`+p!>B$?d|^AXli{}DzIe+@^;7{Sb#(! zk7KG+Yj@o2tUb`psU3Bsr)sAhQ=GNGgr`qdwUbWYN3>R_2TK08p;xE2K@H=S<{GDV zTa4AICCAKEwT)`|NsI0N%}kbW5;h0*vu&?hUTaayPgp#Moti7gu4+H1+SjV~4VoRI z)&@eI4aqUFq$BzgL0{CkLoUm4r#2-fin7F`aI5Wp`md0knmZ;sweqN~;$MWkH%~k4 zuoc{_67Mv?M2D^4lo$pRQ!Cru4ySKQn37{V0Ey<;Ko7P!t6J_av&E@ZxR>KzZHu$l z;BLg-jJs7$TJPD#>ey((e}wDJw>rHOBAm7Rpdkoot9X-2fQ-|(yY3p6E%G$l% z%A3R0+8@JJ%U;#jd%aqoq}VFxl$py{2o<)4+bU=)Of6q%MFJfULqZ4=y1P9fMu7M` z(n0J7Vwig-h*2PZ#fhOHhPtl@F&f16oEQdTH+KSvu^`gU39@$wF~mI>#CQ;CdkO@L zI|7765Soa9CyPa@x2IaU*;YXtB95aq-KoaANjBPnVrAS20?Fl~=2K1AffT3L3UY2i&Mo#pI+*Ccd?FGrtmQ2- zM>|LuU1Y9?nZjm6BvakO?3CH_-cXn21i|=(qlTqc-i+r?s(0YqOGC&IYt;8otzP}W zuOmOWhzglT6~Jhdh0&_ERomVlaX561YJq427aQ;nzU^}KbLkR3X%Aos8^fHXvX@=F(qBqc1(sG5wlfq ze7Dm*R&$jKe)d( z)iZ$VQMLUjo{TUs8hercngi|aq~G%2b|=)7A8^>q+RwHmnIUnJvS)5oOI_GVdDUEc@}(lMqInxG%b4{JR|)`~+K8BwaXtL2iQ zKGdwVHq{q-6YfSeX?t;=>b)8cFUo&m3we>0DEM8^H!g2_L@Fw`JH2y8uhIkjD?_gv8js&|o<_d`17 zr*Az!LJb&oP+CI@t3@&Ak(CzMgKzV^Q+#@)G5yU$oBWY-(J7uv~G7&f`23)(v?PetCzIZi4C ze`;*i>uuFPIBNqD4p?$+vo*z5z1dmok3u(3ACe#aT6&W`CUDbrP-?XXyYGP1rgfBKN_7{T}ow>CyWHBgP1SB9fG;}yyx+8 zM*n%wLb%}@2d$}DkTf(LYdi++>3Msz zy3E<5#$M50vYRw(@(!kmSTy_c=ry zXscGicsDfRhm?CQ_Qd1vJv7;WF?|N4bKEAYlg}TRHU1zcq-tM5itYZVer<1ug7ql_ zie>>-`;LSrTXpXNOM7E}T5qY?>^VsD!WrdJkT;Qnyo=Y;ERHa*hcP}AnqWM?-)})6 zH_@6e3F;R?y==k;&DNST-)aTDi|1ieNzzg3v;- z4(rQ3BFEbSME9RD{XpHQWg9iCVLcnpydvuLjvdbQN8RHci^jh2A9Jw&<^0S-6OQ`t z>^ze^-?H+VF8pJ_4V(Wj$iw(ie$T(kA<5&<{2A><3v2yDg8qi|ab{QO0WP%k@4fBq z=x&%xj7!_=(th=CrgDxosaV7fE!=tr zK%qCFa4Lf>XY`mcIGA<&l}v@k9)_J5G?1?siZE=&*+>ibFS#lYqEaKJqZJJ$*04;7&g?Ap`Nc7nM$lfT#1P;*96` z`q1InX(@<|acbvde<)SUk4n{YqOr?zGdBM=L~%o5+d$Zl*oXCgOQYOB+@8tdlY;(K zwJ!{-`4n1GvB?lBHW`v*65(AZk{k5h8-pNp56prMKJ1OcNZ{*K+9vf^V1vm!CdNOQ zEXJE06AuBgnTWrxm}uHpB=JIH@W+K>z>pZ+r05HwRb%_A&cBoyPX!Et$A1+6M#G}J zh<~xfiXv7%NILUROMYL8bg5YtF|_9gL&9*~0*TAp+HFr@-~4e#hh%({#nX!dza<+e zcb)&>58^x#S=UZdB1Rh1VESbLDGGL_Yn=`=EhyLqG_imA&$nIIT>a_lBm#^$Z zPuu-V!S48wMjpnNdyeYGXvwdIPVj+IJ*zgyVcVIa_!WrZ^eyU*URbBlXwXuu{$Agr zQplUEZ>REg2r3=vSyr|vMDaX^fYKi$WUHKw`4M`-AT#GGg8&Ev`o!VC0`m5LG>{8U zKHp&DK0Hta?dMc=R4Cko=J{8B>p7_gzBWWwsp}dwC zbE@hmw3ndPkoxZNEq`ap0W2Te{Zr6wy8oZ-pbf4~j`yhuC+&0{3o1t6KIIZ5G+`CS zbdzdJ*`hwJ9{XC`j_ps?mQsu1?3shK(sHK~EM*Mjycows+qxxG}y;+Au(l6eO}ndpH-RF{2e0y&m4^=ap^ zjjM+m;KZCZb!&Y$jJkWU@Y!#H zN}b1k#uB%`n%iU_XTxJGUV{GtWS@=|RhY%_Sh_tie}(y&#DXwgdSY932$ppfd7;!} zf1(`-keu2V2((Jf2uXqz{rz9NPWjAO7p&c2YfU8MIPf-YK(VfE!J zo$9+G-ZuUVS_C5)3|-pOv!{MsdkLJ&xYP0Mfg=6#5Z433rI6E0U62ir&-PR4TyW2? zE(z!*dKu>yO?IDN?$i_{x180wDku$jOQ_#@^^(I1D3V+?-3qCQ{v7B|?^IMi4o}c0;z7V=sxJ*+r&1 zX81}~JM^)F=MBt}slE}1y&(zY-ef*5sER%{e^fz%2A&j z+K$y*Gh#7)zN8rT1YC0?>pgc&EInL|S8*~S(S^v}?!q?}F7--VTyjjBEzT7)(-t=+ zrqCAmTud4A*y3J}p~>48_j=57_W5qiYWDeIObtGza3nbQyV$%9w1|ibE+YECw^9Rm)BMR$pwYf` z%pv3N3}MTiKUhVy62(H0J*DzbLz$?VhwF&cg?X>X5{!a$Y^NZo_x}j1s85b+hbJ>V zzk%v@F#-R(6sGG48iRUhu|vw}Og%n4!ByeaNHJm!V})W!ygGk;6OR{YeQeCfYmtQh zMZD{Z=`HnCMa)X1uu#-ip}|4J=G&hR!R(9$IQ9_Uw<8%|7powbR(Dwax63zr_p%V> z{bnR>%j}8q?qAW})=K!K_u$}Mc>L}gctw$gYD%d2n4)(-y>E!4v&Q48ZD<{O$5vhh zVk*}9)V&{l!PFGqeyQhqJOujT$zT=epofgYcfi56tVq?GdCwIi0X@h14f_l4A(g%U zjUt|BMQ|;n2&YlRW5o0<`WRh10a{--2IpCzz3$+^sk7er@b8B>ekcB5Qj+{P+X!Mi z@)Uv6VEFHJZXhpv2%VMJYYXD(D8(a+d`fH+Rvei;ADq+pYI>g=@#vT@Pxh*7gdtLe8c~Gh#|I4 zKY{QV?;HFLaD6h(Ukw7ra11sr_cJbk%vN*^ECuctbCzlUbc(6I_bP(*eS0`qHii+a zZ>m%u(fkwS`WWTi&;Fu5tb+~-2Zx6T`o#9%I;X)q?kOkZ$I5e$-t$bsP%Qd8f*D=s zPJ0&)G@cPYO#ZT~@F7V~=@>pFi16XbPobTT^W)!)`O&X?3IqTU7?UsyIo^h4wQTYC zf?_3;;r4>~Al(=_|NQ|1l==95vNBttJxQ5=6(D%wJ4Gb+iEdBf@WwLDe-sYRuwQ0R zv$vd~vA)K5VkYfB2o|<~Ln~RV%8FGwWV4+e=fbl->>a?jZc^hKRh;3gY{C`-_V!yj z9Ugpy>P?SDK#W6WyGpNwH@zGOF-ym~yxBM-lM?N;{6Sk}GAff1W;rhd4a$rXwNJJ|jYwbQ(gmhKte9o8VN<$D}fs7OncmV~A!)wLGNn&-kwC zfuoyn!u{b|0}J8XrQX+{N4b0p;xVB3ftWwxxe-6nruugOV5l~wa-Xe|-qFTcAEy`l zz0_tr{Pdf2v5C{6izFweHA5N6P)09O#<*1PJUS8!7wNQYMK%6?utqt^ty1H5@Vu4) zDTa>EGR=I5jqaAMIaN;Yy)hjX;%KLh@{OdC3xr0TG&I;^#ic=t@zzug^|stk>muJZ z$M0nN2*G?9DCtxm6fggOJRT|*{~wNrd-exyU-#$7FhlC&foT3;K`_PxqfBY|kKr%b->~?N+tRxiKbSK|UE_!#3^uNH(1{j3*DM;HP@D@liP8AkWXVzmGBS7yJ7x zFYOJo`Kw_f!^ZjrTW{_HWB1rw5Sesj(b)3S5v3C=_bu-EQ8el;KOJ4#-Lfxf|Ki`( zEIe7>ta|Ukq0aG9OgMh_Z2*}_xHdPEMC3t!9kLu*v=zl1T}O3(M1NYar{3YbIqJ7n z&^!Ay=g^~nI;_c`TB})8Ba$Hzwa^!OadMJ>@p(?)7#zv`6>|6=-rU}9nJZ4GvB!kv zZ4sq6V?Zs=a&W=hq8CqdX6+z(_0T|wOA!DhJ}a3-&YFz4b9_|Gi(*{ks2dsqYD-m$ zWgqG-T}P!JX3cQ$cKlnUEN^+ZLw~UL&~E?)=TPkQ>rKHBwY$8tV^r3rHbi+edf zj7FQw+oDUYDVCp+xY+N^`m$@qk0$jdYgx=*=y=Zf2*=Xl5zK}20!lxjf`!gMWVW-K zGEv_k1NkdHzovI4Unt2pk>s2DerV_j?uU-iV<~@39sC!aqhy`rr9r)ssXa8rY_C1) zyRg?AXv9npj>ELS9EW{7jKdcUO+F#)bp09Q?;Vk{vgOGR;}6Hh6PmP5$BtsA3@2BK zeVug6!N~{-xL1Kg(Qb@L3Y4aoACD*<0x5-;JI=jafO)x=NO-S6=@BGL-s`{a`&hm5 z{=E}tVUuwPMWd(0@Sgqgs0S~qV9=y^@BEX_=sJ@2*?dcgpzSX|6>599Cnl4W%A*)u zECMM%Wi9QKVmX?ebY!ugm5=uh_z_>`h#j_7JDGQ*hot4}qu5bjHh0@9UPQ6U+M$G_ z$#Hv=ZL38h_FLQEyG(%uZB7djbmk=igZ3j8?ri7i$MRlVWV zr}MBaX!q+gwO4IR+G0!HqHRr5|17=Ow(1}oDJ8AUD%31tmoQ2kE2r$|X>%g+Py>Mr zUPdyG=s=n>XoqbQk{#$6$NY`)Bp zQ`#ywm&PYGmJZjB`9}5BK8Aj=ORwHvT9Vz0W}MiuU9Sm{g1$j$IWOX z^)gMOpUEH58(F>KScD$LswACOB+v)fuC0PSS=sJ|xp_P1RbHp)b6DSkekjG(n%is8Kj7 zbeU7DC$pGM8+PQRPNdjYor04>mK~{C?eJ2G%uC(<5tK?yPTz#66z%3nhxcZL5jY5f zqeiO^$9-dIL6+|+8#F=hOlEycwfM$F`Agy99F}|v7-fY^@u<`5j%mdiaX+y*v`skd z7r=t?N8KL(hyzL=I;KJ+ z5eeT`u_LOD)fBxaR5i$fa+rxJq3w!4x}dLH8TrE7?fQ$A=CjwZCTzFrrNvit?KNq|27 zm+BpT?hN=(ObXbIoJQmZJ9EXVhV^w5knpO75zI@a!hj-L?&Ff$9=?ew9+SbkM3l>C z1nE_mpn1^*$QQ4+iUedz)>;#e#`$Z1>E?^vh`oO7r|&~^oxXcVMERpXfUdLf<_zY) z6wHI7$u0LueM;kJvdZ5lpr#Dz;>8&ps-md~eih3I@cs1*HVE28oI>TR-Wwv=3vyqv zgC;jqD7JRkg%?!({M?~WU0IGl)r3ZNk_6|>wxRG6EF~u39Tvo4m?k2HKX<}}Frqe> zjGUvE3!zuTlHC3$|P(|POZ&*x6ffu$5XsfUwgIJ$sSD*OPtzP_X~v`sCa>6pbc zQz$d&2bpKWakK@fD>BA*79Ri7ySJ}mm<1(dI#g2+%!6!$HQAA-a~!vEH!Ha zCPh0Y#R-3wUWT`5F)7Y(Reh7A*hsO(;o(U!e%*|!!+L~H)j?8Zg14l6c$vzctO5EW zce=D$7=Ji5jpP5KGB)p`Fzju-0Vi+FoLDRJ^ScN*Sx>D-ToY_J^%NSO(26rZdQbgL z#rur%`+BMuaRb`hhh)ILO}+Xuw3i=$74J784=?|w|5$#!>YZvTpX+mw(C2#f>Uh$p z-O9&lXYb?D`<70ei==sC4^2;o@ug->(wlTB)i-#X)7y(IY$_}O_J>}F{o&LViziSo zj(cEcT)LLV0W8ZG>ZBfZU9EVUF>K(4Pqe$wN271TOSU^Byrpe)NFeKkc5|DW^o6H4 z&S_OSwX0ixg`UB+828S@wt^5_KMbpzV0MYBmWEJ2D4&|p<~#<;dP6b`sr51^H|LhK z^tb{U*}}i61dnNPdW&g&r+W2Oo}Zm~X5+CrwI0&>usS>qV5-wx^%0^!#Q8<;zS*hr zF5WD?j9xlegq<}Q>9rJL1>Y>SRX#^rfr;S}v>xuFe7q40e!qvF)8IK+>O0UV9za2V zzp;I0_MYiim;w>Ty}{ly9UkMpj?U@5fOHmo`=?bS(_lXTqC9^r3gbsS{NJyGi_i zAe145d9riZt9dwoM}oqs;c<@(_M8y*Qk z*YDAt$?zal{(m7u+&2)Tr%4RRxgmOboqs>dmL?P7KPHo6C#c<~VpL@K+1sZW09#bs zxNY=`=TO!1Ek5VZ!@J*yRbNRn-eZsU4|pG)>a$0aTZE13^M}aCUR`K%|M~%i3u@9g zs%<=O%3d1nx4wrK@^>j&2@Q5WRf#FM<#osjFl5P#c$$p9!#hbgVzS_Vp|-XMdsoS_jlagUUroTt$Sts<{%RbKhtb)n zEkiJ0J%;;1wRUqap4y$g@V*U%v($ubIGz(+4><8d6o?thPlwtn=-4VfEh|55#RG<{ zsiA5X{bWM4+GD*7hJq1a+dNQ>Yh+Ky5hd4diNb8E(zhSORLgoe1fWLZt=-g@l6;Y6 z=+P?1vSqNrJcO88agdu8$J(!cl3A3UpMA%|d6U^MI5^jOvWp9|^9qX9wU1Rj`{+M> zRnMvkUpZ0m#}7T@z5n@VpYc+e2|L-04+R_#w>^+Vt*G6T2oD*)AQ$t_KK$4L9{oS1 zT2A1g9r_2)-zRs6%<*cEZS*}1-($u1P~Koh?}oSgD4ye7A+(`Wj1zA=4Slj|c~3G3B_)6Uu&Y*L2Cs}_v-_5IX@6V!3F4R{qFol-y0 zUUm$x^sVosCe*75TU5(25HSOwWvA8JT5us-O+VFgkeK18SyluD39SIZ{cP``WbEBE z*7_q78p~U+@kQ1U2u*0rtr^ksTUo{5v!@j}cUmjvHTW|2jKy2b4g+DO;))rS zf~oc4gBY9G^Y_QWIbreR#AJabO^Axe+KKFOlWiR0hVk@!B$iZK4d69toF)QkrfcXC zRU3|w2)~TN<`uPDG98@2#s)n$((34kLbQ#TaYm`NE#1`GpTgB{x5dO_Hh%a7*_JO7 zhloR*Ne%eL5E6qHunv52`8+t$aSA^q@)J1@FOHuP&rb5L#B(0!!6Ca--E9?g1eyXE z#&e%u&T%lK9IWb3h1n{eyL|0Oqt)f3()@5biLTPy9RzhsYgR&(7IPy@_4R+KlJo-dC4KRiu-vwZ+$WyrXUTSq>Uny`c9eyS=meV zwB0-{)O`cec#D5$!XRHAPYa{gjE`j!m+ARl<7wfZvyc|g(l+R6I7Wv)fXS#{7=&db zwp6U@g?Pq5)_?#B)-XIK5Wv0=^S>_iyi@diyk7T2l+m&0oqEp?*L&z(P57)s4o#$K5BYGa`Tn|6%=^yZP(ep^>JwHnC*=KpW=y{~`p0Ci;??F26 zd88#W{qNG#GANCD{!Q=aTI&b-ddU`ES$Cw$;(|OCE(bHG(^l)2cZ=TLtUU`&l z`4tyZ$PtZQ0;g@&A8I!`M}|_ri^xc2l5Q%k2olM_QfX@4FikE0r43U=avvqQQOn~imdaypJ*vg8=>+2k zcwP#wNKk*(Tg>qVLU8<3UteOl!ozhYPk&jSDahi)jtCR!I6D(7>i3RI>pXwc>r#7e zL;k=eh;mUMJauRFy(sgi5&uc3-RUiYd|cDC3rPBs)jb@!=vXw<+DZzo(@~4met2)a zK10=4A`@x(Pt-z9Vw*fI32fczEjH>iRaN_Mo=X)yi?fD$*Qgf#SSysT_p?zi9ZwDR z^Ewn`^z%~eZUqLQol<|y(dF%!?>;#n9gPj}Q^iHFr>^zW8@UYqyiQtu1>(#483#-5 ztUvn8^+i*CbCDDMf&T8gzUTfW)i;ysqod9p>MPOfGy3B-WTO7qi5)iT56AoPwn~6b zG@w4%OV{-cm+On9`ZmG{g7qOk`l0LmzyDK`_YZ1iWrzHj0ALSXKZd-=kcs3?hP;7; z9omQb(0^UmcduOE%T(V$bA5(?FzS0i)b|HFEem^vH&Ftms2Jx*iVRQG+4$%!*Efag zdj(1g>IdyJ#;;M|AW`4TdVO%&N}#W(5B%l^y6fFe2<5|O#OM0 z;EX}}fr$`b)=x3?(`kF(dX2ZYgv#GxlwWMvt4W?Ef`1P2hp`O+(oeBrpCR-& zzzD=XPCV8k?8om;Aej9z+}SOiq+E|62Tvd)W8nDkgo7Ob(f4`8i}zinv)^}1Xi8}J z_k0Oe&`)%!<@RR0{Dap|>-+;>0Z|m;3%$fthqQ!e`w1!7++r_C)9;Ig8n6E1L8en{ z_9wx5*?tBVnb;lBu4|(Xq^+Px{v%_wJAe&fel#8o(U`mz4&bgHlFHE?39ei}aT4ApKW z-s!|!{$mY(-YpS34xfKaKURmtC`!D661B9Mz_ILScPt0-Rpr0HW4F_=v&Dtj;>Jbb zj>e7Gpy$`%(~cW&PK=w0yHM5oR3_00*zv8&OK0AK7GFZ0Z|pzl&-d^m9NVCdg)(gw z8%S8%DLA6W9ag<_ThUgwLB%4%S6M2~@u%Vk-n7sCF*s97bNEtv+(ipv@1h7YKnzv9 z4#lld^(`QEBW-fC+AU?K%bR9b%Tr?UdgEyrk|)CD8+V#&A&p|gg@4%X7@ClWp0B0i zrzB^vJr4~t z;?<;-*!j&=Kuf(of2V5OQf;d?vY%GKBNTkc&n3a#T<;qddcAKd(PYby3q& z>F0;`3y1XE^}a}33SNM*{OFIwu~3?~vB;DMqKbhCH^1J_bl~-1PZt|FqA|An>Ak+d z#rT8ZtOD)DYsiak331VEE z&ck4w;3*t)QU1u~B+Lvv+?|8h!CGwP^nZ-ls?#GrphxuDw#Iz^c|~PGcl;X7 z(sV2jzF#m__1$CjKS@5uiys}s&xfd5ge$IvbqIdNf=Wz|!VX@8e-C^PZB4_M(S{y( zXV~xS;h&1^JOlRYu%CcU=?4E}7{2TfJBq{uCOla{-@s+Cqj$jqbbshgQCc3zsS*#y zQ86qsg*{|z@kttB{+a#$1K!YQubh_uK1ao`s#?`!_+3_4Ol&f=I+%^9WqAD+KbL`F zrdm$9ym{odv7dm+(1|0n&LsQ>SoynH*P#ywJH4Jn+Vt27Ob1g(F|S*PUv3RiedF;4 zVoZ!v_*>gJoPte3Gv~#`yR;p&AHtpn(msd_M3q*yY3k5j^cexxnNIC{`W{W|VAiqG zu++g&4o0lA){oz1ZE)5$hu`3fvEanb!^_cQc$SG}4t78`+A0e571nRq2PJQ?v>H-6 zeGBk{OFOng{)T~zbpkd5zh*KPl`DL6_|5%=A}CGD$X1VW~>@t;LKPxf|*|OVq#3!nfs$RAVwINySeeR zfV6N1<20S|GM&-E8M}iq#GMRAtRunLt~2(~8S@|zm@MvCFkutLZ>#V?B3A7#n`r%g zV4|nc6&FBDPH$)o;~Pz;L0bA;=X(QuN6|e{5qVuYLd+Ffq_e)Sv(hgSkZ{N?I3rl0 zt98ydbk38U6HD%w1t(W!wa&SuJ5bzI_;1m>a(pjIA}M zmn}M1y3TbI=L+{+MqJpBfnugGrJ=!dIoHCooaOh%6rQ$KBtnFshanTi@lX5~h!eYx z*sF0}crT0@)@yfRvoIo%h^Q9(yLh^`!AUz5=$R_r^j3H1&AJLf~DPm$C8UiWA?$kPh1_Q=KX`(kGUbMrBOHO&@UUEn-g+vw|WtP6VMr7vvc z;O7V|E8}Q%{);GAa#$kIQ<(9yenERaG=_Tx87s-XXW7$KZZlhxS zS^0&^h<9!3mH3B(sS2og zFF|=y2#DnSe$UL^z4vAVrl0=t`}vaWoij6MX3m^BbLPy<8AtxP%>QRseyj_R%a4nk z`OBG~2A9P6xEb|_S^&n0!h=zvn|m%q-e?S9^707qp|xMMo#Rf&xSIlyP$wgmJDMW# z?x$L#0Ri(uVpg-R&d46teSje6Ktzsfk(0JSJtA*V=^V2iR&Rb`PThhZmhmX&6G1&A$oMxCIam!aSUpDjOxzN!RnTW_qK z!PcA&u%m?TJ*<-e0TW~Ke_SRS=|ui8kzWMlj`^^123ug^bdDSUhk!sjL1?zhWHnDR zlVUQy?rFs_Cix_u%3IIWSFlbwC&tBHZZqH2Ge%>Oj?0$rxR#j^; zbsR)a@(vFtj|wapPwh|Q#pfU;I$Tbe<$&R z=~~4baN^xdp7?+?6zx<4L`;jlf|!j~VKV~0bq|@RB9|B+D8QnnQl-BcEZAyMzgg%Y z>8mX24v`az(nu^MF!fM?k+7;LzxgL*VL3BcPA^9}32~o7-d*l2XDZ7{OO}H^Ptl&< zsvI(&^*;J6(SGzvih7-@(qAFk?@SL`r@+4h{LXfCXZkW{dJrrH{#RA{^O>HOWKo}} z(HMbSMrb0S78hhf6WJ#<*vhY(BL08k$mz5|r1I`u>{5Rijg@SRRF-7=2f&e9wC7NB z>TOZDQ;!d94pn}IejK_Du5Z$#Qt`o>j2qWE{fx7l`WZu-iu=V!w!@NgXcLY!l#J+w z6#$z{5Ak$~&Le$p!7VNI%6mL{v$+#zfojThDQ-wHijiZ0L^A+<) zp}t^STr3&=yTy{xx)oXw^le;6pmG&8@~r29H8FnR-%Ll5X2D~W9_d)bLjg;(W*`lj z@&}@hPs6&j*?a|6!W9Qm!3Oid<1zzGgFqcS;^VAJ@uOyH%&Myp;>1aj@;gsS?W_f=q=Zaym$Ynw zB*f2HQVf4+53{G?g~05rx8Hh*Wq}R}vEjycr^1#_Y*!`foq8N>v<^@gM9n#g-QX!J z%_>&yVvpSFiA}enDwg!U!xNif4fjCWTQ&R0U@gbvQrb-&BeKDoplU^WUg^Qp&dMbu z1s?d8ur5H(RB>_#tX(Uyjk8nbk=sJ07+@)5v8eP}C9TDZEtQ|O7K^2xPdGkGi5Y8O zpVbJ3Q`+-j^S0kp_$lwSorduaAB1=p!2H?(8^X)SUTK1s4(G`_iyvxhlAJO zMdQKC-ooNa?!&`EiCv5}{sI2o=5Qh?x^vr9oYTF3Z{s5JGmP66Nb5sezNCFy`Dcxm z?i_qwJmU|H;;~A{{zJ)HY(K<{aR6^S?bjFjU@_IZ)xtK97mWUaXQ*cHravaV^3h-u zY!)~r_>c0&**jpr#O3+-aa~EP{u{tiIM&l5mD^$5`(h@HC#OV>w_!N;$3ji8$LUY2 zdKsanSix-EIm5vY({J2+R4@9leAo5yyZlGgoHvD_ zFYA@jE=@k`S}=}T0Yq}YiT*3$v~uFw5@Sth2whlmZ-6&1#9cgyurnN#3kP+2%im0k zB5rM^$9gx>0-784H)7SEhx=4P)YE4cKZn~SOLOok-q8_$ud#cy!|bq@-Myyp zP|)%o%*+vf;uL=uI*COB{6zx%RYn9oNjW-<9iA{CtTJx|!>RsJ`%mASzj_<&62Cvw zd_;a^-)vRji}5&Pp1BkR#>m-J)*jSBk9XTUt&9FNc0-Es@1bbS7aIu8ss;w$-MP5p z@rUkkICB+S6~BDMCD;UtUo{>-=7Ss<$<=`EF9Cz?EfxR+G;a3d$}y()??Z6?;)gRd z9;$}$#w9nHz3)%lbmN7%3+)SW(fAZh|NEDb(B`yyRF&z~r8t~^yCKYraX8i)Hgb&d z8DV35(qSnb#wPX?aMdFh2eRPS? zK5zo3`56+>V zCWWOv62(3o?+ID`(S?eyB6QwSyFEisKPha40!jZFg1+b;X`}0a9Jp`RWE~Uc0_o3{ z$G>?ot=0s;7X-PXgO|dz>PJWhI0hJip1ET{sSXJQ`&tcOka{~(v~K3Go|6LQw^mH+ z;w!@lzdjc`ov|W{?ey4GG*!UanbVhUqqswIU5E=ahqo*O- z#%GT|DIa&|;3`_NOElp!s<_W`S~Z#a{#?i#DJ4&3Ay5<@KHa#n%wy+Mk=8N3!Gz)- zv~#vVK3x36ZNGTG=E_}Zh7JO)gY`4cM#ehL1x3QKaN$vTUl~%Xuqk$bVJr<^DRHiBuTDM)w7tCig&-bH!yx**~#6gXwqXShopI#Qb2!o}`rJyeqc)H#h#) z9F9u4=nsA~Hi{k)bmBFEE^1dFH}P;jxsF|clhu`PXBs$t5W}t7P1s5)y_XN6^@10( zSC7VK6~@lMJ|M14Mn5zL%K%t61F!LEbAEyfz&qC|*k;#DGal0Y8?NRPRmHy8;9&aT zoW{*|ec-0kzn{;)VRK#HtGU0}JQG5~)%RxzvXY9bf9&hO6pAe=c{}eBB`FDOHY6aZ z*~vpqt@?4&NidP(AGmtM92l_51VM{^+UqhrR)u3SIMk-Mj(a#FLmvUbeW^_U0nr{S zACLv+_fVUh{UKaPVz69>UAN_nSbY5QpIs=%(gOUL{n2*wg@u;wZGxj8VHbiywJKT2kSmYkh_+xPmQaesS?mUSUs6!=Nmt;tdVJ=kwJUci~x*46{dt zqA)ciDdH{_M90QlQkCI&FQj91n?KR3EZ+Q|$4Mm==Om6b4zx4J1GAex@MBCK8d_Nw z5`LNZVoIKuSVD`#2+jnLfn?c0)hu~S%;K?(hrsA(Q(^dz({Bm1p&v=V;<@9y2>R`x z*@Aw1W)akaetF{bjFc}N6@q@HSiiSIzg(bk(GPFLewbL2V0J0}6mPiB-u*NgBsMD- z^8aiHF{^~{avbg&g?=E}LALBjtUc+Q$3HmiBmAV|4s43zCt+uUT`#)MM=9~i^7%fu)=VxTL@E82u3F`?_I%cPD*R|=L0#h1A2hLcfo=S zz|(8+Z*%NofPcW(Kf^`EN3}=$u|P=0uyHXSjs@#(9uh3T-)Jx_WWZSPKpqxQ>G&XXLDM^V0xcwiWm@W1=q$*?aT7!~~7TrJu4P^KEqg4DibV3`qQzD*_c7ILEujM z(#t?Zb{iGMO!NG&o0~Cm1FFdd*AZ)Ju`_Zc>go^x%6zhy~^w zbKUf5{JqKgIQ0{y{i27@OtqYbX)gnl5SU$%1Y$)P?P$iNbw=uS0rPj|o>ndr&2|k^ zd%@ivQL=wUf5_$b|FQh@z2%=L$`@1~aRMse_jfOqVeU$?;48mCQaLG~%$w(U==&!# z7d3Iw*RlR`NfOt5a>jp*-?Fjz1Pdnl?My+MAH{EXz3Qbz8~Lq2%q-6F2!3OIT=e*{ z{e31$(i*?@fB<*3GG8=1CBHq1v<`l&-IqW(O4>xkeDv%*4$OyheZ8Fr;IrPdw%%xkL;B0l0C71 z6YCpH@0(*iXvfhvjdXVU|Q%9iw?zbhEo1X&U^(Mgl|pk};IB$o`a zf(1MT4ZBz@yx^!%5FVTCY4HLk%7>~a_!=OMY zmH|`V#1C-{66rAgP5cNmdmTuE8yaKY0jKm5x-1gGJW_zNspzGfcTW;DDmKU3A2-Vn zp5xis3w`yH#?l}?_I<{hOp z05xLaye2`7$6<$Z4fw|dc~Y|fL8NuarypIv)Bg|k+kdyGe)~j)Q`T>oyM9t$%wB&g z#%3y!K^|#~2<0IbXs#y`p~c=BUn6S#qxfs#pS^>km3b}S7NJ$B6nbCEQK+eMJeYUR za`PYfs}S{a@s~}X(^{d=zb#U)zRS~RR|%4&?6cn?t=cby{RyY4Hz!O`bhOHAJCFqq zduJh5w|Lb9dq{iCCoqe{bw5n-mu32W*uxsg2S4(J8$ZP3^h{V|ek#G?@_bA>5ff&R$$~HG*LgWH_hF^c0K56XbXeV-=q9syB~7fn-D`9q_;Gtbns9^F1U3 zX)>!f&Aii7?ovdldf>*?9`i+nh1E=y6q`04E>I_#X|&VWWy75cj(EP8QjkIH_df*PFXrOs^vTlM#oswZu*P~!G z&^Q}S>w+4v_s)j>WAZ65e2n6}XH(_{XHA@io6Cqo)@t|yM}LUcJcmdpZ5~CqC2e-n zo{*%CIS#25d%&^H$FmlPjB9h`ah;LUUKNIn?_}Nz(+sPOCqB8iZ9RiXa%Q$Dl6`SD zF#&RR0&~+K!;cx8zzm(M82t+weT9s^j?s4^+H617zV95Fx?x11C zd$6oK-i*NEghuA$UBabu3)L`~(^Soh5Qes~gF}E(mEbqlrRcKtM`_`01y@1*;Sc zOEGb-q?|A(;ww4VvwpLip&+Ku8Z{g4)+*Aq`JFJ#2=k?B^EV(5;r#x+_y*js&7Wq& z@%?*5S2mZ-oE4t~1bCIGWNI;_-p@y%aL2|Y@Z90^wbSNP{}MRyy+M2fXNESP-s%KA zV^-N@u`iBs(|oM5>7J>JkYdG6?&_)d?-@TE@wbjl6+esd+MHW}32BbN!Kkwg zIp2wLj#M^Hz)OEJ-Q%!oCDYAr9`EzD*XHvokZ3QyO*D)7X32a%z%~Q?X``L_?D`4tyn>H(4~=oA6XpLP;{R91pFh?aFZw%erY_PQ73pZZ1)k-%In%kylj&}g>2A2) znT~C!NYm!&h!k~`Wf#e`JI6cIvY$k`hbx#RdJd*SgIAr{HLKvK$c9%0= zkGEUh(a+;*lHLe!gcEaT_*75$8&CSTJ>eHT;gO#Fy*%NwJmJHh{2zP5OFiMIJmE4= z*jwN40`B&`?+JH=tarBG!YiN7a98+tbB3RZxWoCL@G{*UAM%9P^>xRO@Pw^v-SM+M z;Xwo4@kQ6U!>@Si6Y_-D;mVk^J&Oz7;n7#P!=VCq_;9{Eyya4Nc-hb0;mJ5i<}7be zH+Q%r4p}(kw+G$f$+!gQjPHh}t24arRCjm;v^!^fk*9xaJ^j0=qdWa8nCG1N2MuzE zCwS;RDC&-%9CnBOp71k$-0}II@Uq_S_>d>OF653M;R##C?)cfB@MKSaM?C%A(bM1C zJ^j7N)8Df_{mt`(PWpBA^!GlT9C60K;_2_{p8n4A^!G+=N;&h7_Vl;k)8AV&-097B z?(n}n@o#v-9X;v4^u*VC!oTr^2Yd2g<_Twc!h1dWH+sT<^MvPl!gqSY-ujO2;BJ4& zQ~qer_y~E%$6^ovpV!ZgucJra)q3RD^j_}t`#ka}Qsj=`h;3+Be|z|~t@7zH55Klm zK3PF`eHMB6b+(6Jv#_t~tbZ*ImpQ|Wy1K*p+3s-d8Se0))7|0ir@6zEJ@U|><&Gb% zxx0?TRiof;HlqXPyGx}{knR_%VJOcy!ymq55KrD z@W)}#{OZ-G9=pVy->Xk`^YBMU4}Yxl$g4#jc{SR@A4MMi*y52_v(I$br(47w&g$z9 zuk(z**u`r~&o`2@ccI6vSwpLE7=7RMicw_GHDYbpN`7IGiI6G%V&Mo2gO?&JK1-`VOn zOZ_hDDd9!xcMIhV(oI*t9og>)7xk9E%dV2YBh>FUWEx?{xLsRsCjBP9dG8=(0@FEBh;8(~U{O(h9S$DRCKcnbAnsNp4{xjt7MvhB_N2`9yQvG+>FX6LQdk?GMb?SFA zme9;sq<#neAj4hN?_;o;Fg{=XF1CLa|MB&GilWbSMV}FhJ|W6wq#Hs0$8T5Fe-p^>2zR?${#vTv z^Es~o{tD#<ROof>EP9b~w+o%}sd;n}Fh*KF0#TPWv|ubXQBV%6?v z6u%pkQ;46e#uv}0G2B(v(^CDmQKc7pB;q5AKI=HoBAl=2BjhN;gH-<%T`c1dtNz)p z`e%`%kD=%@SB z6kj|;e&_j((2$kWV1gZr9le0re+V`>jMe697{71yDSHP@E8XUHVnk8hN~Nj3BXkb-e$m+fu77}ejSG`SAO$R zv8BEYCXw z%B;t41e;iy=9}s(+kBN@*xSh!ubc5Po!F#+)r*ZvZb69q&+@+U=KM|K4s`M|bzgA< zGaUyA)VY!MIQfs`>pb%rjo+3zE~3qBz`6EW0~*IC?7_|pBZ>D5d07OM@R{#EO8S%} zu4?j7MxR)xmw0|1*OPcy{Tyt5mM=9|;qq#d}*fhH+@7Bp#9vB|7ef z-ex~sy~7+O3&RV5GHIlABg=~#y*7}dVZ8n(oG^+V9JSwL_D8vB9Qpt|1oj=Wza-9Y zkp6UK8=I9a-e74i!!5(UO`)Q_GqE#zP>&ZJDjJqCvr{@;`GYM=H=$Ll90V8rHw^TY z_m%my<(S&gkPvpX;c8K_AlL~HR$@zQI`5;F;3Z$YtSXKn=SSmH!JL!!a$}emU~^DO zaV95VPR0pgTp|hJRCyT3r0rwnc=v>@#$H+2$ZaTQH=tR(4?7Bnls2Q1m8)@+)U;`V zrE}QE$yd<+-wQa3zo9wdVv<vKisns3zVf#?>mY#U1E6TxpJ!{zJCU-pt39;((`p|BIss&b~|0z8u+U>|=`d(GjR#`i<~}N?(HW9tMwKCzfI+c4q$8#+wuT z@4Byo@hxw2aC{3X_q8}OfGd;gT8nOM;pQs0vc>fj+z>tx((b{-0=RmdrWZBheK5S@ zyV!gm$DWWll<(b9u{crevV>(zEKPK)AjLZql(i|-a3{ypAR zeF;;h!V?<|sr_N&$!eTODjT?;jE9Sod&ziAV#+m<(!SZzqP^vxhGKoOt6aW06gx8# zyFP$z?sj{*{aF(0+hrvg&3yTKcG}=TJ+JM&M`aekTVM>Jn;Ad?WSX)kMlWp$4O1R2cwwKU0>jRn0ht3HiN>#@A3%;@ z`oN7n2(quiyX@uP>hMZ{_tZYY532q{jv3wVtojaFb%?Fnkg;WOi2(;Y$7IEax+=cf zZ$5OLUGcHO?DD5PmA2;((Z55M>u9cbKK9>)Yd87pLb!K&&+XIAQRN(|&5q-s!Tjv2(ca$5vv7mJxva29ohWaruA0gu!lm4hMnQ#^f_ z2Weku{%!z!TTJuJ3YN!wZ0#Q55`khqjlf57I*!fYF(Eq7tG62Zxz9TcTh1ErumfEN z2)U0J=5!WzKMuQ0nPi?$v_ki&KuR&T=od0R5S_6aF1e{1z=tTDR#5xEzZ_cwxl>%Y z7S$B*iYo0zjLz%baXHGlMK*Ammmw}3DRrL>qizwW|TuElYIs{ z?mU0U^V!t*{)SC`J$`?-`GWk&F@GaJa?OSEBkyMG z5qu?CK+2bBd{&<8w3*IpqotpQ<9EUv06f~q`sPvK53*?iUnV7nDJf>*1~ZQ6g9-mfQA3Dx!Glg6#Am*9BkC+YP5epmBlU{foscP0bM@FT z6y84h9v17%E2$U{qOfUAMTcf)**W~U=#C>|Iv<0qBHg9DSNg- zRy7}AvB|wEk2RNd1#9O-OZzrmkBfZ$Vh=Q!J%6E9PecN;N->;d2gC7uF+kyMecC{? zY2urKrAa78xaV{24x(oLrZ;H^&#;1>?4zKAX=MqzxW0gd|z+F{o0%hY&gF6 zz-w;hk&>yW;-FNYD34ckc?{3r;pMjl5O6q2^_GQ;lXE)-&0A12sC1$YIF1;f)NKAK z%*wX={5|B06`DQ!Vkl@!I^kqC=gX3+by~GJkBWN3_lM8wjbk3b8tV^V@E`FglV01D z<6jF$ywE$ zNP&tKs=~1+H9omY37A))BQ1z?R*bbmjhq>$1$f*&AMs)T%>gU7)r12Byr&wl6E@4H zicAO8w`{YG9jCdXr6bb~Id?fqHtM zJb{~|15|sr9O7_;W3ff!N zQNd>e@Jx7YFs>}QdS1DI0>RPyj;;s!+tm9Odf(K#v2)n_4cE54_g{JNKh^tNOHSPT zeMD&|?EPDky}uPN>OsQ%f3)}0zfax!jYmZ9V|u`_x*rSOkJATticJOJy#vwMsj)k{#O?*V_Rbuq&8+K&r*TUM@se@%OCiiLedPUQ zcK9_QdF6P)lEs20F;VFB`39$&IK>JFW4o-cGI;wRo<_uz;qa*6ht!m}zr_j>ZwL=G zVK;z?xDz+LJ`|60?-Ik7K1s91l<){v-9$l8ve`oNCvX^B()&aFV(bY|O#RW?;wChu zNXzP#X>C6&D1nI}Qu=+Q_IuEy51v;j&kij~$J6kT|2r~bLox&DU5){o87p`a&@6$@ zzX6Ujp;ia1=OhwI#z-ljRfS^0Nrj7^KDgL^PY*3CPdFq+TO+0Kg^dkD`igY$5hc=P zBHhJWR<0FQmFk1pX$$2Z+o;zb&hgh8>)4EFY51f-?|7$d3~h#u8f{70NqD0LcuF5g zgZ!?Gq(1=tzV}sXBD9*CWJ6oUi6hSmoD>vrTLpA6ppD>7y}}r#xCs1~7E&9*G4otZ z!h+D&^9u7|To37=i7D{Zu>WH`P0PxF5NK3J78bUCs~idS(#_Tk{EB&b_AaO){>mdb zH`@riV4BHg}2BHa;e&n44|_;eA!QN};3yW)@Bg>$}B zb7vm`vUXF8MfwbpevwRnaX(jj)~kKxk-M}xvt|6-{aeJht2}b2HfOYqzjc5!Ud-oY z&OFRuIh@A8xV?gA5LyZ*t(-9a2v*I5>%rz(&9<7fnr&qrp(ZU5-%y|YQ%4Dye`+4o zBMzUbc-Or1bJsablzNVQDaP7x&_!Pf6|G%@804LHy({m61bwHzh1jzO`F%1^lEM*(?B^+yT#gG(bBxEuf|D7l$ ze50#g3H%u2Scqb(w$JOv2I@jXIFtF{&`PW<{F@t3lI@0vM_lCCtUY|_TXf+4fub21 z<&CjZp&8RzhB0uUwHOh}^^(%Fp>kA@PMN@VEswqkaY!)FC0bh z*DfCGtuV5U3RqLT*_s{MC}vss5qDFOb6UVE^5)#?$jMwIQAzH z3r>RkJS*&noWd%SGISG+Gk4_$_G`D?6@Vy(Xto;+qAfe%q_wkbE%Sj)$YMDu?AHc0 zC$gd152hy5JL|(}-OH`J4<2GJ z?c5t7?V;g4X}i7@-raKRw1@j4gxpzZgtuGY+wW{8x)p06IR~Pca3+gc3u1TQpv@V7 zC2{VlA?@qfe1XnEG;m3k7oC$qiAVMLE}MJ_(t+ZAy_suJ9Gab|PgJl=ceZMasKtJ5 zBrfP)`f=G$*x36z^I5J$758hy+XW#-TB+)mea@D<`xgyou5sFp?KVMfxmW|Od|qYc z{73$R-cWEBRS7bf&0VBl9_bej1hgk=!FR?cZ3!lGn_@IbScBV)>Z#UH1%6C}hP2y0 zxMhRh-79JF;~Ug!Egot!s$aF%pkK5Z)xy?L?cUR(ft^&36`;qc`RvSa^;oO5TGSHN z2|B-3RKr@LIzg>kCHu+$IaD>@{Ksfj?m#6JMJax_Nzohj3!!UUKEheB)Zo@A{6J=; zSy#%ew%RP!<$7QHr~w&&j>5|ctq*LY9}7`*gyRp>+V+g}Apzi`)gabA3nTdq=ct3E z`Q&SN6EX27`%ujg)MvLVo?!O<+N~JL33&xE@YH_<%b??7AzO<2*frstHIns6h!HtG zpWJ3F!&6{cv->!7Nm-W>DX`2^SQ4tjL5FmOmQt2t;>MavSJ~L9{WGbC$jNFgd@M)O z!5Fr|zaBRMU%zskF%3dmsxjK@;H7azX#`TqH8T{ zYlgXP&2VE|GhEba25ki<)^WHr+EQoSx2u(gYfD7 zrW&Qa9!+rMxcBO@NFHZ=$9-C>tkuF_N8tywH#ecuoNAN&Eb{g8R3POHtG)g}AdtUq zT+su0i5b?32h;1fmYbnZjOVv<3i06ii^4hZzvH zN(-3ptc3v`P%iv(#^EU*nEHf`FOQ0tWbC@A(L*=x2w2y(xt3}k@YJ%s2Tz;2ZM~!0 z-ZI-tw%?>ATe-p`$p(s%SZq87Y+68jtEO`APeGg{Nu+@VjMKBtHBhsxha?BSYHLfB z4&lM`=QiVcJ|#s;+l=SQlz4v9W<1ZO#PiVGvvi}V%7qq~#>5G4#GCLiVc|_q+v$q)P=gOJ5i!e2a zIU3enSVd~T${2U3tccS$G&87n{@SRKkC{L>CIqbF)-W+C=8AK*l}dBeRz8pwc;-;E z6d?{&0#DQD$7|Wj7L(Y@&>^Cc~K zXkG$~yFRH*E(u*>TzU&U*D5@U(#O*Pv9ETe?$7|?juo_{2IvP8rv@lvpaGgVvy(KQ zg_e+)gtfmlYuoFsWuPIWg%T_F1*%%ha-h{LgIdd?m!>Wwr?m{R)amzblkFx|2WMiD z+xa3`DZ!)C0?4hxOZ9;Ea6zum$Cl}^JkkUxb^~mw1zqYzg-2@8Hu2!;9@Y}=(stYX z_=G&@Ny;h(ldeAfZfV>rczQVqs8{TFM7e11?{Tl7N-b){51o zsMyL+Q;-Aw$-<0vP}tUHN~6FgcQ9 z%%0p+QshUpXh(|56iFm*#lGH$bHvc+&vujmTM-3t zhR`-`olMR8A5z!UpV1FJ;MF#DV^F~QV2f-pEBfQn*yhBy#rnN>z?#<@8e6loYa?}E zUFyCq(DePc&^T3S(I}rnV_Uj}o+Brxg02ZMP6biUP?Y2+520q5n5EzPNYbS(C@yfL znDK9*xXg{>7so@vnME{4H3PGY_BZBFPE4GY4t&`5mR#bl#>*{H6m(T98%a;7+Hzlq zWs6xq-HqaI4~llS_SA~Xc9knVceFxF5_$IHoV?UZa2TgvY5<9YDe1?{3H9t^l{J}7 zAmN#owVRWJA;m5+Gs|7#3(mE7iQCBH~QRJUMJ=t6Z+pn6H6O4+0X$D?VPE2pCq zQTejuCh`Go&_T5@(`L#tuXN?~kny-I;B(}3?;N90>>P9BxD$4Ya|^LkjBVqt_|D$2Wv3HhEKZ@^kOIhgE_(<#GV*orS&AhjA5Nk&`_v)&S_4U%E1* zX^A~&nHyWqav?FN11iC$V_9NvIk!XGu`k?$@lR4nkE^y3e;x>cO^epIWNY4Yw#`F% z-t@3HhljL#;)+BA?P}SLoN-(%SSQ&dfwXe*T)p7AD_i78O%j{o>`5__y5o%AO&bM$ zurb7?-01;Ss_a~*PQ53diFg)@p0z8TEMJtpVv{{|j-!%0Y1!(wWZ8cV)5R?@wcIOp z)~VH9*KHfy*t%9RJ^3HQ6m5y=_^oS|;%RNg)N1RNZfGN&THSr0=)mMrt=VI#b!$AI zhi%}?R91&_jR_%zavkf`VxyP)KmQbMa4V5X>oBfOLYe%hOq4K!VUy_JB!0CA6U1UG z`^u72IqnXKePtohl9On!;~{cv#7Z>ArUROHBNhUZv*3aC?oM>oC?z8A?y^Gkr#2(< zZZIoEqmPHkwfBAk8f7a)N_jt?wa~9Exw3d}a=*Cv$_M-bt$OpjZd0MQ1iUym%~}Dt z(^BZS)wSYBTAZ*GX4H6FGmLC&hD+L-;mAMS9gj9Rws4oUuzoK|TEB}oajGt!E4Of8 zQHbodK!O-z|Cn32#dA|_;ZAqt8;m6y@~OSQC5gSi;<;<6V{p-CYmhKXU*pQDHd}FV zHq~b95^lEM`e(;xD{vlnvvvDBZS35aRR&wLXcHy5t7=p7_O_OMg{S1yyVpyEX{@-g z^@6vE62mlFu%jJck>SjrS-WFzct* zIg01Po>!wXI#dqadrYBn6hD&ka9M?;U|P}Kgjmr+@~%iqUKb$>J$Oz$YEv6F_tqx~ z;i^x|JwAI9=z*=d;p!<0$w@7cjB7iR^IIS}Pa#p$zL-~Brax_ky*<1_ZVzLM zaqJCawi4UJTh>Uja9&HyPqvv%%t)}1K_f_mVQllk`vTgM)voC(ae8NgLc|K$h}6zE zPa|S#gIP!++IVm>2+K)N)hL0bU!pqdekvG8UQtCib>;OO9$LrA8P@3F*d7Xv_b5@i zLepZhYQ3{BZb9M|t8I=+?B}8`sgKa(x}>z3q_sdZRiXLuJ>?<|&jg5`x>Vt@H4qGh zF3JGsF=z_g8>sEE;C-`7w#Tjr5J+n=5Q!cVo5%LXd&`pv!mPUZ-$2sB#5-0YN!b*x z29!yY4KdcGGH9OP62oz65=0S$mh)$%-MmFJej{XdqYM$k-<~dG5)vaY} zr8PraOTWMM#+=t$o>t1(x1yECw|3y7rqv9s9JrX@YKB&hPu!|9keNqi2BRK{6BrO)IAwtu!gT`U`{$A;FhWL+Jc5Blr-}*Qzb0(J zLx?(rF-xI!4K}h{Yv)g{b7jcKad|PtNe+FpEV)l07s-mNvt79yd0IPg!Tqq$-%K`H zkv?gmk~+qAoH^mS>f@ZX_)QzJq+SNz*hVa=7lB!A#FBam_|6+`qf6=qa&;TAq+YgN zXu7F$gwF-Go+_n{iHUyR;uhAQa`2=sTa~$wGm6| zW$U43ZR^w2i}6>0MP4+vRwh4pCO=<=KvoHMCqFx4qEgU{lAqg?*-l0qRf2BG&%Vh| zJ^9%eJPvvms`OVt_NmV$$%j39aG_j5E5#np;Cu6t^9xCA1G`l%i5{X$+I-HN*X zJQt3Ju}QWCw?;olHMe1pZ1>dsL^6PQhxD9{@KalMmfA8i6W2&fI3tEc2r;IN7jFFaq+ zA%^gN@t01@7lUwrVS5aOrwn*%XtLIVUFd~FoBKTa6>ej0u=^JNX=+W;!PZ&FfC4^9 z;m>B~rNWy@j82PoWR%3u_UV4O+2199Oo$(aViv5OL7?4=&NIrd(X&7ccbn0NjRG=Q?lVT=<>=YjN<3O2ev~3JQtd9NbtUV zmQy_)tUSQNyA8Zv!pB0{eY;7ylZnC&*EOQhp_!_@fWJ4Jm~{SBQvJouZg?OOt{=Y;HW=MsmtQ0N znb3=8C%=}}DNV1e$&40lEB`udl;hMd9I}DT8Ora&-7Ft3G)IkHy75iaXihqSuoiuH zta$;69gf`(GJm1SJSIWr29o(-;n>|I^D05+uQ{wsrFT@}GN+Fu^rXGf_=7p{!Y90H zk;L%!Ku@pc%>_=v-Y*CXr*66=Yyj_W`$5czaFJ^hbi9Y4w}GIyNrD~)nUe&a0fO!* zG3=*VNz=y5M0uI9!J?=DTpzkEA<*y2cI44=PEO zO(6+xB)Os;D1Go11*%MPA^f=r$x{og;}$S|z`O{l7>7$6BJLo@u<{=gFGn{-gSq5z z@FtK@)<`zbF~4q4>L5&B&RmAx#PRJ0ayOjBAQyy=VE7!X;V2t{xfxx_zE$1%7a(9t zI1p;#j`#=E(7f~8V-EKuLVnO=qx9JN&7ot>^@1Y0c(|1QZIv6(>Pz_qj%AmNDPj{BlJ^cz66ZmXq_Hl zW90$!6jT5upqux=OQp33{hW0DlAxd70Dp#{YC~W6IE2r4ulxq>q-X1RukS=c)Wj?W zF5S39htOvUH{T-LFa5vrfoBk0R~hJ`((EBJ+#-(qS8ttphF5L?{|^6s4hNBK;=fl# zhU4+yw*ANFzyJQm%YWB>os$2+o7W8HBkS;Ei;;Q-T*3vG=YU!C82&E?GpjeTzwvRo zb5Ah$vd6*Du_wQ8&r zd=)cu(#xk;%*;zK9|9Njdg--=dg*n!dg*;oOdg`cI32HaQ@PP&r$*u#HPFex27JpP zUH;pg@;?G}xrE*rNB#8!v3er`~NfRpV;BR^^Mvqd_fF z+7zkWz85o`HV;Y$C^aY$iHG0_9qZXZV~&{D>WiCjV*e!F!0cO(g)Yhzp5Km@4bU5q z3Fo1g^lPd(oL2Vpsi#$~EB_}lq{mJ&0*(I^`6~{ir|OUl@b*~qr@?uRukr5-5NwCz zYiT&d=xhA_xCN2e4cQU6(2T@p1VXWs%(E>JAH4Sqy|SmTEFZT|0(wJ8=ncKvYjfrU zBlzSZbcD8KuU=a4y6!)Ab@NVb#in{ZDn1CJuf%k$FBUR=?3Skdnsux6%4kND&%#Qw zx!#}7t;@!Z_5L*euB-R^1)xS^X)muf`mp^-wVAY;@wZ>er3yR^A z4d9Tc0TnuAevP>l(i55d%rsVJx=m)ff|(3tGQU7O?e{RC%6uRsLgntlXsPDtdA!b& zUo)V3-OSODDRA#T3@=l(D*+#|uWIn{J(MaGz|NS_R9w*v3eAGfT&ykmXQZ^#+^E0y z>gMfsf2P~rApH0{dNo?KzWjCF*alf6oN2>n@D#8ZWnO8Y41Q#qzu(J#0UN<@m=B9+ z*hVn$pqoKY&z93p4t}5)6=F{duEFpLiQ7;jyDm+u+Qe$k!ES-C3jR=E!!MU4f%+03 ziYP%_DgY9&Tt^4uZ6PiY3vK-m`~%*R{A2wBQcL9n+%NgnIuB4NZBpIQ^GoolG-brqN`YOrp3}s^S4HfLl)k#@{L;n`4#9F>y>N1w^I;zWU!bIRIJgmhC@a|9#AVtk4Ig=}?TyRertbJuN<&Lo_yKgcmD+KK)Bb( zn7}@Q!{QurkQ56;QLuUBi(}2K@eVX!n-j%f)VMkW<3>`kc+_>Vf?Pb1kVDCX+$2aw zE`8bmR5W9zh{!dwI3Te4g;Aiv3<4`#p2xP?G+eoz>f{uJK9f?hyWX`9$8k;qq0T}fu&rmlS=HQtNyALuRJ2tn{-J_==;acUEa*n&JD z_@nb7siI67d-+As8dkHYu^kxb?AE*)IhZmwEt?a3H=D_#vF^eAt$O_Dj~rs|*Mb$u z+BhK5KvLs;n;LvM3WW5TS3#Ic@;L~>p64T3k`o4I3odvAT)>p67b3U8>OvoOP;ke? zT2Kpx21e6#7_lHgV8b^2d@ak^rDa9H<{VIX1^^TUB?~?>zrtYTK(za@`dV;{Sqq#A zo3w6hwg{#Ceh2hP@IWKvy5a#oX`@x$1V^>5R=c==l5BTLj%+tN!p#6+YCJJl0w;PI zLF+qVgCZe)EcZQN7Nf4cRV`w|4qi+LgQBftg99T*y_PjfU;v+t>KE&Bu^zi22Z~V0 z7zSksr@4h~_8AOfy|f=*2;eJby^qes7?2+OIq6Q56@BiftSGX`&QPfhT5=2CwuYb@ z(tZ)5G-zji{XJ(v)JufZtxxQ* z8bgv^l1~b3KGDG^4_<>#9jJO2JsZaeh2qW(7x6#f*z377ne~Ee)i+a*o zae-<6>NA?Y#zWk%;y52g!<3+TY+x~t)$c<08H1C(S3QL+dakD-7NLUvv4Od;zt4k; z2vU~?$IIc_I02?zNNo&hb3cU_N}|Nqs{Vlnx7G)0tUh**O< zAF<-RL_a{Y%r?*a{8)4TL9`JPXie0ZhJomqZ#dnk*`}<=)NIOZ>OZV*x)sHjNM|2gu){AsSHp<*!j@zNq8zpGj^zjmJgI=`i z!CUoMXc9_P(_R6lylXQt-*JljBd|`ryQ-#qaRM!tQJ6Lzs2fE$qbfG)Qlfoev8@kR zzA1DrUzw)Is-*fQRepW;YB5lg?;pWww$NX11tk*&qBE22IEF43?N)^)`zMhe6Ie3+ z`mLVy>2`mbeXz)J_22CfCF2&Dmw4eX905GxZ`?fD8~;#~E4~p%Fc;qOn6Hxgq0de~ zCZ1Wt({+D44bQ}27NBbVcf1O_{Bbfw{PIKCg+amvNNE1#6G)hN=Y@<$n9(1}=;=Qo z`slwzbUCA+mC<}B6}f-M+@HwkVT^u5Mn5Z~douS5L_1dV3HuJ7O%e}GUB@8_p364F z>MPg0Qhnu^dFm_Ayl4yQ{6MhSd{p!hrcs#LL)aw*yCpPa>$kLwBxzym^Dxu&&C{~l zC+$niX)dExTpIN|NF>TAxP>}}*mW>huv~66MhszZqxpyPK9^hpJ|}A1d))Rt*dI^9 zf)qybnzDAlUASr>NX+-bm!BqybOpXa!)r;yXTU@W`7P{;-u!tYKi@MJdnV|jgP<<* zw`cwlsq%NW%g=}v_Ym3BnEf1C{OdA%1N-PKW>4@R%ZKeVQJ4`mv7m!=33-oJ@bdD}8|@eXcV-WUWlUf14A3h9i9w*X}Lqv*SZ& z`uCYe)oraaeWE_Ey3z}KfJ*^V05pp;2^_jZ-P z(2;&Fzgwigce9h;V;$*-r%Zo7<|t)fF1>zu#5jy+sAj@Q+;>!dO_Kj2u`V^xa&juJ z@s)SLOI<&!{D#&#tjyMyf1Y0x>Nz?4!ILAgP!6Abi)HM!?Y7o?82Uvu z|66(fR?5p2<>h(H`_xrl=ti6i;nRQz)pMG&cfUw=2X-i7Ohaf_uSgl*TkCY~Y{Th%rZ4DB`ayC0x6!PU}_>9AF&MvZ{hj z011|9#^P&#g!!z*>`SoVd#77AB!?5w1NKnzMLnm#{S(iYCT9VNL!ty}m*Oux61fjm7{DQFNIoNLgy zAgSmu8IHRcrO#eXvU9&Cbc;B>6OF$qu;T$z^Q*>WEg1e#gr5_&faR*WN1q_ZmklS2*uy~CHY^_am*hT{ckFhSJn{v&o3aI|HgZe$hDoxt-jp}>Hm-CEUL zXjCCSOK0o{|H#761UZVK{JllC$=_;C$G4ND3$e_$i)QtzL|AsLQ4iZlQ9ry!8%JAF zPx4-Ty5D%@MIbZY6k%rO>#Pt!0UkeSAA*s~U#~xHy!v}K3knJsLi672%r=e;7MA26 z6!Kc~Bg@S6)WL^vv~-*3o0`ldY0xM6Un~8Z`96XzUXjvnLcE_J#!fa}ThissiX-=H zRj&fWtWK4??x{F}CmO2|B5D0=1{8f+zKm?h+Vhwa6*e>g4?K@*BAmSvHg-bgLR1cH zS$pxab-?`HMku<5smEat)D2k6UK1F?hQ$G+lN9*fhRo(4F(#qp;S?(D{>C06T>v;h zBZH&xcWiKymbFmiLFg~R3Pf_~&jk{s&BTwE^_So@@%vnGIDQfPfC&CRI3B-dc$2gb z6F1|S z3m(?}d!aZvS!OgX)@r!08wMdgw(g=D%xRcG@wkU7}6O+Sf(0B+>sU?2)UT%>_NDjW_49dokB0nu}S1Z0wxR zl4wQ&4cZHXlJTio`3|fvctm$=*%e&ZDS3pKWz}Rs`CtN-wxj@O02c;7!Z&VZpQKmT zLI>MYzTPt`L6+p?0Xh_PF!#a)8LBu2!^Ajj(`fVcj(iXHpjNHngQO8$S#PW|Ul2rZ zFt7Uu+Jk2D+1^NyV`h(e5+p@}g@;3NxnUA$xr+w02xgD!voJH*d}dvR#l6kGl6CGx zB^=#g-i$Cf!%0grFy7-v*#oM-o)u|)1#%v}bNN8YH=((%^>@wp}7 z#|YrMHd6G7R&@tzg_%dn6gLBbdx{}nl0surG#>m2;zp~!HHWez4YX8agoHfP{390W z7<8Z|2JRSOb~fk{M52~!@DIo5$E>mjIi^ZUgWH+kA^C2U_>we`gE-qfWjzw&lEJ(` zV@ekGDQC7Efkp|F8a>u8Q;&7RE=2Df-RSg@ls})8znkccm$5(KRK~W!s%1yoc%hVq z{PpG~b;g)XX|)5o}GAA1{*zOT5^)*)Jpr}lTbLd`CG8< zKyz%z_Of{$**trW0sU_PTlA_kA3{UHwAf^c8sBg#AB}+=!MzAYRSbhs*BM>*^5Cgf zwHU}E(D4TpeXCWk1ozpDV%_m=vgg?7XJL1+Ysv5<3>&K)%p9K1P*|q$3oLQBW*@ij=UUXM&f^fT$7P zR%I>Bo8D&0TOnCzj)6V7_5RIBQG zP+V4*3!&BvXYdZ1Ry`^UjXT>hn!zmC%ESJyux2Ap?}<};I342Tl;Zu(&7vNSze1r^ zD9fknS&^uRkmJ^*WEk3t&w3hYRJ}3d`>eUiu!v8#LmaTM@{m8j+0@ViHE~i=Zix4` zbsKgXsaVk~(_gGDtTz{~N+h*=3$qY1U_U|0da3#M!7U`vQk&loC085?X;l*ttT@uU zJQ%7k$;L+LG4m%gD4>Gi^7Ob{`DEPQ2R@7KRU*_Ee_* z0ri&qqhfA9H(Rzip&0!fY(w3moCDaXw|1bv?DW|A`Ub}zSwtfJUn%yGzIwGx|Eit7 z;z(Gl{tS~7nTRK|GCIUV9rxq3WeM&!h)Jsg%@F&_FP;T0q%KV7c0h`|n6S(N2zlB-_x|7Q^9B{!t zJZQRadeZ@yoCKl0R3y zjUV$o3@*m6WBe~<{C~;#E51U!AMq>hP(mXEjBPao`-<@jvLDgb*MuMqj!W(_w-K^c zr4f+zgsdZEg39$KA+HlMRzdzm$R7x~Q{j10?)&Q%qC=fJ2J%rqf_orF< zeo(}^g^*hmWE&y<2^py%>j^0$1X4!idXtc!5dx_#Ab%p{bV49g1mtN#(g{J=3dqBR z?E40gn-%1XT0lM{3i1dc)qqg; z4G<>9Jy2U#2}y%58gj^_7_Wjy-)2t@MS2qDZpal&M?e^c%z1VumDtW}KEVjAAc+Vv zorsuyBDl-yi0n!60x?mnRgc1bK_n33(aZ&-i>p{T3IP#ZZ{E%-Yy$sr&S^(PYmmgM zAq1T)W}&|m@{$}1O4LZyJ0VwWac;f~)zX@qBGoabLU)L2{*kH15IfjIbkI;@A4tgU z3ety=s|f+)i8LIaR(C>BO9A;QA!iW+mKOEyOh|wbU>1_AScH5*2rvuC zhlFebgzMquxN&XqeMt6S9G1GurjxZftFt%|J9lhE$I*gC zJ))QLC>~9iT$N!cBS8d+M%7j$SrgKabC@e=|6cB3;fnXlj)4UysSqLY_^wL_K+^9wYU^ARPGFLz= zf)SnV`F)Att_bXunvw?P~*im3-=3?_M8R6YC=l22V2==PYuP8 zk?|b0Wv&w54g(*M0-sQl6_1#RmnNWNl>Vx-UBM8=QtmLmD$ zcb<%oyE9ULCAj?sW?ZB)PVi(Lp)#_LDDe?y9HmOUn;FNejCr1nIqr;un9&|h1kwe94{hGfYy6(OSohd>@j;srFT@@L8C;LW|H6zBDqj?`PYODCj`wDOt6)ZYY9O!1!Nr| z1%#lP0`fW`=M#cv3dkP_$s`2L6p&vLa_mb$auj4XA$%W_%@k?wA>?C1&`bfjg^;y` zpqT>FpO7VhaDa)vS8a8+@(Kv9M2@b^QH2~_1!DbCz>3pM-FNf^}xk zB}jXbO8X7hYL0>bDZ&Zb)tMU89)UhXD%E+?zRI-8f&V!gXRTLhpY)`Cm|@lb5I*F; z#CqUa7V?D3c#9|F0C&cj%n0pG45`t~I1(9G3{^_Y^@Q{z1d9ZrnZ3IlkU~PRNDz=W z2)T$5ED{7{F(F+DL2Cr$DMHc+0kH+7nvlKlX~TI?K&BA#DIt*I0y3JAe-MI2f`F_f zO_mdaMS_6*m5>(+!6HFGeoe@egkX^%AO;~-gkX^%AX5psmk=xx1mreCMiGLx3&>zX zt|MfKqR+-< zvV#z86bZ;}glr%rpdf<@SwRRU9g!wP$X^KQpdgnL@+={syhw8nA#(}A^dlfA6LKFR z=nDZkycCeJgrF}3iJ9Xeb~hguF}$Xec0;5%N4CprL@A zN64dufQAC15i)}i&`>~rcpZ@Ogn)(u(n!cKLO??S*+NK|5YSLS8VKn@2xurE|4T>? zA)ujv{0||g5CR$s$YX>Y{R|M$P(Wr8vWpPVP(bb`yXbd2Vs+% z_FQ-~!F`jvv0`fxeO4m+6|7^`8LE)6dYQ0@;RwkBbTCd6W33858+y9jMmB#1-uHh( z%NY-d^{&T}68!o~jBgEjTr1RVx4M~EVtfV9Ip!iv+6p~_HX^Z-T4QFs%9%x;q<}LY zd4gM9yI7G8w0i7~m+@o9Um+31>9VAGxPno*3bPH1u^aSw#s+=)J@?)H%dxkODGwW? z0{UeOMcr^uBvW5{`!B~$xa+nlKK(Kr{6dZOjvLBPBT=#u2*WoC_VgP5f>M~Wd}1k5 zCa@*)*cemf?GL%CFSX0pizHpjMx=n<#WKpKPhxG3R|YoFAYVOooB~Q%e*}E?o1z`J z|MH$m<TWM!`b1rW-bY8!HmYrc5uNGY%mpyL*aOJ+-;Hx#G>>^9Vxs zn1GO{q;;GrxCt%xL1B~F`v2H_8@RZN>yLYRNu+rJRH|5`y{4p+Hb$aRi6+X1B`nD% zp?MQE76_1l5}>>o)EKFdnC)gQZLLjPTWPCp+WJ>&twx(FF`8f-6<>>0EA{natYVE7 zTf;uzGxy$oW7%Eo^ZEPq`TY08;dk%MnKLtI&YYP$_ujdz2kyW#uwHoblL8B_eTueu zPYl&N^{u}U-HI&|?dwy~UzO;mh&JvvjIiX2-H7oA?!#I4N!#WpQjBCuBU7N$eoNlF zUhvzcbvMhqh8rzI{&k@_B)O9$+-qkZCW71MA5EA%q9kJeN#29yhzYWO{S>bA%~@NF z()5WKb3SEFOZ2-?%bZ7z+Yz!LVce1Nbl*zu`V7f%=r3gGoqFH%D5OV;IP0A!ltQ+M zLiS>1vc7tPC~UqF|F{u<8fw*lB39^~dX*%X3vl##rxE>q)T&=cv|7Ja>2D%e7;Z2a zrVB&4k^W*y|ESR(^+xnX61~ufK2M^bG@^@*=yN6d3q;HM1m$P7@BV9y0?alz&Jd0S zL%_cz`f@`+nh{+t(XU{3r2f4`|JF!7^th`1U*OXB8PPXO^j${ulScIKCHht)`bQF- zX$Zf|h<;0=w;It~B>MA4^mmNtKS}g@Bf3GoI-m8u13nV(5 zX#f2#>i=ih+RXYJY%XC-Fr<7=qHCP* zPd0cilpHo1JO>S)%_8C!qOJFMyi;pL(sM>eKR39ROF6C-A?}w4Tj?t({8u7O-m)jd zYYny<$#8{{mq()47|~@$bfrX}X++mb^o2&W-H86OL}wV$OC?&}81$Ys-H6VU=nsA= zC3Q%qQ)`4rGenFWR5iW}R{cqMh@K?Tl}7Z-M)dJgjk}1(o~g*4G8TBJmJ4H}Vb}K< zjF}?GZOD1$IifExL{7;28ur!q%N-cCjf}BTl-%Qy$EM}g81t=`Bxd70 zHzY5Y%A+C&+GJRhpR@f*+b`7QjaAf+I!ab4VC^v zRQl6hq7U`FyXV23#19TI0|rtJEeiW4#abD6>wQ4Xx?#;P&KwAWfc|vT7$ywWAmDyYwB7!9o;?dPfJCw=;x3)qeAG=duFmuvBbyJf zyK|9;7x~=De~|@P*SuF-?z_b09x|T|H}obs^XSL|G7gSY`lH}S8O@QW^h^E8S~dVP z&krzNr$h%9F_D-3&HUOj+dtm@r;Hte^lIrKAzPmXBr1Kcvi3i7e<+QrpCbt>-gml< z-KTh5Z_8=y50H_yJ$<+I0%!Y3Li35ned@G(<|p%VCSS>zZ@wrp)bf_v*T`=_?72wO zoIR*v!IB@l!tq42?1%QiPqAsm7u^1AJd)JBW>W^3(0o4s6I-S^y%XL(FTu8tv$p2x z{Qr8(e8QepVI97f(>ZCVhr#b3C)_V_1piJj;J+vQOwNnxX`N5g(=@4%_beyJOk5s* zl%AG*TfpBN_8+P*DSL9-Jx3`aUlt);8iRsvKSu(2(ibq8*y5=CxKj9)V<|fVn-S%S~|ds<1kvFm_wxiy5B~BR(-ke9~C)|Lx^h`}aPW!{}@7r2Di! zLz<4)NRzV|;g_<8Qlha*m)ibQZGh{ap{n*({qvQbWA@MR_6_%EVb=qnXusbkhNE%W z_d=B*nteCmp`+P%C%rY)zW@5YNcOGfPa5j=&!6@qS+#O%S-q`1`jJ738av;B0$?}}1R_{sRbb6ZD z%VK4jeRs0t;?B>5t4z2K$aC3 zk(aXve=PM4cKE9Qv|ZauE*~`~F@JD+K0EVN*~akNhB^NnyhG*?qxkn1!tYTGQ)Pl2 zU@+$UtRIz-w>0Bn>B}pPjXo|=TbTceOQAVW$s0&&?TGg?%q=yQHV@<}ZO{NSKWg7w zVkp(+u8_SwGWD0m2F$(CQHfJk(7V!U-<`st`jCSDZyw8Xc&e)HUPo4fyP(>$^>*SM z?k~9W*+8?POqQA!hwSV$gC!nacH?t-Hc6HwlF+yVqwdZ;cj!KU|1tbsVVP{VM$V^*IBvW29hzj=yKF1!ocGWbQ(PXK zeO^w>yDoRnK9~=E80Zf&-=jg~n$9vpZ1O;%QGde-{mRlmM}hX|r`zni(?u!S{Byeh zYpaus*xk{F&D}SJMEY++qOyrX;C*6}`b{BGe5pib zl_^nIheYL=0#&NAkf=QS?luzKr&1M!MA4usN~B&gIFq;#eTq#v+Ye+ayV5U~zQ6|w z^sg8P*}78>JeP7ar~gonwccUq-MHN~Z(q|%Oq-P{weMqXiLLd2E0>SPCmdRyl14@* zyPyvK`sT9?%<8-c@uYT|#CQmi8y`L-L9sLhD z1gD2{`~KwP)qaAx3AI^sy_Y9QIXd2u!ufY>Wan94pf?)qMxF$dZM)RRNg6%H!|oU4 zLC=(VZ>71Ob+#wd_mrWtIaqR^%S~H2Qx4>%b61S~SnHfeRnxYf%qc=vokJ zGJ`gk_>P3NKkPL6Tj0Aeb9%P^U1jKgA^gXp4{MPZX5X>%k{`FQeN@7c_T7E@ss7ZmXnlfYBwvCt3SgSwpU4Rt z*8h$BK^z})DPtdu0%H$Ib^j#Dj&cgz1Ijb$F7<@4$@U=Hz?7t2HOIVH>}jrNEK|Zp z>5&=VR=2C&De_FQ%iWNURS9>dn-`|#*cfY8g#6=w)hgN)rWpp}^D`7<6jsfKFPyV9K-JgW?!AHo-nlH`i z;Uhgp1=5@Yq6kXd=5S{j#)&kN#z`j^#Zr?Oivu*Gg4eE_@V0UNQ~6FA0hX9D`yzlq(xcteOgGwxRyd+3P$ z%80$P$Xl8h@M*XM8al*={XXqYzfVhM?ZS9Wn;_|H%BflY?4O)l{!H%d$XlaN;c%4& zy(up3VR66iN5ml6`3vJyH-PRSR9@o2)5*CU@{V`yKJ`hLZv_c>L~j>Xxd0QU$K>BY zk*6>Z?t=vPVX#|z()K^O-Oj>g+DzVC690BGS%vsF!@1r2Cvk3tdES+V8@!X^dz`p9 zB+bg2_hg#ukJ#cR&UUJ5eEms$99zP%c14q?o8C>l9jB&u#k9lO!qt&Koj z@dfO1D`u#Fp9`-b=bgcRsQXhl2 zlz+@H{i76GY)!J^Lu$%F8pSr;;zdXqbWnEjZw_%n`hJQaUm?mi<~M^lf3dU+_F!pW zLkyO7=kEm;JP7E=d$F{}9W;pFzVMGl?h$j3ruMg6aV=|I-pZ^>Za;(1Idc7osQMwcTNx8dUb9>w1(2M=dB3sk* zgk9>}KFr4MlhmE#sV7pQ;5SM_^PB1yLAqRhgf7Uze0(A>-x2}RzzV$NXU z|5}M(caR;I30uddRy`l^tL^g}<=5o~yy-0M?*997RN>#!PkB_Va#69e+$@+(SD9&8 zvW_CUbJHfk%08I(eMPH|$E>&TPYa{SEI-+sX|+vDdY@ z%*wQ_vsE!1S=B+K@n*$BX}dxgxc+0?8j8~>x371&M)#@k-|&jFi^%H zNvr6;SJ!py_$@ajPm}#WW?|rece-Dq!Q}f^s$Fj*{X%#0ujR$*qIrLB(PjTuGtO<~ zSu(hd@|$5TLHRwR@{_4gExTEv7rii$=ik#hmXmDGqb_2(@4RoR`-39)lx6T4SeJp}MZupC&J} zw{Iel-acnHz5RYMM(Audf`Q_hI8n2=~stLU!U}Nhu-iXttRs z;#z&N?710Ho;kH!Dfe;e*^#6^y`bao&C8vhQf9v|G_U2W4YVz0^NeWL<(<3l&=hMv zfYoI33epwl$cMyuxNHxK;3{1wYx&d*LpxugDZ)b2FQH|uTjYW9ovjQmxs(`VUo~@o z&@laFhScvgRlj?J>PKEhteVMoB}Ez))q?Jri~jtJlji+cbw~d|pNbZ_D!vXG@tZnzls7PvlQGAvg{40l0SL)l!zwqMJa3p#u4yL{BZbFAm`3_NPD!*e;7 zvRFJWuT8V#8hciEs0W!UzQ%suY^`>l{o^gP!IfO-e~k)fLi@7-5eC*FV>4(=2rfL?-1j`o(%V1swWNBMO!I7+lJJCk zS-Sh`X$if=W88li>%+Kf=H3~Y-03fnF6Wub(G1xwez3(*$FKZ+jS8u zJ}da!BRt6{qYGEv{#^t+L)x8WGy7=x!9+4M^55EXWyV6!l;2TbcCk*|`mQQQo~2NI z3M-_h8{@*n*J(7j7=ZpB*S-ki6 z6Mc6u74r>#z&|GsHF>$7R&8f4;8{}@^YLE2WUtuTWDlR5@_a=m99$&vZBV`{Vsln3 z97d+>_7J)Sy~|tUl(i|DTI?p_Q=vhb)Jgv} z)+OK3SBZPaI`B#E{0zN-Q%Q_+Rhk}2Gehdbr)l(4iDf=Tfl)>CGEqZ??%ex&vNP#2 zCl`4(rXvi=ln2E$lo*zvj^$r0Y51vQ6zR;OjN9WKf9H~f=w9>~|ENu}Dl z^G5d3G9Ijv)r;o_-=|>u8DXJ^zN`Db9&&MG`787LU+N*YMgS=sP#&%3s?cuD( zIgz7(^E;shKXjXMKU*FzqX;~>bv*BlvXi~htrri^dvDA6vfruNQ(?+I9@if=_ww|% z`6=8N|6@yj`$Gw`p-Rh%Z5y&j4ip+|N8}ph!sQ7$=WiH<)xwGQthF37P~QgVZK#Ln znG*eNl*>52z=&QW(d9%N9} zm}By}Oax^b`TRtpWs*le&l=G#iTLE)fvpqZ>Ov9k=($0-hO%QS7*{4Cop&Steke};ZB#gC7t}wC5M7JOx8Zs zuiC?$ET5%<)qLD(Rvr$f2upS)T~>@2y`dS&c27&X-NP4G*t;d|&JT;)4qrt7;m`QHUHtvfX^GehZ_uL;G+_%gb!#lAdg+9-ZIjOt7gIur!V`jv~GCBKn0_t9d)Z+z$hF#Z^_+; z+_dXwD(M(hfjZzzTg@0jLE61jvJm*|JH$cFm`7d#C5wLFRJrOyR|RErJlS9$mVEin zR@(KyrRCb$g0o|yB|k&Se^ANKMgE~58~u$Xxhyfb0>UVF+E!jC!Gg{@m-9gurAwasA`|eEG2DgHxWy*Q>rkLX98>kL5UM6rQ&A zvVfHDkmI30sL)kmp}$Z!5bbFf^Eyo_aRq7jsEjWoiEQ-XlMX{cHb_10R16QOfU|{x zWe1C4p`5dLWt|twxoW#W~s!ir5#M9ndWQpjjNAylK+4J4C(oGd`%rXQJKjI_S8bJU)8f zkSjLLoMrHI_E@vpLA2hQnVfe0Ht1gn(ft`wd405jI86Oe8 zOxXSN9UjP5#v%{IOTT(TKEJhB6(nHesuxl63n)27y#s;b)NE!!acZh-Nnv*U*5ALs z0UO`eYiXwp?Hmuvn>1E_d}ELPu#6DZALKhN?Zfmpm(d>{C7YR~!Jp4~)RAClCQNC1 z+a_Kjxtavh&&+x}gZ4LFrDT4eLnU)qnc2M}Q~wo~`)l|q(}NrjzDk@tlx}sJE|my# zXBN(Vz6sC5BzmVD_OZ$_dUG<9{SyKjoV(`%~ zq&zF744G7c<5i-gDXKbowt}<=BwmwvDL=yVUSxW%$CM~DJ>X^z+Cl>ZDDok6~3oJFWJZwuMW>IBrlgYBTwE*kO44hMX|m8 zs*I$i?msi%^Q@+1*XFAII>QRmoXe%CBVjqSS8>|)U87K#khr*MU*PobaMd47Fs&$TND4^TeSKX?{pC9r6qHs2kU zC2P;xLXRsmSwD^3iYj>gecrw_uev|xJ!=Db$mshUx|VPjxnD3xIfngLlLOt9@jg>N zU^!0;#jv!NyF_c7gNFQfakz$BT5|2)`O{qNI>?lZ{Y;tda<$8N;3x z+9P#+@fWHbgMVj4@cWx0PlA2TQultjKl-fG{SUdNaL{-Nz;kV}%l#)$j$VDGd^@hW zyvVbR-0bA0ZWVHG*3n09`Gumj%GL z^vj!6*qN4LnDbN5MJb2ap>*nwUorz)FDBwW>uw6n9Q6d$p*PbP8*vJI@f@2MYT z$l_XULKYdbH^EN{%gM=MgR{cT%B!yD4Lf0UdONbdEhxA5xae_Y@y3b~q^D42d4a@x z%XV_s2We8f=i1F&%6W?uncS7>Z!ls@kSdzK9epbXE=D)k*SSF&UCr zyZib!IiEz;?Vbna_8NiUB#5oM~Z z*M3blkmCCO)~RX<>V_p!8tycppA6*r`>QJ}W1cI^nYQgqFnEac+@37Cw;v&KUK)vd z9p3U3c0O<4qasq$u3vBsbMy{%hOIq`h5}bCrXN`9*;MubeM%ZszvT{A&(&VB-{pwS zyVE*er&xTj5LfZC-bP?i#UkF?N#+X?`_tODQa;%)DZ}OdKsK+Jt;enThj>)<-~LDn zJ#G;x@v+1AwUI)3rwQ-eYF!#gn8U+`fqp@QI0yEZY*YruOU*RjB+~MpFL&Ui410KH z$tr_$-Xo3QldY99R3W&pV5sQ&ml%l5a+nsQ+TY3Z5e=#*kfXi=vn}#mSjMvHp>zEH zL)J5xp*>VI@8PB|%BQnD_T={A327Z?PLk1wY3y5{k-KB0`CF>0aS_bXxu)AP1s!Sb+E+SZrwSr*?3h5_q8qfpnN0WqBt{bwv_Iprl) z`f|`6kQUm5k6ol3D;>k~IeZArnjhkj%eXC=F=e5=t}fOo>zZdyk)~HZwO1Tl9uqKm z?x#1cJ(DPxC)`gS^yq5Ul!DYHmXsfeUa#60v$Vx(PU zc3b4F-LDtTdnfJsMO0IsN@;L;Z_VmNJA)nkQ}Y*YWn<=<`md$NY|gyj&8zPxBZWH> z(nB((wIBZ_27wYJ=~Ai4H#p#%=|<9q;H0l&7{N)uLsG;vBSu~wGu~gFSIbi$*LSNe zB%8Bxm%8;L_n^!D9`0T4-aW`xmewATlHJxdbPRdl|7E=c@xdk8dWAo#hNwd~TH`84 zxR4Ty>b+l?(!MvH`BsL*b8Cj`tTLrJM_CfvIS*h-?Za#%+JKugO!3)6>`gD@PQVTy zX*y0`f^(dwrFGcF_UKY#cif+PS+$j}^TdtOK7ZwC@JYsOzdbLjxLcLQ!7ZH(zFnHf zU0tW0*Rk#Gk4)i}9R2sKhOi9nKtK|H31_coi{Wx5p34Tn`QGH&JZq3c`DFN%ec3Yf z4Y+7=i8a(@DF%nGnzs~H$OCE*;T(F3=Jhny(M-w@^4eyDQ&y{7e6<~py=s1wFvPIl zG>;M5s4pt(rETXeOyQfZ6Q%y}wm-yukpTZgjnCV;=V*y^8TtCfTaDhI2pW`k1Lq%6jLuhW3r4=P>o+N>G*0>FMCbB%rW)yxWjbE-n?X& zdrFp4Q(lXqrp%-8*f(Y!q<;2!&!%0U!a4o4m|aNw#Gdgo=qTqIvA@>=Q_u}81DvLa4^KKxG$Lg>= zm1r&Ys-y2^(`MeBB`?-9yG#LOIPaDu$DssCH^hm& zwZYe%V97CA+LYAWv&!V0aJ6iL*vhn{Z_Uaz1B*e?mCJpr+6o!I5`q{haG4!h4IJy8 zYMGHO==bZNtrIW*p}qjujw>1<3mMIH5G!*(rv_AcFyLl+QZr}-cJV}sBy`lu?7M&= z1l3zBSV;?uT_|6T%=<*7rQGMLgOpnoCh{Ye98?oAIZT#jVjkDDK?GxNYP6;6=5ZOA z$NG4$K8fUFh-yGyFk%hJ@6$wsC7c@luYE9q-96pQhRxB1d(J!$b%t|ZU)uFu4C{jr z@eliEt540Bw)J;RO#EYmJl`|;L$ztTk-vVE63z(UdBC2`9A1=U%;Pr`qdo)dewle~ zUC>#BLobzN18i~~SkL16(#f6IVoQ!pVjhA+&Bpfrjav$PRlj3C#$#K1)3)74q~pBo zmUlc2$?aSOcHBsS48GjR($B9EyOfcc06dczr9N00G|s(9dzn7+Ne%-=-KWmfe0Fdy zB4sYS^*$ViHSze2>R;yUBCSKVxDJ|xq_uk$ubPOoJTD6q_n{w3)2Q*;+~M|rC9s2n zJow{T^<1B4lH1Aa->sSZ^KugoNAx2uN$xa+$8YNkAX#^;GPBt5~Azm*@7RKFvYT2!HI-1wsC zi-)C*YO%qy>W{kTn9$<7i|6aRHy9-z{5re2>u*)iLzuTsPV2aZo#xwztNDVJ^h)o7 z16&8__tPBq{e`Wz!8W4B<8$ls&}lm=ZjoZy#CU9sT!i6 zG*tVeE^92gLo{WLP1^`IRJxfXgSC;P!74soV4PNP%GIUujGs6N@2p4Iev}=?&;ny# zbJp@hXYhQhT5CzBytrl?eNx8C0v=xBYASfNbeIKWSa12$;GY?e{e}7H7OODta%ALQ zz|mm;5M15`U;7@dFqc*`TH#+5QKl*`CgPd;W~Qz#t`sxvYPrL_HNQZN-8X#xe_5XhVr-A>|!2fCB zlWIWw%+yJ?rsm3;I{9DU(kxKb*l3%5x~-hyKhv#V{5Rkh{KHCI%XHrH#X zEYOy%cDZabnr1Y~zY1WCU)S~#Up4eGYg=b*8}WIsH&Bj)X=7M;e^7h_$1UtcR0owo zDg=UJGEIWH^Qp4x~ffDxv*K{+d5|3 z)v+nt%38|IRd&cv>JsTEyz*;mtT_FOji*=CS5}p-YpJQFZD&{5HkH?2Ue&0bzd*|| z*lacHOPk9ZH&ivxzPzTcQd_VT9_!skfQCjMo_*Y*5}uN&%t`k(Z=~Pwu#;S7q)`r=vgsI=s znx>7SN_elRYHX^huRDEX`G%Sbt*&L`I+~=$NbBbh$gQoZyIkbgscxvrUY5-yS6RWn ze9h7#tF;>%>o+u(Z^RPIjrOjp+fY+iWi#Nk^EXy)tZ%&1=2~ud6gaFJ7%24(PF}jc zv3{dr%l1{vmloxhN=Btim#^VbhQN9L^5Tm^0u~kFAA&hn6cm;g7OWZ-wlXZN$Wc;i zU$G*Duh_oAzBD9cMZv1oE0%=><=ew@lz^fTmW82W7p^G1I4r(&S$IgegrZStioz2u zTh2alb(h!GZ>rO>XH?F#O}b=8<)zw;Ce1YS1v4to#p@bf^NNjh$buCsmajOMCP2HT zt1c=oaFiA;Te)gQw$irU;h3daF{MShGw~~a<;TSym)tpL&m50jVUJ7hnP-ekuCT`? z_w2djk}K@-$XztP@+$VYbB6nk(Ktr?F!L|~7shcL!i_7H(RF1fMnAx!Sr_7KJx zOTQ7=<0)@UdkB>q#U8>K$5-Cy_E5Sq+#-zmUbJ}x#Tk{%nuUbSPew7Sse#I1T=E0S z#?*UgfmG9vKc7IdIP;N~_yqb2B#SQ}!!*WSpP*!M<`XE`c-qIXFhdU!?O;X0(r_z_ zCO-muastm4NY!q32P_#%jM%#z2Q0 zm;6AoIO`GUkmJfHkSxx8;zXMvzg)wp>G8{#WO2z?J!<^&C0RW3gRiT{p?{SuF8QCV z{K{}5%3rx^`HF%t!!gtob-o-aS$y&X1{6&{qvQvYMQ(>t2?B;Ru6zQ?;>;(|Amhp> zkSxA@f(&as`2;14GoL`g#+6SXS>$|Nt40}FH2D$OioxgUlx#@i1gB#TFW+E`nEH*t9!Gg&+e3)lX!a1wIKJ{mw}(*wA7fl840Tt^ zfFktl}O3rl5aG%x}uLZPYRbW$>Nf)G#-0BC0xEFi$}iM^y4kRN*0&=Pgef1 z$CbkIUFSw%A44x8a%20E(d_ZajqOK5kxkAyIeue?$H z$g1)AkPr?}E^&=6I#U(%9ei$ZSlEouGZa)$#KZ+j-WBg?0 zAKQ;C8{d2+0()e=gfhnTBN5o+ksH&GgvyQLM?x86_R9$Dag;Z<9|@5g&5wjKj<3AY z{7Am*;_wTWXybVV_Q-k&V-$lJhg@Yp5&K`5TxsEP$W;ark6cmkIOMAKh%9&2`1CuP zeGL7EGRD;JX!f||#?)`9+$j1DWsIrc(d?1STfDqDe_8MtYg}W=#4cLJk1RL8peVS1no|&Q15xcTQnHBpUgTOH@pS$?>NR5ha3BTBN!`7A52uP9gKoUZfO5iwTxKaNFCACNwjiDNfx>M z`D<3OD?yM8D)P@zqUJMpvUv0H&s4^jk3U(w`IuUZF+Pu~k3U(IeCC`TtT=hLT7EI> z6FFJj`B)klXFgW4xbv|zFwT6eWKr@t^NdfVx5&xj&c`zBnDq(G$4VA=K9&N;nU9q$ z-hASkN2%@|QXhY^xbv|JHop3pi)i$R$o@?_+}U$KksU-%7I!{YgT|~+Xg*f5xbv|( z+&J^GlEs^kzd^^Bk3U)5`NVApA$p6$4k${n9asv8Ri81Eja{Fx1o75qjAZfV<5$4= z>f=uqC7%#Q7sai&$jRc)CvLrk=3^y`Hy_k)45%^n7Lt!Y*%H5l9+VnYWVK>K|pXKtsfGNND&nP=cMmGE6XF{!nRYN5~asO#PkTwJCs8C)7gvhFuSGyCu9qfkz?-f*Z|7jkHtZ8|4ANWrOiYB z5HtYweS*AtNYf2s)ov-Og>%t;;jay zo5v|&-u;1aD?S$Hk|qbrhBPP>N{3P)8#GLQLr^R^pH{q1*#@9~s1NFeK1o^1sH>#g z$rl9$J57uwYdd_Id?zgh>f25JP#$H-g>s;5NP~jgu#L8!#&+*Qt>$q9SRE8@#mB-j z(v(2OkQ2&-a-ke38`7XmD3+X0D?XjFr9d`l7#$8l<0*@7@(-BUAC|t)Jnsz)xAKto z>*pgpyC5CP<|{&(Pzp56c?$h=n0~55{ZKEo8(Pbb@It%HK|7)Glx4fg-)drYSo#w4+!+>b<&jHx8=nGf zg{r&pk5CD8Gn=}{L&YYq6U-Y&I!ksgX>y=!NP{w=bSMR~LBo`52(tM98&Idb15iKI z2lYZd&~B(3+68q%k?OM34DSq!-_E&)ze|U5pdozh0Cf0A@ll`2+XHrwBb_C`i!_~3 z8`J=mLB&uWlmlr{I%M(xH=vF34x!5ds1NFax}h$p6KaDRph$HoGsBC+;`2CfMQ$~; z6WT8HbHbo%s04CCxllHg38g@;n++OfekaE|*l!+Naq(a;X?8=qpqS)N+2hc z3&kVr)8NUbY?)9BG>q(Fm5%I{8aU_>Mm<{d5hITH%!aJ+ zcu~F|mI4jar*)_w>VA~maf}8-(_M~SiF_T zPQt~0vY|fOuovA)TUqTP`t5|aLv2ti)Bw4@Yf!a$Tn3f|g4QbGL%90tzFSa1*#UA33BkkYH*S;E{PN)mo3GMh! zeAHv|?gqQZkCgbw5A{L4kj$|x{{IG)@hAt9F-e0mp>!w(DuYTO znMX#di_;9>PQ4|*4a}xJGoj&|&=1rPz5T8DsMqA(4elC8I!pde(rkxXp=ziEazeRK zHk1jaKo8QH`2PO)5#P_D zGU!Uqj|ZQ4d&Kt*r~tA-cfK{^+W<8j!iU^t&_d4TIESzg&0{Mr9^6g3y9P&mmqF8^ ze?foIM||Ig7DI{9{ci^Hi$~`FCeNlfMtm1Qzk&YwIzI3dmNkuZZsTeEo8m>1h&NSFTheLmYh#3{#45L9)7C?x*a+e8c$hXc?|tR*FsC7&V$q!vSg$X zHjDEf=p%fc6)xDz`DM`S4~+P3fxi6Ui0>lsSV-2F;-P!~VDLQ+UTq%7lg{FKeGj$^ z<&ZWRypK6W9kiU|FMvN{PO$>A`2QPFr@Y5QzclA7wctJ6KRgF|m$^$FI95HbA$}2* z2;B?)>7Eha*Jzhu{vQzb?HBk+FJy!6{R@2#{P>sgkrRI5{S5f{^YNt#mc5a5L;d(- z(jEhLfNQ~@L%n|<@oj<5z$XN!`TzRyX3E=zk4mKMFM$tnJ|FDGcjZIF_^Gk#@O$EJ zfTtOp7sTho4<#bw32+YjZvM@GMjh`4(qGB(G0;)a$M4`np#M~UwDfi4QwptuilAt` zk>buL&6!X(BxA=Ll=lVbzE_Y1od&)6H|+V<5#Nu&HPF$}!GRIq_0ZWbkNDpCD|2k{ zoL?C9DCcr~FJW7P!mapNcmZ+N5=&R7zIW7lJfS!Dj`6y(EV#)cm;z#I*`=MO= z{x#qM@RO9~Ceo!-&l8dH7Jd0X$i;CiS*7sJ+&AKT^`Q~p_o3y`roFT!bQH(8^e~=4 z$3m||e|m7l_aJebZZqgM&gHlYoMj$caq-}54{)y%S_Msk9{vM!MCf08n14X~e$SW$ z#Uty};8{pn^C{nC%KESSxrPGAQ;z+l`4(iQk!M{_heqz>dIYk<$HIDeo*lr)z0BGG zxC}auKKB{$z$+uZZO~-;V*!{1?IrG7=w8--gf2FZ&l+THP!EJ#@v-n-(lkQ0x9|bT zxF34sP5dx)F*J*~3^10QPb>cAH}D(KDbP!=(+8mOl%*Q}R??mWP7F%>9M`ms&;sZ) z&@;5bPeDuet(?oT3%s6dPX}a!e%U+XTL%q6Cqh>}ifrf)(0vEcC-mlieCSUM8sS`y ze?eGXP`DKz3%^L3zdkbJy9t^IJ@7Dd6=YrkF5`SQ_{P3KezD~JZ^z$3*;=8OA7X3* ze*liB3^nkd15JdU*~^>{dZQ;G_SkC!-_`KTRj`+R^-31NYMH!%T zy5ge+Chx^y;yBV-@@tuooC4hodAhmYhCaA+#P=NdD=-Us{*L&{V#)lpv7R#jm@?-; zw{ng1KC}RO;CAd1`T!cMjLF13#&wSyI-lbe;4tkH%zp}DCvaWa_XK?pIv-lcdHav! zqbD9W_&UMW=5f4fh3_zFHa&(fhQ5Cg|IGQT;AxyM2PZ)H;S0ti^V8-{`V(u%l-Uhd zf<@pyeAaU4kNB}`p|Q%ikhssn_iy}84`lIe+4+p$wQ_9! z+N^^h3=12&z=#{L@ImuDACxdTFO?5G%jd#RU(d(5tN6;Z!qd)|52o||boGI8^*M1B zPLUd}R3C*_X*Sf>uPd)5QTFtfx|)p*wN)Fd>YA%6jnAkX+3{bcH+V~%s+vR2`I>t` zo|P#Qiyag(yi(T_-WDD?1o+%tD0JBo111=mN%D6 zRjp6muj3>4e5C!f^W~%J(mr)njYb7ur`Y)tZ{?8>_UY>iQO5 zFsQGqy;56OrL{EimG|}ajas?Zg!tMjzV2?c{3+J!2R18&kFwWSw$$qbdks1W-3`4>oMmv*f12w{4YNgCr4{A1wWX#4EqgS+ zFJD?ySJP~zvrr{OfXESTho^67tilwlNNe^HeadRe8*K(P%hRczmEk%|6{|u~Nz_%{#l=CY4g}kd&^ksjX@XKBua| z=k*Q2XH8db3_g_xZ?fetU$%1f(t^^37p*GDu4rzZDXpK=dUg(NTx9kYdW)ssrDUgqQrfiCR0XxU2k98jig)Ks-fZGCxVRb}anN?R%B(p=S8S6*uvrrDm# z{<5d9E3ed=sw$dm>g%++mW}JG8Vw-<%Thhhu&m&0jdzlQbDuqXwzhKliWL`W=bml| z$)3KlXsN?+KusE!tJT#vYZWbxjd*L^KvPSDsMhbdX~;lVS5;Q6JzH$k(6?coA$-zN zLfI7#q%WRZ0M4mEqe-j7Jkfj{iY3CdLCxGUgQ8jhz>!p;-R=ZqA z#_2xhiU~en9@rqqCQ5i0lule9C?lFZ%?aFA@4hrq5Rm zb$)3yT|)Smq4m>zzRf54eD%=T&?}^m#2*~?EIfBckof@N7k{2|L9?LGLc!?Q^p7m;3VIyc1N{ox32lR#p>tU%`Xe@BPp&=q9KGYJ|$5Wza3yniskn zs(~(q3ZOHgQ=l|RXM-j1$s` zi-+>y%Y}akvXQ9x z_$XmQ_nLGYDD)kZ^5K)d_%Z$EAS5G>Otz{@jhUJ;rW@ms_1}nJZ%iN?Thyp;#R;16 z6>5SxX1vTkBSiYstKrw2Qv^%5*@x4Du@>BwhrXze@^MtXRI{ zQf*N=Gvi9Fxn8Sitm36>t+^Vklvz{F`kIPzHA`!#VFEl`O|{j;<8)(EEwc}bQ@?&a zSGqC{sjaG$d175-RYm=VI%ZG^rpTI<%b51q^g{gRBA;*Af$f_p#~Q~{w1DJN~ zN_FWpR{XlE4K;NZhqPt54ogi*(zNEGc{%AnlQtnML)Djxu2}<+pPxgz;3fXE5dJUE zQ7PuwPVvXr)>m9!RcX7TzNXSPqqj8kyIQkp*Vtxhwi!(eY?s)wTk0;atKU>N)0RzEh86jPrWZ5j zLOYh>x0d7kz?+~c95;Lg{X^Zvmq1RAT`L)5SNMFlLUOzZyaSrTaqB94Dd+^-z>&>6d>` zE?X*U>sd<>{Ri1&ZGHXaEe)!nvsJKM1Fx^2rHPU_X3GM@Vv9|6?t)Ys=6kBBe#`30WI_;+FsyaPd0os)24(s?&dvDvu(~ zX0!bPOgWjwLa+?n3wDD0z}?^@-~f04d~L4H_71pe5hZyJK5zg`nacSKgo9mREm-y< z`GRfW!(cbq5B7m?gSMBB_-v_^3(Nq^z-+J+oDX(`#b6&;2@Zj+VESK=_;!GKU>8^n z-VNGbrkoNF9tPXMS!t9HTm|M09Pw=ii@{E?59|eV{)Rpz9!yTB{NQ|W04xEAz*;cx zl_S13i3e|#aIjm>Up?YG2-;pFf3N{O4EBL3N0Z;{M|^X^F0c%=y>Z0XEaBkIU?=!6 zI0U{e@o%D+V+aQwV7h+9*8%2%H-in}-C!4Z5bOhA18svxd>?}vm~pJl)&rJ-#cz=g zECaj2KJZ0w=n(qOK+fCf7aRcFz?{Fs4|aq7a{dl_`7HT@v%oemPtM;(k6 z2K$DPFX!)3kK@SqALtX*KBV2iKCmCO4WlP;0L(m|`h7%ugF|2~IPmci-!pQ4=UWyXSf#tYLj?&7i^o%eNAv^3imPTM-5Yn2fM*G zuq>5l;pH4mKZ$hdqyvYJ<~;2Kf;yZ(u8YtYm~%1hA>nIzHv3f0FXcHa zun#N)hrY`5WMEq<`T)x+D34$z^~?sZMqgkL*Z~fJU0~;C$|dLEYhd~o^vl3i3_8G^ zYk7x42C_EV85{u9zeKvPQ(my=I^=*2?dTsI0<|xbu7h~67+ec>Z-oyW0{4Jz+jy^J z7WD(Cfqh^O*!DDb3HI>TU{tHB;{GpIdJy}>+i57+>{Ch_33 z*~kZTz#-59+Wx{CK9~;Hf*RNc=72YXdEhRv7~BIkfPG*m_!`&^4ugH*l+!6EsDVS^ zTu`1bbb{&NS}+G}0E@v6umRi&c7okt7x*yP1NMVMpe=`Tyhwini$NFI0G5FRU>i6D z-U!-WqP$=_xChk0KClh^SmOUm|DQuVm!C`O!)XqdMI2X)$550g5;99U7Yyf+}?O@qq z_~vpBI>8+7v8)BVzy>h=pXd)P9t^)JGYEagLHiKngC)f$z4t9Y( z;1H;THn~)Z0cv0g*bUZ#ePA1CW4&Y-SPTw; zW#D134NRE_KbQmdfevs0ECGkWS}^^~$N}@fU0^3T0Cs_g!5%Q>T=>BpFntzsKn*Mb zbHG}#7;FREz+GS;H~2AM=7Pnb3v2+(z%Fn*I0W{9w%I=4GoS|Q zU=H{(SPZ7mCqFP3>;oIX0k8wKoz7Ydm=4|!YG4nT13m-hfjU?QehjvO=?lmo%mRDB zT(A#xfdgO}I0QC>wj9a{YT(Ua9@q_*fe(XiU_aOcCZA7wFcTaCXMwgk*e#e2t^#ww zYOoCK1iQgrum^k*8~}&F^fQoi0sP=BFb~WFi@{Z316U1qft}zG*bCauL=LEdaz{P~ zOwOad!A!6loCOYmtH7MOTmyo6U>8^h?gop`^7-Bd+d!M0e83E_`)tkTl*m*8`28$P<&xP<`fZo8KeB^lj~(&-5VUPbO<$jyV$0*O zADZ>}5nm5zTa=o_Ud* zBR>htA?$9#PEjF7n1irB!j29KD5HY2W&G0G-+u7xibzF>Vc5H^>vOC+tz zbBX9@>55A${iGm+zKUE;-(RoOWbY3w^-s5e;|<}E0?%~#Hse-T*6$0 zWj=kxSI)8gB&>|EX2Mpe5H4}RX2N<1YnHIEzL~gAwOWDXBfMSkW<0}nx1SgL+m=)) z{kUGS;tGh9ybi*f3-4EjmtPh%Kv)N1b_q~zwM^{Sm8z{x&2%U;QhX^BY_{9sW7skI zXqO0lIq*FUUx}X&du~r!oT^>vmnUT@f$wAZrV1awJgAniVZz!aK-F8-Mfs!}IupgA zGWnA)_a7HdHk_1FhmE1z){P7Y>pg9zj2pyvdL{oI$%|66w@tvgwNFeen3S50T)(qi zI2nHN6GNo$ApLYn?@Ud<2<=ID%0!#(X2L~h5`Kvpt`UAW;c{6d;bmrcF5w3w$SEfL zwFq*m3ICYz<4rkBkmKaIjqqgFtG+Gafj+4EgG2fQ^{J#X!T!NH`HIwRIQ$NSq`A;R zWR)Omd#EguZWG7!BD%jJsX)fGg)*j5k8KkjGNu)#7AL;JkY*n>q|r)Jj~kKI z^XDVJT^tAL1e^EQqal$IOj_8l1AMUb**?Vo#wP@Pt>u(cRLfmqRTawzH zuw|0+-I8YtOgo5ckvPh4+7)^3V7$bxC@priGSsXLJvm6*N7_a+?G70u%&|7HETAW` ztp<3ro@c!(n73X0UyaoPRA1f+-+cH!BYgZ^(Cvif685eHC|jWwcO?@76HA zESkI_EW{~oEPeE2vitB5kXDZ*gi@LgM|Cl>2Z_ z79J-&Z^JXhvB7hZ@O;bS5gpgUvzxWp?+MQ`zmA#I>_}b`&~c42ZhzM`>b{G#(^#k7 zjXw*rFVdR!Mf;d@roz;Lgd|%+L<_@840|0$-VWAVeBe6=^oW^+>amlRLzUYrzOz{%;q8FT+eX`6EI^gMn zNBM2lu0_(W%m=q67n(kxIn)IxTi-)EopjwIdx?m>MHzG@r~SljWN=1@NR zj=yakBv*bfgZX*`Yu#z49J4eB?D?W3Fw7WHyRfpN28q2atzlZewq>oiju~8k~ zJ*=~{>|plKulfCAGK2I0aV(*kaT`>e_y~=-!^DwX{buohE@4B2T`3{Vi?+$NMZ3&N z5*Hh_RC8j6)_wb3uvEgI$J+h@;a@Iu;DC+fo8!`DR1&2HxpQX4q(AtWG}?r1$vcv~ zV#*@#ZsheK?}<=(A%3tqOrB$MWO-7bWG4K1+-vx;$WwNx+Av?Wp-fDSHdMYt)gS(D z_`e^CA8#AZFZYTx?p@px%CF`X`6g$8+-{9TPYY#Q%2MkJ#q zL&Io=ia2F@m~R~J+i#5jq)UG9i0?czU3((7?AWKS(cdO6o48vg4hf^`92i3!%u%k7 zG)0+4(z$euf!*fZHnq6@sBIH>Oz&zdGtu-aYz?={0V1hF_wzc-iL?VpPF585Fhs#)4j); zUO$nVQZU&`cEjYogZp5Q_}icB0&~6=$Xm_V9JK!f5!-)}G3RnHfk;1m#CI`rjj=y* zl{gTxu*v#63gYRZWIF+slhKK3S7rY*j&PL2%92l_(_qrc zK>r~%C9!oM<2zT=n+a}%=e2(`ccQLAYhmiTAz+7UEzFVHnUM5iGM32t7}qoSD*xEc zg(Bh9KWzT8WO7ou%xp-3wu4h(yCq~P7k@WSWRDg(4)e8Y{vyV=D9lAUC$AV4!X;Vo z_@yCkEwWR%cUvs7mHxR*y~?kC)wXT$%!Q}W&%}j*O7di@+GDo%MzbNCxnD)i+JiBg%6w4n`B%f|7QQ9^c}K`Rp^8Em?8CS0$3q^(Lq3+8vSf0rG4&wX zEGX+Z#(U(*+NTW<#F#pDqT`!MHzXH&C+wKW0Fe0aNvs=VQff}TO88Iay?@h}c@y!i z=D4S9YEf!iLgL{tb=fCNU+*OCt)J&U8pnpstWkYk`q=Hn?ILca#5Jep$Q-hVu)~Dy zlQ7!-2ANxVWsKb+>xr`2Up6iyoE* z!sIWSJgSeWv0BzB{_LO0VaYkL&VqLs-ds~xJD5_^AGvmKXTDyX_*U?kga;MdFBADZ z$IzX`iRzckV}jR30{V5O_9Y~}CX4-}HLT|R8X~%pSIKh_H**}&<9fdyrJVBY$pAbn z{pIAkVq21$&lYk+K`wp*gXyBlqKA|dY_=(e++Qx?sfB0f>AZ8#u~BweuwcS0O~!a6Je}|yhNnM- zXP3c))e4V1Gt-?DJ+FRvZk`i8uaDswh9})$W-frmHA;E78kD@W&oMro!Mooa8)X)r zMt_;54?5r}K9gq*OyBF3xyuf@9x~VXi|Fr3t5IXf5P=06y6Yf)!&$u7?$1N)wB4VF zM))px-j2a@5T5F@gYBI@Vb#a%H$(7Lp2J=|{=9@IP#-7Z87Jbe;d$O~v)FmC%?6Jp zc(TYB2gW?J3|W=ujrjf{`GwiP8s7uiB>W-IGri=Oftt;7sBy<%4kkP@UNGwVM<4puT+(euMlUjc zZOSm`8ESm?&%M4A+G$q7dpGIwE@bbs2s$2RQ)P+wrdBUWZE&WxE~Oimq?Rp@pl1OsJt}2n+fZRq?uCh(Tt}d}`Q}}kk z*9)KM#;_}EUIUwPb41z2-S7>-hx<~$JpT6*_BLS`n>x8c{I|K56lf2+SkiI$q0viU zV%Jl+X`1r?wD&boa#hEfZ$>}J7Ki~skbe>yM+VuTVI*WEFm_t#2Z05&kqF3OdRj9* zKQPnXneG9}k#S?mL=M=D4IIlvF_T3}04rwJc48$TbPi$`>Ym7UX z*w}`!UsZke`u3ad=&W>!p4zkVN_Z*$9bjCvQpu0U1^vZNWyA=qAEyMQfI#MXKC1KS7efOMpd zwxsoum>XP#)%-dA$F|H>*KPVC;(5q>`S;MC=FXO<&nUEhS%!%YnFzJ$iZ?Eta}`Xu z=8{>%AQ}Ii1kqblh@THK#^9OJ`w<mgV*h5j1Y0)up2@GfAI{-9te z_wBNV(|C5@iOEOf;r+&imEcPAIfC@*&tu;UNsmKp`dkWYj=pR?;?|0pA|*Z4kajK6u`^d#)^j!i_h| z8bmyo2b8(odhfcENrLCfNc)2Wsr9PuuZ^vac;g2ulD&l9%b05!{(4f;N1n@a?Fc% zbF0kLWbV*|5o2RSF1qdm|KT7%!43j@92l0r^$@HE?9l*Q1?&j08nCUxrEO5>G+2CS zJZ0CVQ}!vsCgnNy?WiB5%L?G>{5hsIbA8!B@?8VoxBfx1jWz>Y0_+S)kGLm%y}(ul znDjFhU>RUX$k+Kk%o8yhhT72t?w#}nyvN#s3ym`uA2)yP?m2U=>5wV5#&Xg15cr;d z2=DWDI-kZPnXfECbY@7}xKO%|?}4}Hi`c73f(FWUhdgKcq>P#PzLm!Pi)Eel5}AWd zz5(HV_Ueev!F2qGhok8~#Xg7-XLgcj3)W^bxOW%sc^yfa`hiUVyFBLiaV26eokVPP zJNOQPFZ8=fU|$BdTGC!^HkEh8XA;=sz+`_v?Ypm9)Ftin8DMLG9V2OZ+z5wS0%gCX;ZcrL}*5?Ss@`urC8E0m}#2 zCluxEfR`|}3hwcxJ<+XHOla$Gdty=%1=h3Clg=G-J*J^UlF;<0w&oaawi^xVAb z7S-oVTC# zo-LpeH$QV1{SHCa?a0H|AOw?d7WO8v8DO#}tmq|&@Vx|VBEY0<3*L$G1~4>@aD0O{ z(HP&rTu~4B4uJ0(;ZqyvI0|zotjWS09`hxiYr*>zc;#FUp(S8PfO)?qdhG!=18lD_ zbq+5QKKZ{N_|-4vn!ug{whLHO z`GM^PcGU92xhBB(y33Dr_aj|X`GFk()>M9A@-4{O&E-cMx~jSS;5!7qrt$-O6j)RF zfyp-|A@&rohk-SfAK2r-j#~b1#82?O?(!qub4b@zeqi4N=H=J%53o8gOxd;mcXwW` z>qnAq0{33Ry|By&fJHAhm+ug;SHKsR?=Y|h;5%CRUIJei_+D@M&O(JPL%OhhX<$o% zdHLktR$x8A7T{bC!G?gH1uWEe7qB(Jj;8Mc@TI}`di8w<>EyQ`LVfGNwgLj8FGfGtDat_L;=Ovj$uhv>6=tiA4@n{9N7 z>IWt8emO|rh4g!X)qsUM?gREPu&_-Z1a>Ha)hI9Yt0R-DQuE74yg zo#-xy&`rRe0p@-sec!dfo&$!k6UKSOxhtFE+#c}N!FPi2;ShQIfV~1Nw8=qWF9Qp0 zQUmr9u%_|@i+*(UcJ&JQ7J%<{m;W3*w?Mk4@&kJ-FfYH{lW+c(0h@<&IRumM|CR#N z@e{E2e0#u`D1(m2@Qw)An>Nl`Z+aQ&_9LB_ zLGnHp0`~#)G62%&eoKK*0{@NxI)>44ue_)D2gm|LK0Ghd2{k6YI2D;!Bh9t=t`W;E zdI&ZGYzMH##AH5D1-1v+F*4sp+u?iwZRb{b?^xge?mV2B4`3#G64)O`TG^ZOe999d z{mT*HRp2TU`r^BVSEYjypRD6v1>aSU3$vC5&vxv4mTilc0D%6Racc<%Y=^-o-^7Ss zcgS<)Ps;nToHI8+1Dey@dY&XpqWTs!l<-%(hA;C1Y)SXZ3ud5TEM_j|x+fXn<;4#8dq_B=3s4uNB=`#O?-PG@6r9B<&6Hag`I!j^2Wsr zW!+#I8tb(S@%`W&%j0%=r$fHO>FL}amuK_B&Mmk=6j(1WKH&Mm5~z%VUbc{H-#EW~ zp*&bvf^Us*fv2)oAA2GHmE*8~9%ck#|z#aX4@2q>?J?8P1I|*W1ZJu(?#Sc`YEra>;8Y~Sit&8>eSO~NSZW5+Dlp@ zt&&cVPLfWM)<~yGXGrU$Q7+P>i!?>rLz*VdkoJ<6NUNk1q?4pmq&3oM(ize^X|$dB zlcq?qDY_nM(hO-YX^FH-Izc)~Iz?I|ohF?jt&>K5%%3zx+C!Qq&5-tzmPo6l6Qq-* zQ=~Q0Y0??eI%(9;{7F-!J)~*U3~4WEiL^>OK{`n~MOq`BCY>RzlSX;wPnsg_Ax)EJ zNP9`KQN12j(h1T@(kap!=``sKX`M70VE&{j(jL+@X@<0yv_x7Zogkegog%G~PLs}% z)=8s5=1-a;?IBH*W=MNUOQcoO3DQZ@DbgD0H0ca!oirL^{-i0=9?~>vhP0QoL|P@C zAe|(gBCU~5lg^OVNuy!rPnsg_Ax)EJNP9_3q*c-h(n-=O(i-VB=?rO|G`gPolcq>} zNYkVl(q7UMX_a(>bdq$6v_?8jIzw6~jc#E6q$$!K(llv?w3oC*S|yzzog|$it&vWX z&XCqgqY>s$nj-BXO_OFwdr3>ARnkrK=cu65x2?r$V-(#|#Y^fJty_0~YT1?B@mmt( zsTC`_S9bR-Uvb{JUaxou5&iU_Qxh-HzdkhaE?U9hk3{ZyQHL^jNWcDzYkBwBfJ;t0 z=i^VumX4&vj{WkOT@~)Qu1n5kl%T2S&OPSmIFC9`=y?00#S7=0`j%6#JmrLrE-2I4 z-7#m;`3v8@@Vtd*yy=9F6OTzR`Oup>7DLdg$ZuS{xbu{+TnOg5OQVGg-@9sakp&K6G}R+If7Z6tF}8%<;o49mGMIUJ%f4t>dkN;E^}jjL+{zK=G^7yu1YN*^naJH zOfC2S1APO_u@h>=c9?$8!1%~WYWaZf_KVF~myZl@AMAsqfkJV)Zl>zY_${2}10~6% zwS-)GXQ6LstXL?HmzR$hZo)p!%XP2oGE$hNz7gyh>+-PEY&2dd4-Xcwi!Annjm}mb z&PMLbSFBh*c;Uv2)@S_69FQC+UiREo(6lhVWBFiV{A|-X>h3<<{>i4khMkRldk2OG z&mKN!&ADgyja9_LVqk3Wdlc?Snu6549$Mq`w{gHd7q$HS_1R zU9bp{e5n>EI>sx%U1KoO>PgYu=wDy+Yp0TTCpLTS;kaM-Vge@5yZkE@?}}0g3J{jtHyrNO`yP#sv zh}G!xqOTA?jyT0*ZPfUWPvGAoenJ9&f%xJCE)^^Jo}9oJLtnu&e+C8|`h6kj>A+k0 z!`bBTy4wo+IVvi9TSa@K*J58|Bnx!!8_8P>z?k>pn2xcoT82uO8wq z{{ZE@m-@Vs{ExKB_b~aBapoV|@c&Qpmnfg&BiVmL>@b_~3d*3H~3n$=Bu7$nSpZawZdUI_I@6@0);Y z9R8uzh(3!%{agTC;!le80it~TKW9b%Kk;luitD; zHS*p{yz3JN>?M8|@#!xZpnJC9^Z}Pc#_P!c2=US%7@+&e;PeUN(H9NZy-jfXH1Vnb zYWVw%MBgW#{+I!m5dS&xo(B#19PutpUB%9s-!kAC;%^~dmw^qAl}4f!#HT)DfUkG| z3i0$G8Ls<|;PidOC-xYAjS;j@%Xi`y1AJUGO1$fC!#_j*4-v0Yj*pY?B0fobt|$NJ zi1+L^;Qho8Dt>(A&ld~F_{9Cj|Co{JIpWi_ z+iApqO1yrX0dFHd{}{7RZvtOTyz8Jby8L$%kLV8vC}%bC^dB1V0pjb4XFhJgTH;$A zzuf?K`%#W~=|01Ck1U)PiBGY-EX%M&-{il=Ey(j_(u(J{##rQ{b4EjKSjKk`~~9o0+)W! z*UJd&o`*U$CF2i_e>h2?(>J!Cy7se#qd5O(bL3T z5tsig@d?IxkE{PiyuQZ-yo++?VS*<0p8jnEbRR>UoRYr z(R1eG24Bs)PY^GC-hg|Ff17xwYQQHv^a~`Udey*4KN< z|6SlGLeAuLhaw(VJJ1jWpZ*)e-%0+HiBEpmaPbT|-a$M?{7nW&Y2u}4jei~St6ctb zhQFEknBu$$$S2z1pKF6ZMEMhcZSqg2tS5xLgB{{1#` zWb^+Nc|!6QIGFmw(Rlyf!tGe?Zy?KVohi~iHaL?0!d zX1y#R{(Hph^dEg^7xx}xQY3HrI``xD|l@}mnl9G92arMy+@hh& zaSQ9^e8nq|mgXmqvWR3aePlYf%qI`{Kih)=Ly&SaYVh}YPzo&S&9$p0Go zds0>)hMVa7ZTMd!f7i3dzlHg}8HN%&Ow%!aKDEm6zcqfhgY3PDP&4bukv~IzkLSCH zr&%s9@4e2?c|h;#49a?JVSqQd+s5gBJTE)-***#CaAxUm;O}o zKoOo!JAcA%&l8{dn%QBVCxt;oewyR4)x=LAUT40p=Q|u{|K<2v;;Cs9pnLD)^!>n7 zlo^s<^7pcRAx!CUv*IDAoqv-2nM6B%ka$lb4nIOXy4%X@@$Jbr^1nm=dctn<0=?L$ z#yETpRXb7f*?KM|fAyo5?-^uSMLeQCy)*b{Hhy%XpikoL$5x7`MG2pK$sA zVELX;{s)Lp%{Tl-;*Sw8QI6~L55zMZ=X-qkSK<>tHaVVe*IUd!)3kq?^3NdN#d#2t z>#>@6L_hcPZXrJTgz;TO{z2j?j)&dhk}N zcn{kP+(VCNiFYN&+jHM)b|}#fz2rZUcqTCpmpwhT-LbxoF|$VN6=&jrzbNu&AIlU+yk$LR$s+rGiauSZntb=4i;362XN(N5(RIYr94ENneuQ{0 zyS3|5u53{m}T=m|D?4yZna@zrjfKvdgD^ynYw|l9j7BF|J-t zy!KNQtlum`)`i3;=(iqkKR`TvhVidrz9YmljL*2#qpCPB0&*w$OKjI(M_(j9k?`BU zCq7Akb3gnM@!p@C@DDMMlTWqs_D~;hM`sf+J!^~?(4Oyie#&1?IeFq;9Jjll-%7m7 ze#G&EE}!#O@9!Qap81&x_IbfS5|4gtIEN`wM~eD?*YJ-Ui53%2bDZPx3_mizIC()0Wh?f%aZLi`K8j#;=gMXn7 z{#YCQX~j`qP6)omJYFQ;n^lfQ@h`}ny=yqfTne{?*ueD7htuMv;_mjNCp-~7vBhuO!OilZO- zjLG>RSvETUONL)Zd@J!P$L-#Z28gE__uPN>IG*sIdx`gu-~IMah-cW3?`6JE5T8iI z`G0ab?7v*jOO7Y%{gkEV57UhQ`r9MOL-y|!KdG|)`ndYN#Ou`4>v0?LUdBDIuae7Q z9J_}4-%Na(?aTXxyNP!t^!y6(I_=q9CQ2sRBoPM%r@X`Dr0G|mV7_aJr#Q~>c(R4~e65NG3f<>K*=**D4O_Qv%Wk~#vUOW8zWlOm zw)&ij`=z*grmk+t78aoX-yAVV`nkY`m``U&^&odIi&D^~1UD;qq9n zyQd}Rc`eb_rdZjM_uQ7~nwDs59agpEU9qAiyt*a2qNi1f&7v(0v@+_?Zyz7bR))s% zxqjFdm6FwO5winhxzRkTwl7EfhMd`ap)y7{X#Q%j`7=%2D_7vJklddi%MT8hEBUc( zWi;D|?=|yf6r#VF9ULic&y8gJE5)&LHaET_>MM?x#1;CrwpwtAR}70c=60GUk!jmM zK03M+LW3(f$#A8KwXfKpH;+Io>WhjuLYn}}=KA}yaIxHIIl^b4gE)Z|a!?=4H%c>= z6zY1}0!$=rsCxx;mhUKWH(wYWE{L1vcc82VoTxTJ#|wQRR$QEjj9j5lcqIU2NAsh_ zv7M^00hMA_@{)AfzTC)2b~_pazCl$Da8pywzH4$Ibu2H2FT<{qhkk1+bS#Iq8dY|d z1idjgB4^owb5=Iaks;E?*TccRTw!O^t#en!Yy);aE@I7DhKgmhab64;28vA=K_wXb z7`~+*hSNc(@jRkMlq;3;X#RSQN-f4Vx@ovFloh?Bo5qGKQk?_NEEcl_4W?{%;}vT! z-H_dI+4^i&;?__eVG9-&?=FsIQO|t>4-Ajwvno-pMstPSV7}j&#f@SXwvs}BZp^}< z1jfO!;!VP#Q4)2VAFCANYm068pcfzVW>IyBWZPG)MCcw8-4V|%+wAt86?hcD(ik`g zV_EShg=pY}|5&9IvSYczpk}GQ+&6AH(h{y~lhwVZ223f* zqsjL?(OAAb{GmK5NxumfHZ5a*2g0fHA;x80akKG?U&Y})~7@$>oo>UWvHi>J=Ap@os0=Aep6f2fYCt*(D~4BG)n z_BfNy3r>Y*R4nxs5wrS-F{FaY8r7$M3BBW%S{NTiyw|4RX!+h!y?$*RrIjl3RF6u2 zWW@6ooucgeZI`XR^x|~_YFj7|!J^t?G*_>Rrm!-O2re47Z0oTJDoSdl;r0~R zHR>+_GV7 zcI(;;H*e6MM6;9fW-&eu^)ru>rr^=~rXh4yxTaSDkB`fkg(VatC?m(G*({n?K}v0- z^d>b2`g0ZCbEFBiF{6&{U3;s8@dEY>X_!Jvju24jEA4DbFT*Pz7RLj6t0s)$gIdyI zu-37T!%#Ggk4@xgsYzt^;;omWDqR{HolFk4=CGAZFmyzWC>7B?t4~Q?>nJ!L{X#Or zeWgjTmerD})#OiD16qGFpEWS32vm!eDX5kf?u6M)sr3Ycfk(&Qv!ssXljbn$poAk2IihJZ1W#K!?kZPOt&RdHYdBd}1Y4Wp%6 zG0cHado^UG`@$@~JlmuuugNoQ*5eIQq+u|R@QskD#S9uZ8*S)F2%WP;MWydI z+vtoR*H{M0s$Cjp3aSUT3d9F(C~hvXH8s8DJZr|+H(J`!H#BG&qw&RU+ggW~D0h>^ zT4;o(VG9qeMwwKJvs-$t_QqwyUM#z>Zpg^yhWyTGps!FFX|gkh7=1TL6-Z2t@2psD z^36viySTa~Y_Yn1Ua1PS@Y+1QFAf>%zE<3Mz%#X zj8jS;EmYc$rGY{aJ=9mx8;y_1+{xiBVxA z`9f3jS$@zzRxF_>#N^XNH|kA;1UijM4z+?F13OU-H!5sZlO{L7U!zg1i~Yx$$ufh) z?GbHgS`^Dq^D3jXM^9fUmO@R{YT8<&lC9`uN5?B@JNnv1J`s1QUSy0x}=lNm1P#)Z0xw=S>57MvuaY* zx7Z6V1iYi0Qr1^YjiAHO^_^M5OPJAQM{?WYS(q3~n4~Qc1auml@Jq9TD(O9^4vbqE zSZgpgl?;D<%n)ehfp?Z-QtG%7pjXp*N>kCz&3qIgf*Rc)8xp0rYQ?CTxS?yh(r~E} zoTOqx7m7O)i7pn_%h<&Y!w0Mi#af}u3gQ80Kt7&nU9sb_+XtP18%4|9GEkUh;sP)f zt3|?s&5mGJyO`F)r(^*>s9u$Z`idiFlLchi(3Y)a(9+yu!t4`u8{TR}H_akKA<$FB z){7g1g$}kLcJfjgi9S8<`{RJ@cAM=Pvkk$5%P>p}E`Q+rV=Xlb>IMe#{9+@hfUu-= zTp6^wh2xBxluXp4w0{rw7_Mlr&t?Zliv=k>qDR)mC5$Ykv#woC6dUxMbBvqXv}}rI zYwu;7_;pf>Y5It)Ml^(afn-70U8YeCyBkg@w3>r~_X_qf7D?cR93Umi)V3`Lvn*HJ zhiY1<^cxdbW`mT)Tc1x^Z=%j^9wl=Hj!-%5_wlTa*;uo)o3=OtGiX}w#f)=!L|O}$ zH)CIHOds-v8x6L3L@>4rgIQRqG7KIX6_4o859BbAu=GKf1W~f48f2%_Dh@bgv881k z2_%SLHjHkgGHn!Eg1APT+&mo9CVfD|zc@5oTIes{g4ihpmem!X<_;f|IgB4;2aLmn z4pZ?5OAyQ=*~VJMeY%~NEX>y?Wa`j5Aybj`B`L+Ic85(`SaNkOZJnkDD-|oy7)zqQ z@JK2e(3Qic#44Z4him{$MJKVn`sQDn*B{o7F;2J`uL~KX1 zY8mUW&Eqku7v~)}sm6#NP%BqYMASB1GVE=qOjAK*a5zg{;gAP;vZSof_2T|ZqF*oz z3aY6&(gb}gYdTRDnX2g8&8+1uHBC6)X$nsE1X5Q|y-(#FHR}r&jVg(KUjswSVw&rl zZ(t#9c05pZ)Vo!PUIukw!DO}x8{I@h9k)c#*)dkciiWNkM`x*HOEWrNNKV^Ix{TWt zh5Yf^EYr%^R$52Qjj^p*#RvVZCb`;rY@*8!H0WMpy_L(Ff2laqn0QKDmuJFpjskd3 zU{Av0TTsTViy$ITozZ(s`(Und?-H~#xZh~;#^wQjTwkivY~NA1N$=T}C;o4)EsJ?0 ztr<`(Om1b?AD1BR#eBG;>q4%%ib*WINIPwAIe~Rs_=`GIW9=<;mmneL)Vb}$BeE)? z=J3`OM;R6_HiNOTlM8LuaNd|VsOq4@5NGuPmbFnCCug;xst$pAK@IAyAO?qdi)|8P z7tNbyF`eDG4Vwh|H5Fc0JruDR8)nraTv>z5_4VaTjrI~(aG$2jay5d#0%x#j#5(u%#im04-C{MpasG6UL;t%axiA2P9}oCA`8C zM6ciW8HWcayI`MyYgL(S9s z@1{BBHrCjK*$(G(Is{mLlg+RFciEgyFkMoA!V#LE%J%gBJ8(|@dri+jS^is?{z3}$ z-<5Ofze6YKYj>QM{KF+C@b4O&ekzfE zV#FAoIv`;2kGPAUwFaNJ0Do|@N1{f0u|qt7$Ar5L%M+J zYpgzLe{y&_*XwRz!s6*4f6elJ{D(%@NTT5I^iJ!z7p9LTF#v9g86iS(?6>(S>CbJGzMtv)j*|XDrcajhF{bzU z?P6SirED&O;prS6ZIk{^rk6e=Jd)|<+HwlH z@H^T}|MDusmxBBN|J{GR|CZr_`>t`?-}}1_{Vu-C3Xf#|@{L!k^tWv@l%G4tB9iI< EHz+37-2eap -- 2.25.1