From: Brendan Hansen Date: Tue, 26 Jul 2022 23:10:44 +0000 (-0500) Subject: outputting debug info into WASM binary X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=7270e3a89eb5c697355e24fe083c4f2f3f16d0ee;p=onyx.git outputting debug info into WASM binary --- diff --git a/include/bh.h b/include/bh.h index 2d941eba..536ab38b 100644 --- a/include/bh.h +++ b/include/bh.h @@ -509,6 +509,7 @@ typedef struct bh_buffer { 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); @@ -2153,6 +2154,10 @@ void bh_buffer_free(bh_buffer* buffer) { buffer->capacity = 0; } +void bh_buffer_clear(bh_buffer* buffer) { + buffer->length = 0; +} + void bh_buffer_grow(bh_buffer* buffer, i32 length) { if (buffer == NULL) return; diff --git a/include/wasm_emit.h b/include/wasm_emit.h index d02bdea4..d2c60391 100644 --- a/include/wasm_emit.h +++ b/include/wasm_emit.h @@ -791,31 +791,41 @@ b32 onyx_run_wasm(bh_buffer code_buffer, int argc, char *argv[]); #ifdef ENABLE_DEBUG_INFO -typedef struct DebugLocation { - u32 file_id; - u32 line; - u32 repeat; -} DebugLocation; +typedef enum DebugOpType { + DOT_END = 0b00000000, + DOT_SET = 0b00000001, + DOT_PUSHF = 0b00000010, + DOT_POPF = 0b00000011, + DOT_SYM = 0b00000100, + + DOT_INC = 0b01000000, + DOT_DEC = 0b10000000, + DOT_REP = 0b11000000, +} DebugOpType; typedef struct DebugFuncContext { u32 func_index; - bh_arr_each(DebugLocation) locations; + u32 file_id; + u32 line; + u32 op_offset; + u32 stack_ptr_idx; + + u32 name_length; + char *name; } DebugFuncContext; typedef struct DebugContext { bh_allocator allocator; - // file_names[file_ids["file"]] == "file" - // file_ids[file_name[123]] == 123 - Table(u32) file_ids; - bh_arr_each(char *) file_names; + Table(u32) file_ids; + u32 next_file_id; - bh_arr_each(DebugFuncContext *) + bh_arr(DebugFuncContext) funcs; + bh_buffer op_buffer; // Used during building the debug info OnyxToken *last_token; - DebugFucnContext *current_func; - + b32 last_op_was_rep : 1; } DebugContext; #endif diff --git a/lib/linux_x86_64/lib/libovmwasm.so b/lib/linux_x86_64/lib/libovmwasm.so index 704e28d0..1a53a55a 100755 Binary files a/lib/linux_x86_64/lib/libovmwasm.so and b/lib/linux_x86_64/lib/libovmwasm.so differ diff --git a/lib/linux_x86_64/libovmwasm.so b/lib/linux_x86_64/libovmwasm.so deleted file mode 100755 index d8250e79..00000000 Binary files a/lib/linux_x86_64/libovmwasm.so and /dev/null differ diff --git a/src/wasm_emit.c b/src/wasm_emit.c index 36a872fe..a6482f74 100644 --- a/src/wasm_emit.c +++ b/src/wasm_emit.c @@ -219,26 +219,146 @@ static inline b32 should_emit_function(AstFunction* fd) { #ifdef ENABLE_DEBUG_INFO +static u32 debug_get_file_id(OnyxWasmModule *mod, const char *name) { + assert(mod && mod->debug_context); + + i32 index = shgeti(mod->debug_context->file_ids, name); + if (index == -1) { + u32 id = mod->debug_context->next_file_id++; + shput(mod->debug_context->file_ids, name, id); + + return id; + } + + return mod->debug_context->file_ids[index].value; +} + static void debug_set_position(OnyxWasmModule *mod, OnyxToken *token) { + i32 file_id = debug_get_file_id(mod, token->pos.filename); + + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_SET); + mod->debug_context->last_op_was_rep = 0; + + u32 leb_len=0; + u8 *bytes = uint_to_uleb128(file_id, &leb_len); + bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); + + bytes = uint_to_uleb128(token->pos.line, &leb_len); + bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); + mod->debug_context->last_token = token; } // Called for every instruction being emitted +// This has to emit either: +// - INC +// - DEC +// - REP +// - SET, REP 0 static void debug_emit_instruction(OnyxWasmModule *mod, OnyxToken *token) { DebugContext *ctx = mod->debug_context; - assert(ctx); + assert(ctx && ctx->last_token); + + // Sanity check + if (ctx->last_op_was_rep) { + assert((ctx->op_buffer.data[ctx->op_buffer.length - 1] & DOT_REP) == DOT_REP); + } + i32 file_id, old_file_id; + + b32 repeat_previous = 0; + if (!token || !token->pos.filename) { + repeat_previous = 1; + + } else { + file_id = debug_get_file_id(mod, token->pos.filename); + old_file_id = debug_get_file_id(mod, ctx->last_token->pos.filename); + if (old_file_id == file_id && token->pos.line == ctx->last_token->pos.line) { + repeat_previous = 1; + } + } + + if (repeat_previous) { + // Output / increment REP instruction + if (ctx->last_op_was_rep) { + u8 rep_count = ctx->op_buffer.data[ctx->op_buffer.length - 1] & 0b00111111; + if (rep_count != 63) { + rep_count += 1; + ctx->op_buffer.data[ctx->op_buffer.length - 1] = DOT_REP | rep_count; + return; + } + } - DebugFuncContext *func_ctx = ctx->current_func; - assert(func_ctx); + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP); + ctx->last_op_was_rep = 1; + return; + } - if () + // At this point, token is definitely non-null. + ctx->last_op_was_rep = 0; - if (shgeti(ctx->file_ids, ctx->last_token->pos.filename) == -1) { - // File name has not been seen before, allocate a slot for it. + if (old_file_id == file_id) { + // We see if we can INC/DEC to get to the line number + if (ctx->last_token->pos.line < token->pos.line) { + u32 diff = token->pos.line - ctx->last_token->pos.line; + if (diff <= 64) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_INC | (diff - 1)); + goto done; + } + } + if (ctx->last_token->pos.line > token->pos.line) { + u32 diff = ctx->last_token->pos.line - token->pos.line; + if (diff <= 64) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_DEC | (diff - 1)); + goto done; + } + } } + + // Otherwise, we need to output a SET, followed by a REP 0, + // which is what set_position does. + debug_set_position(mod, token); + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP); + ctx->last_op_was_rep = 1; + + done: + ctx->last_token = token; } +static void debug_begin_function(OnyxWasmModule *mod, u32 func_idx, OnyxToken *token, char *name) { + u32 file_id = debug_get_file_id(mod, token->pos.filename); + u32 line = token->pos.line; + + assert(mod->debug_context); + + DebugFuncContext func; + func.func_index = func_idx; + func.file_id = file_id; + func.line = line; + func.op_offset = mod->debug_context->op_buffer.length; + func.stack_ptr_idx = 0; + func.name_length = strlen(name); + func.name = name; + bh_arr_push(mod->debug_context->funcs, func); + + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_PUSHF); + debug_set_position(mod, token); +} + +static void debug_end_function(OnyxWasmModule *mod) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_POPF); + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_END); + mod->debug_context->last_op_was_rep = 0; +} + +#else + +#define debug_get_file_id(mod, name) (void)0 +#define debug_set_position(mod, token) (void)0 +#define debug_emit_instruction(mod, token) (void)0 +#define debug_begin_function(mod, idx, token, name) (void)0 +#define debug_end_function(mod) (void)0 + #endif @@ -487,7 +607,7 @@ EMIT_FUNC(statement, AstNode* stmt) { bh_arr(WasmInstruction) code = *pcode; #ifdef ENABLE_DEBUG_INFO - debug_set_position(stmt->token); + debug_set_position(mod, stmt->token); #endif switch (stmt->kind) { @@ -889,19 +1009,19 @@ EMIT_FUNC(while, AstIfWhile* while_node) { if (!while_node->bottom_test) { emit_expression(mod, &code, while_node->cond); - WI(while_cond->token, WI_I32_EQZ); - WID(while_cond->token, WI_COND_JUMP, 0x01); + WI(NULL, WI_I32_EQZ); + WID(NULL, WI_COND_JUMP, 0x01); } emit_block(mod, &code, while_node->true_stmt, 0); if (while_node->bottom_test) { emit_expression(mod, &code, while_node->cond); - WID(while_node->cond, WI_COND_JUMP, 0x00); + WID(while_node->cond->token, WI_COND_JUMP, 0x00); } else { if (bh_arr_last(code).type != WI_JUMP) - WID(while_node->cond, WI_JUMP, 0x00); + WID(while_node->cond->token, WI_JUMP, 0x00); } emit_leave_structured_block(mod, &code); @@ -1452,14 +1572,14 @@ EMIT_FUNC(remove_directive, AstDirectiveRemove* remove) { ForRemoveInfo remove_info = bh_arr_last(mod->for_remove_info); - WIL(remote->token, WI_LOCAL_GET, remove_info.iterator_remove_func); - WIL(remote->token, WI_I32_CONST, mod->null_proc_func_idx); - WI(remote->token, WI_I32_NE); - WID(remote->token, WI_IF_START, 0x40); - WIL(remote->token, WI_LOCAL_GET, remove_info.iterator_data_ptr); - WIL(remote->token, WI_LOCAL_GET, remove_info.iterator_remove_func); - WIL(remote->token, WI_CALL_INDIRECT, remove_info.remove_func_type_idx); - WI(remote->token, WI_IF_END); + WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func); + WIL(remove->token, WI_I32_CONST, mod->null_proc_func_idx); + WI(remove->token, WI_I32_NE); + WID(remove->token, WI_IF_START, 0x40); + WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_data_ptr); + WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func); + WIL(remove->token, WI_CALL_INDIRECT, remove_info.remove_func_type_idx); + WI(remove->token, WI_IF_END); *pcode = code; } @@ -3459,7 +3579,6 @@ static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func) { } } - static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { if (!should_emit_function(fd)) return; @@ -3474,19 +3593,31 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) fd); mod->current_func_idx = func_idx; + debug_begin_function(mod, func_idx, fd->token, get_function_name(fd)); + if (fd == builtin_initialize_data_segments && context.options->use_post_mvp_features) { emit_initialize_data_segments_body(mod, &wasm_func.code); + + debug_emit_instruction(mod, NULL); bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); + bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); mod->current_func_idx = -1; + + debug_end_function(mod); return; } if (fd == builtin_run_init_procedures) { emit_run_init_procedures(mod, &wasm_func.code); + + debug_emit_instruction(mod, NULL); bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); + bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); mod->current_func_idx = -1; + + debug_end_function(mod); return; } @@ -3525,6 +3656,8 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { bh_arr_insert_end(wasm_func.code, 5); fori (i, 0, 5) wasm_func.code[i] = (WasmInstruction) { WI_NOP, 0 }; + // TODO: Emit debug info for the above instructions + mod->stack_base_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); // Generate code @@ -3535,6 +3668,9 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { // Place all stack leaves in patch locations. These will (probably) all be // right before a "return" instruction. + debug_emit_instruction(mod, NULL); + debug_emit_instruction(mod, NULL); + u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_LOCAL_GET, { .l = mod->stack_base_idx } })); bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } })); @@ -3548,12 +3684,16 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { WasmFuncType* ft = mod->types[type_idx]; emit_zero_value(mod, &wasm_func.code, ft->return_type); + + debug_emit_instruction(mod, NULL); bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); bh_imap_clear(&mod->local_map); bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); mod->current_func_idx = -1; + + debug_end_function(mod); } static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) { @@ -4050,6 +4190,18 @@ OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) { bh_arr_new(global_heap_allocator, module.procedures_with_tags, 4); bh_arr_new(global_heap_allocator, module.data_patches, 4); +#ifdef ENABLE_DEBUG_INFO + module.debug_context = bh_alloc_item(context.ast_alloc, DebugContext); + module.debug_context->allocator = global_heap_allocator; + module.debug_context->next_file_id = 0; + module.debug_context->last_token = NULL; + + sh_new_arena(module.debug_context->file_ids); + bh_arr_new(global_heap_allocator, module.debug_context->funcs, 16); + + bh_buffer_init(&module.debug_context->op_buffer, global_heap_allocator, 1024); +#endif + return module; } diff --git a/src/wasm_intrinsics.h b/src/wasm_intrinsics.h index 59633ffc..49dae8c0 100644 --- a/src/wasm_intrinsics.h +++ b/src/wasm_intrinsics.h @@ -444,6 +444,7 @@ EMIT_FUNC_NO_ARGS(run_init_procedures) { bh_arr_each(AstFunction *, func, init_procedures) { i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) *func); + debug_emit_instruction(mod, NULL); bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx })); } diff --git a/src/wasm_output.h b/src/wasm_output.h index 99c19d92..c7b4e7dd 100644 --- a/src/wasm_output.h +++ b/src/wasm_output.h @@ -25,6 +25,20 @@ static const u8 ONYX_MAGIC_STRING[] = "ONYX"; static const u8 WASM_MAGIC_STRING[] = { 0x00, 0x61, 0x73, 0x6D }; static const u8 WASM_VERSION[] = { 0x01, 0x00, 0x00, 0x00 }; +static inline i32 output_unsigned_integer(u64 i, bh_buffer *buff) { + i32 leb_len; + u8* leb = uint_to_uleb128(i, &leb_len); + bh_buffer_append(buff, leb, leb_len); + return leb_len; +} + +static inline i32 output_custom_section_name(char *name, bh_buffer *buff) { + u32 len = strlen(name); + i32 len_len = output_unsigned_integer(len, buff); + bh_buffer_append(buff, name, len); + return len_len + len; +} + static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer* buff); static i32 output_vector(void** arr, i32 stride, i32 arrlen, vector_func elem, bh_buffer* vec_buff) { @@ -414,7 +428,6 @@ static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer i32 leb_len; u8* leb; - if (instr->type == WI_NOP) return; if (instr->type == WI_UNREACHABLE) assert(("EMITTING UNREACHABLE!!", 0)); if (instr->type & SIMD_INSTR_MASK) { @@ -677,38 +690,29 @@ static i32 output_onyx_libraries_section(OnyxWasmModule* module, bh_buffer* buff bh_buffer libs_buff; bh_buffer_init(&libs_buff, buff->allocator, 128); - const char *custom_name = "_onyx_libs"; - i32 leb_len; - u8* leb = uint_to_uleb128(strlen(custom_name), &leb_len); - bh_buffer_append(&libs_buff, leb, leb_len); - bh_buffer_append(&libs_buff, custom_name, strlen(custom_name)); + output_custom_section_name("_onyx_libs", &libs_buff); - leb = uint_to_uleb128((u64) bh_arr_length(module->library_paths), &leb_len); - bh_buffer_append(&libs_buff, leb, leb_len); + output_unsigned_integer(bh_arr_length(module->library_paths), &libs_buff); bh_arr_each(char *, lib, module->library_paths) { assert(*lib != NULL); u32 lib_len = strlen(*lib); - leb = uint_to_uleb128((u64) lib_len, &leb_len); - bh_buffer_append(&libs_buff, leb, leb_len); + output_unsigned_integer(lib_len, &libs_buff); bh_buffer_append(&libs_buff, *lib, lib_len); } - leb = uint_to_uleb128((u64) bh_arr_length(module->libraries), &leb_len); - bh_buffer_append(&libs_buff, leb, leb_len); + output_unsigned_integer(bh_arr_length(module->libraries), &libs_buff); bh_arr_each(char *, lib, module->libraries) { assert(*lib != NULL); u32 lib_len = strlen(*lib); - leb = uint_to_uleb128((u64) lib_len, &leb_len); - bh_buffer_append(&libs_buff, leb, leb_len); + output_unsigned_integer(lib_len, &libs_buff); bh_buffer_append(&libs_buff, *lib, lib_len); } - leb = uint_to_uleb128((u64) (libs_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); + output_unsigned_integer(libs_buff.length, buff); bh_buffer_concat(buff, libs_buff); bh_buffer_free(&libs_buff); @@ -724,11 +728,7 @@ static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* bu bh_buffer section_buff; bh_buffer_init(§ion_buff, buff->allocator, 128); - const char *custom_name = "_onyx_func_offsets"; - i32 leb_len; - u8* leb = uint_to_uleb128(strlen(custom_name), &leb_len); - bh_buffer_append(§ion_buff, leb, leb_len); - bh_buffer_append(§ion_buff, custom_name, strlen(custom_name)); + output_custom_section_name("_onyx_func_offsets", §ion_buff); i32 func_count = bh_arr_length(module->funcs) + module->foreign_function_count; @@ -753,8 +753,7 @@ static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* bu bh_buffer_concat(§ion_buff, name_buff); - leb = uint_to_uleb128((u64) (section_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); + output_unsigned_integer(section_buff.length, buff); bh_buffer_concat(buff, section_buff); bh_buffer_free(§ion_buff); @@ -762,6 +761,81 @@ static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* bu return buff->length - prev_len; } +#ifdef ENABLE_DEBUG_INFO +static i32 output_ovm_debug_sections(OnyxWasmModule* module, bh_buffer* buff) { + if (!module->debug_context) return 0; + + DebugContext *ctx = module->debug_context; + + bh_buffer section_buff; + bh_buffer_init(§ion_buff, buff->allocator, 128); + + { + // ovm_debug_files section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_files", §ion_buff); + + i32 file_count = shlenu(ctx->file_ids); + output_unsigned_integer(file_count, §ion_buff); + + fori (i, 0, file_count) { + Table(u32) entry = (void *) &ctx->file_ids[i]; + output_unsigned_integer(entry->value, §ion_buff); + output_name(entry->key, strlen(entry->key), §ion_buff); + } + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + { + // ovm_debug_funcs section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_funcs", §ion_buff); + + i32 func_count = bh_arr_length(ctx->funcs); + output_unsigned_integer(func_count, §ion_buff); + + fori (i, 0, func_count) { + DebugFuncContext *func = &ctx->funcs[i]; + output_unsigned_integer(func->func_index, §ion_buff); + output_unsigned_integer(func->file_id, §ion_buff); + output_unsigned_integer(func->line, §ion_buff); + output_name(func->name, func->name_length, §ion_buff); + output_unsigned_integer(1, §ion_buff); + output_unsigned_integer(func->op_offset, §ion_buff); + output_unsigned_integer(func->stack_ptr_idx, §ion_buff); + output_unsigned_integer(0, §ion_buff); + } + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + { + // ovm_debug_ops section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_ops", §ion_buff); + bh_buffer_concat(§ion_buff, ctx->op_buffer); + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + bh_buffer_free(§ion_buff); + return 0; +} +#endif + void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer) { bh_buffer_init(buffer, global_heap_allocator, 128); if (context.options->runtime == Runtime_Onyx) { @@ -771,6 +845,9 @@ void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer) } bh_buffer_append(buffer, WASM_VERSION, 4); +#ifdef ENABLE_DEBUG_INFO + output_ovm_debug_sections(module, buffer); +#endif output_typesection(module, buffer); output_importsection(module, buffer); output_funcsection(module, buffer);