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;
bh_arr(DefinedVariable) defined_variables;
b32 debug_enabled;
+ b32 stack_trace_enabled;
i32 passthrough_argument_count;
char** passthrough_argument_data;
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;
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;
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;
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;
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;
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");
+ }
}
}
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;
foreign_info->type_node = (AstType *) &basic_type_bool;
symbol_builtin_introduce(p->scope, "Generated_Foreign_Info", (AstNode *) foreign_info);
}
+
}
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);
}
"\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"
}
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;
}
// 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);
prepare_builtins();
// HACK
- special_global_entities_remaining = 3;
+ special_global_entities_remaining = 4;
context.options = opts;
context.cycle_detected = 0;
.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;
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) {
// 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--;
}
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;
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);
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);
}
}
+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 };
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);
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;
.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) {
// 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;
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
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 }));
--- /dev/null
+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 .[];
+}
+
+}
+
{ "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) {