From: Brendan Hansen Date: Fri, 9 Jun 2023 21:03:59 +0000 (-0500) Subject: added: user-level stack trace available with `--stack-trace` X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=50de7125c9cbb86e6b972877037afd5b4ea9cb08;p=onyx.git added: user-level stack trace available with `--stack-trace` --- diff --git a/compiler/include/astnodes.h b/compiler/include/astnodes.h index 7191b961..8dcca96b 100644 --- a/compiler/include/astnodes.h +++ b/compiler/include/astnodes.h @@ -1402,6 +1402,9 @@ struct AstFunction { AstCaptureBlock *captures; Scope *scope_to_lookup_captured_values; + // NOTE: Only set on when --stack-trace is passed. + AstLocal *stack_trace_local; + b32 is_exported : 1; b32 is_foreign : 1; b32 is_foreign_dyncall : 1; @@ -1816,6 +1819,7 @@ struct CompileOptions { bh_arr(DefinedVariable) defined_variables; b32 debug_enabled; + b32 stack_trace_enabled; i32 passthrough_argument_count; char** passthrough_argument_data; @@ -1903,6 +1907,7 @@ extern AstGlobal builtin_stack_top; extern AstGlobal builtin_tls_base; extern AstGlobal builtin_tls_size; extern AstGlobal builtin_closure_base; +extern AstGlobal builtin_stack_trace; extern AstType *builtin_string_type; extern AstType *builtin_cstring_type; extern AstType *builtin_range_type; @@ -1918,6 +1923,7 @@ extern AstType *builtin_any_type; extern AstType *builtin_code_type; extern AstType *builtin_link_options_type; extern AstType *builtin_package_id_type; +extern AstType *builtin_stack_trace_type; extern AstTyped *type_table_node; extern AstTyped *foreign_blocks_node; extern AstType *foreign_block_type; diff --git a/compiler/include/wasm_emit.h b/compiler/include/wasm_emit.h index 7cd54e0d..8f79b699 100644 --- a/compiler/include/wasm_emit.h +++ b/compiler/include/wasm_emit.h @@ -749,10 +749,12 @@ typedef struct OnyxWasmModule { i32 *heap_start_ptr; u64 stack_base_idx; u64 closure_base_idx; + u64 stack_trace_idx; CallingConvention curr_cc; i32 null_proc_func_idx; b32 has_stack_locals : 1; + b32 doing_linking : 1; #ifdef ENABLE_DEBUG_INFO struct DebugContext *debug_context; diff --git a/compiler/src/builtins.c b/compiler/src/builtins.c index d7bd9a99..0fa9e19f 100644 --- a/compiler/src/builtins.c +++ b/compiler/src/builtins.c @@ -41,11 +41,13 @@ static OnyxToken builtin_stack_top_token = { Token_Type_Symbol, 11, "__stack_t static OnyxToken builtin_tls_base_token = { Token_Type_Symbol, 10, "__tls_base ", { 0 } }; static OnyxToken builtin_tls_size_token = { Token_Type_Symbol, 10, "__tls_size ", { 0 } }; static OnyxToken builtin_closure_base_token = { Token_Type_Symbol, 14, "__closure_base ", { 0 } }; +static OnyxToken builtin_stack_trace_token = { Token_Type_Symbol, 0, " ", { 0 } }; AstGlobal builtin_heap_start = { Ast_Kind_Global, Ast_Flag_Const, &builtin_heap_start_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; AstGlobal builtin_stack_top = { Ast_Kind_Global, 0, &builtin_stack_top_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; AstGlobal builtin_tls_base = { Ast_Kind_Global, 0, &builtin_tls_base_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; AstGlobal builtin_tls_size = { Ast_Kind_Global, 0, &builtin_tls_size_token, NULL, NULL, (AstType *) &basic_type_u32, NULL }; AstGlobal builtin_closure_base = { Ast_Kind_Global, 0, &builtin_closure_base_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; +AstGlobal builtin_stack_trace = { Ast_Kind_Global, 0, &builtin_stack_trace_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; AstType *builtin_string_type; AstType *builtin_cstring_type; @@ -62,6 +64,7 @@ AstType *builtin_any_type; AstType *builtin_code_type; AstType *builtin_link_options_type; AstType *builtin_package_id_type; +AstType *builtin_stack_trace_type; AstTyped *type_table_node = NULL; AstTyped *foreign_blocks_node = NULL; @@ -569,6 +572,10 @@ void initalize_special_globals() { foreign_blocks_node = (AstTyped *) symbol_raw_resolve(p->scope, "foreign_blocks"); foreign_block_type = (AstType *) symbol_raw_resolve(p->scope, "foreign_block"); tagged_procedures_node = (AstTyped *) symbol_raw_resolve(p->scope, "tagged_procedures"); + + if (context.options->stack_trace_enabled) { + builtin_stack_trace_type = (AstType *) symbol_raw_resolve(p->scope, "Stack_Trace"); + } } } @@ -597,6 +604,14 @@ void introduce_build_options(bh_allocator a) { wait_notify_available->type_node = (AstType *) &basic_type_bool; symbol_builtin_introduce(p->scope, "Wait_Notify_Available", (AstNode *) wait_notify_available); + AstNumLit* debug_mode = make_int_literal(a, context.options->debug_enabled); + debug_mode->type_node = (AstType *) &basic_type_bool; + symbol_builtin_introduce(p->scope, "Debug_Mode_Enabled", (AstNode *) debug_mode); + + AstNumLit* stack_trace = make_int_literal(a, context.options->stack_trace_enabled); + stack_trace->type_node = (AstType *) &basic_type_bool; + symbol_builtin_introduce(p->scope, "Stack_Trace_Enabled", (AstNode *) stack_trace); + i32 os; #ifdef _BH_LINUX os = 1; @@ -641,5 +656,6 @@ void introduce_build_options(bh_allocator a) { foreign_info->type_node = (AstType *) &basic_type_bool; symbol_builtin_introduce(p->scope, "Generated_Foreign_Info", (AstNode *) foreign_info); } + } diff --git a/compiler/src/checker.c b/compiler/src/checker.c index a5fb1168..e8847a11 100644 --- a/compiler/src/checker.c +++ b/compiler/src/checker.c @@ -2778,6 +2778,10 @@ CheckStatus check_function(AstFunction* func) { status = check_capture_block(func->captures); } + if (status == Check_Success && func->stack_trace_local) { + status = check_expression((AstTyped **) &func->stack_trace_local); + } + if (status == Check_Success) { status = check_block(func->body); } diff --git a/compiler/src/onyx.c b/compiler/src/onyx.c index 21ac430d..5370ffd1 100644 --- a/compiler/src/onyx.c +++ b/compiler/src/onyx.c @@ -62,16 +62,17 @@ static const char *build_docstring = DOCSTRING_HEADER "\t--verbose, -V Verbose output.\n" "\t -VV Very verbose output.\n" "\t -VVV Very very verbose output (to be used by compiler developers).\n" - "\t--no-std Disable automatically including \"core/std\".\n" - "\t--wasm-mvp Use only WebAssembly MVP features.\n" "\t--multi-threaded Enables multi-threading for this compilation.\n" "\t Automatically enabled for \"onyx\" runtime.\n" "\t--doc Generates an O-DOC file, a.k.a an Onyx documentation file. Used by onyx-doc-gen.\n" "\t--tag Generates a C-Tag file.\n" "\t--syminfo Generates a symbol resolution information file. Used by onyx-lsp.\n" + "\t--stack-trace Enable dynamic stack trace.\n" + "\t--no-std Disable automatically including \"core/std\".\n" "\t--no-stale-code Disables use of `#allow_stale_code` directive\n" "\t--no-type-info Disables generating type information\n" - "\t--generate-foreign-info\n" + "\t--generate-foreign-info Generate information for foreign blocks. Rarely needed, so disabled by default.\n" + "\t--wasm-mvp Use only WebAssembly MVP features.\n" "\n" "Developer options:\n" "\t--no-colors Disables colors in the error message.\n" @@ -263,6 +264,10 @@ static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *arg } else if (!strcmp(argv[i], "--debug")) { options.debug_enabled = 1; + options.stack_trace_enabled = 1; + } + else if (!strcmp(argv[i], "--stack-trace")) { + options.stack_trace_enabled = 1; } else if (!strcmp(argv[i], "--")) { options.passthrough_argument_count = argc - i - 1; @@ -372,10 +377,11 @@ static void introduce_defined_variables() { } // HACK -static u32 special_global_entities_remaining = 3; +static u32 special_global_entities_remaining = 4; static Entity *runtime_info_types_entity; static Entity *runtime_info_foreign_entity; static Entity *runtime_info_proc_tags_entity; +static Entity *runtime_info_stack_trace_entity; static void context_init(CompileOptions* opts) { memset(&context, 0, sizeof context); @@ -384,7 +390,7 @@ static void context_init(CompileOptions* opts) { prepare_builtins(); // HACK - special_global_entities_remaining = 3; + special_global_entities_remaining = 4; context.options = opts; context.cycle_detected = 0; @@ -449,6 +455,12 @@ static void context_init(CompileOptions* opts) { .package = NULL, .include = create_load(context.ast_alloc, "core/runtime/info/proc_tags"), })); + runtime_info_stack_trace_entity = entity_heap_insert(&context.entities, ((Entity) { + .state = Entity_State_Parse, + .type = Entity_Type_Load_File, + .package = NULL, + .include = create_load(context.ast_alloc, "core/runtime/info/stack_trace"), + })); } builtin_heap_start.entity = NULL; @@ -462,6 +474,7 @@ static void context_init(CompileOptions* opts) { add_entities_for_node(NULL, (AstNode *) &builtin_tls_base, context.global_scope, NULL); add_entities_for_node(NULL, (AstNode *) &builtin_tls_size, context.global_scope, NULL); add_entities_for_node(NULL, (AstNode *) &builtin_closure_base, context.global_scope, NULL); + add_entities_for_node(NULL, (AstNode *) &builtin_stack_trace, context.global_scope, NULL); // NOTE: Add all files passed by command line to the queue bh_arr_each(const char *, filename, opts->files) { @@ -687,7 +700,8 @@ static b32 process_entity(Entity* ent) { // GROSS if (ent == runtime_info_types_entity || ent == runtime_info_proc_tags_entity - || ent == runtime_info_foreign_entity) { + || ent == runtime_info_foreign_entity + || ent == runtime_info_stack_trace_entity) { special_global_entities_remaining--; } diff --git a/compiler/src/symres.c b/compiler/src/symres.c index 385e7743..9535ca4f 100644 --- a/compiler/src/symres.c +++ b/compiler/src/symres.c @@ -1145,6 +1145,22 @@ SymresStatus symres_function_header(AstFunction* func) { SYMRES(type, &func->return_type); + if (context.options->stack_trace_enabled) { + OnyxToken *stack_trace_token = bh_alloc_item(context.ast_alloc, OnyxToken); + stack_trace_token->type = Token_Type_Symbol; + stack_trace_token->length = 13; + stack_trace_token->text = bh_strdup(context.ast_alloc, "__stack_trace "); + stack_trace_token->pos = func->token->pos; + + if (!func->stack_trace_local) { + assert(builtin_stack_trace_type); + func->stack_trace_local = make_local(context.ast_alloc, stack_trace_token, builtin_stack_trace_type); + func->stack_trace_local->flags |= Ast_Flag_Decl_Followed_By_Init; + } + + SYMRES(local, &func->stack_trace_local); + } + scope_leave(); return Symres_Success; diff --git a/compiler/src/wasm_emit.c b/compiler/src/wasm_emit.c index 53f2f7be..028e76fc 100644 --- a/compiler/src/wasm_emit.c +++ b/compiler/src/wasm_emit.c @@ -556,6 +556,7 @@ EMIT_FUNC(enter_structured_block, StructuredBlockType sbt, OnyxToken* blo EMIT_FUNC_NO_ARGS(leave_structured_block); static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum); +static void emit_raw_string(OnyxWasmModule* mod, char *data, i32 len, u64 *out_data_id, u64 *out_len); static void emit_constexpr(ConstExprContext *ctx, AstTyped *node, u32 offset); static b32 emit_constexpr_(ConstExprContext *ctx, AstTyped *node, u32 offset); @@ -2189,6 +2190,12 @@ EMIT_FUNC(call, AstCall* call) { if (cc == CC_Return_Stack) reserve_size += return_size; + if (context.options->stack_trace_enabled) { + u64 stack_trace_pass_global = bh_imap_get(&mod->index_map, (u64) &builtin_stack_trace); + emit_stack_address(mod, &code, mod->stack_trace_idx, NULL); + WIL(NULL, WI_GLOBAL_SET, stack_trace_pass_global); + } + if (call->callee->kind == Ast_Kind_Function) { i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) call->callee); WIL(NULL, WI_CALL, func_idx); @@ -4211,9 +4218,68 @@ static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func) { } } +EMIT_FUNC(stack_trace_blob, AstFunction *fd) { + bh_arr(WasmInstruction) code = *pcode; + + mod->stack_trace_idx = emit_local_allocation(mod, &code, (AstTyped *) fd->stack_trace_local); + assert(!(mod->stack_trace_idx & LOCAL_IS_WASM)); + + u64 file_name_id, func_name_id; + u8* node_data = bh_alloc_array(context.ast_alloc, u8, 6 * POINTER_SIZE); + + char *name = get_function_name(fd); + emit_raw_string(mod, (const char *) fd->token->pos.filename, strlen(fd->token->pos.filename), &file_name_id, &node_data[4]); + emit_raw_string(mod, name, strlen(name), &func_name_id, &node_data[16]); + *((u32 *) &node_data[8]) = fd->token->pos.line; + *((u32 *) &node_data[20]) = fd->type->id; + + WasmDatum stack_node_data = ((WasmDatum) { + .data = node_data, + .length = 6 * POINTER_SIZE, + .alignment = POINTER_SIZE, + }); + u32 stack_node_data_id = emit_data_entry(mod, &stack_node_data); + + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.index = stack_node_data_id; + patch.offset = 0; + patch.location = 0; + patch.data_id = file_name_id; + bh_arr_push(mod->data_patches, patch); + + patch.location = 12; + patch.data_id = func_name_id; + bh_arr_push(mod->data_patches, patch); + + u64 offset = 0; + u64 stack_trace_pass_global = bh_imap_get(&mod->index_map, (u64) &builtin_stack_trace); + + emit_location_return_offset(mod, &code, (AstTyped *) fd->stack_trace_local, &offset); + WIL(NULL, WI_GLOBAL_GET, stack_trace_pass_global); + emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], offset); + + offset = 0; + emit_location_return_offset(mod, &code, (AstTyped *) fd->stack_trace_local, &offset); + emit_data_relocation(mod, &code, stack_node_data_id); + emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], offset + 4); + + *pcode = code; +} + static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { if (!should_emit_function(fd)) return; + if (fd == builtin_initialize_data_segments && !mod->doing_linking) { + // This is a large hack, but is necessary. + // This particular function (__initialize_data_segments) should not be generated + // until the module is in its linking phase. This is because we have to wait + // until we have reached the linking phase to know every data element that is + // and will be present. Without this, the generating data segments in the bodies + // of the functions is not possible. + return; + } + i32 type_idx = generate_type_idx(mod, fd->type); WasmFunc wasm_func = { 0 }; @@ -4305,6 +4371,10 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_LOCAL_SET, { .l = mod->closure_base_idx } })); } + if (fd->stack_trace_local) { + emit_stack_trace_blob(mod, &wasm_func.code, fd); + } + // Generate code emit_function_body(mod, &wasm_func.code, fd); @@ -4479,19 +4549,18 @@ static void emit_global(OnyxWasmModule* module, AstGlobal* global) { module->globals[global_idx].initial_value[0].data.i1 = module->next_tls_offset; } -static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) { - +static void emit_raw_string(OnyxWasmModule* mod, char *data, i32 len, u64 *out_data_id, u64 *out_len) { // NOTE: Allocating more than necessary, but there are no cases // in a string literal that create more bytes than already // existed. You can create less however ('\n' => 0x0a). - i8* strdata = bh_alloc_array(global_heap_allocator, i8, strlit->token->length + 1); - i32 length = string_process_escape_seqs(strdata, strlit->token->text, strlit->token->length); + i8* strdata = bh_alloc_array(global_heap_allocator, i8, len + 1); + i32 length = string_process_escape_seqs(strdata, data, len); i32 index = shgeti(mod->string_literals, (char *) strdata); if (index != -1) { StrLitInfo sti = mod->string_literals[index].value; - strlit->data_id = sti.data_id; - strlit->length = sti.len + (strlit->is_cstr ? 1 : 0); + *out_data_id = sti.data_id; + *out_len = sti.len; bh_free(global_heap_allocator, strdata); return; @@ -4506,11 +4575,16 @@ static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) { .data = strdata, }; - strlit->data_id = emit_data_entry(mod, &datum); - strlit->length = length + (strlit->is_cstr ? 1 : 0); + *out_data_id = emit_data_entry(mod, &datum); + *out_len = length; // :ProperLinking - shput(mod->string_literals, (char *) strdata, ((StrLitInfo) { strlit->data_id, length })); + shput(mod->string_literals, (char *) strdata, ((StrLitInfo) { *out_data_id, length })); +} + +static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) { + emit_raw_string(mod, strlit->token->text, strlit->token->length, &strlit->data_id, &strlit->length); + if (strlit->is_cstr) strlit->length += 1; } static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum) { @@ -5049,6 +5123,8 @@ void onyx_wasm_module_link(OnyxWasmModule *module, OnyxWasmLinkOptions *options) // the code will probably need to be altered. assert(POINTER_SIZE == 4); + module->doing_linking = 1; + module->memory_min_size = options->memory_min_size; module->memory_max_size = options->memory_max_size; @@ -5101,6 +5177,10 @@ void onyx_wasm_module_link(OnyxWasmModule *module, OnyxWasmLinkOptions *options) datum_offset += datum->length; } + // Now that we know where the data elements will go (and to avoid a lot of patches), + // we can emit the __initialize_data_segments function. + emit_function(module, builtin_initialize_data_segments); + bh_arr_each(DatumPatchInfo, patch, module->data_patches) { if (patch->data_id == 0) { if (patch->node_to_use_if_data_id_is_null diff --git a/compiler/src/wasm_intrinsics.h b/compiler/src/wasm_intrinsics.h index 653ab7e1..f3f72e6e 100644 --- a/compiler/src/wasm_intrinsics.h +++ b/compiler/src/wasm_intrinsics.h @@ -430,7 +430,7 @@ EMIT_FUNC_NO_ARGS(initialize_data_segments_body) { assert(datum->id > 0); if (datum->data == NULL) { index++; continue; } - emit_data_relocation(mod, &code, datum->id); + WIL(NULL, WI_PTR_CONST, datum->offset_); WID(NULL, WI_PTR_CONST, 0); WID(NULL, WI_I32_CONST, datum->length); WID(NULL, WI_MEMORY_INIT, ((WasmInstructionData) { index, 0 })); diff --git a/core/runtime/info/stack_trace.onyx b/core/runtime/info/stack_trace.onyx new file mode 100644 index 00000000..95c929df --- /dev/null +++ b/core/runtime/info/stack_trace.onyx @@ -0,0 +1,37 @@ +package runtime.info +use runtime + +Stack_Node :: struct { + file: str; + line: u32; + func_name: str; + func_type: type_expr; +} + +Stack_Trace :: struct { + prev: &Stack_Trace; + data: &Stack_Node; +} + +#if runtime.Stack_Trace_Enabled { + +get_stack_trace :: () -> [] &Stack_Node { + trace: [..] &Stack_Node; + + walker := __stack_trace.prev; + while walker { + trace << walker.data; + walker = walker.prev; + } + + return trace; +} + +} else { + +get_stack_trace :: () -> [] &Stack_Node { + return .[]; +} + +} + diff --git a/interpreter/src/vm/disasm.c b/interpreter/src/vm/disasm.c index 4292876a..70f29c66 100644 --- a/interpreter/src/vm/disasm.c +++ b/interpreter/src/vm/disasm.c @@ -122,7 +122,12 @@ static instr_format_t instr_formats[] = { { "transmute_f32", instr_format_ra }, { "transmute_f64", instr_format_ra }, - { "cmpxchg", instr_format_rab } + { "cmpxchg", instr_format_rab }, + + { "break", instr_format_none }, + + { "memory_size", instr_format_none }, + { "memory_grow", instr_format_ra } }; void ovm_disassemble(ovm_program_t *program, u32 instr_addr, bh_buffer *instr_text) {