added: user-level stack trace available with `--stack-trace`
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 9 Jun 2023 21:03:59 +0000 (16:03 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 9 Jun 2023 21:03:59 +0000 (16:03 -0500)
compiler/include/astnodes.h
compiler/include/wasm_emit.h
compiler/src/builtins.c
compiler/src/checker.c
compiler/src/onyx.c
compiler/src/symres.c
compiler/src/wasm_emit.c
compiler/src/wasm_intrinsics.h
core/runtime/info/stack_trace.onyx [new file with mode: 0644]
interpreter/src/vm/disasm.c

index 7191b9611172b778364a84125d08e6d729c94292..8dcca96b9067afcbceb7c498ba0c2423d77545fd 100644 (file)
@@ -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;
index 7cd54e0da98c5fd9cf979c2d1f86b29a9837f043..8f79b6995b1d0af89e406896d6e5087911766708 100644 (file)
@@ -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;
index d7bd9a992d0083b7af5086fc97b65622ac446163..0fa9e19fc98d08d33cae81d5752f837cabba03c9 100644 (file)
@@ -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);
     }
+
 }
 
index a5fb1168cb8fc6fd7b4833d4578239cb278f518c..e8847a11b3eebaf57dccdcf930aaeab8cd0a0e1f 100644 (file)
@@ -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);
         }
index 21ac430d00cdb00872cc68359a431d4a3ea3dc35..5370ffd17d3216520d17827f5a59ad8a5ad162e8 100644 (file)
@@ -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 <doc_file>        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 <target_file> 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--;
                 }
 
index 385e77438d5efe136ca65a4f9b047a8206165e39..9535ca4f533a83d00e4b4fad41e9ad1c6a63ea67 100644 (file)
@@ -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;
index 53f2f7be5168f3774614eba554338758b58a3685..028e76fca37816b16268b5070b7d49a3f91b989a 100644 (file)
@@ -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
index 653ab7e1773eb55df4cbef4d097c312eb29a6703..f3f72e6ec17325bd7e3dd95205de0ab8f27c5748 100644 (file)
@@ -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 (file)
index 0000000..95c929d
--- /dev/null
@@ -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 .[];
+}
+    
+}
+
index 4292876a25593d4d8df6e790d342167e1b3e4646..70f29c66fc12f0d9d75d5a245a3d39ca7308783e 100644 (file)
@@ -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) {