From d5b1fc48fbc5f853e8995ca520417a69fbad701a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 21 Aug 2025 17:14:33 +0100 Subject: [PATCH] Add `@(raddbg_type_view=)` If no string parameter is provided, then one will be generated from the struct field tags. The attribute must be applied if the automatic struct field tag approach is to be used. --- src/check_decl.cpp | 7 ++ src/checker.cpp | 170 +++++++++++++++++++++++++++++++++++++++++++ src/checker.hpp | 11 +++ src/llvm_backend.cpp | 22 ++++++ 4 files changed, 210 insertions(+) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index b2522f24a..7dd9db105 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -602,6 +602,13 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, } else if (ac.objc_is_implementation) { error(e->token, "@(objc_implement) may only be applied when the @(objc_class) attribute is also applied"); } + + if (ac.raddbg_type_view) { + RaddbgTypeView type_view = {}; + type_view.type = e->type; + type_view.view = ac.raddbg_type_view_string; + mpsc_enqueue(&ctx->info->raddbg_type_views_queue, type_view); + } } diff --git a/src/checker.cpp b/src/checker.cpp index 44e63b750..c6dd1643a 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1451,6 +1451,9 @@ gb_internal void init_checker_info(CheckerInfo *i) { mpsc_init(&i->foreign_decls_to_check, a); // 1<<10); mpsc_init(&i->intrinsics_entry_point_usage, a); // 1<<10); // just waste some memory here, even if it probably never used + mpsc_init(&i->raddbg_type_views_queue, a); + array_init(&i->raddbg_type_views, a); + string_map_init(&i->load_directory_cache); map_init(&i->load_directory_map); } @@ -1479,6 +1482,9 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { mpsc_destroy(&i->foreign_imports_to_check_fullpaths); mpsc_destroy(&i->foreign_decls_to_check); + mpsc_destroy(&i->raddbg_type_views_queue); + array_free(&i->raddbg_type_views); + map_destroy(&i->objc_msgSend_types); string_set_destroy(&i->obcj_class_name_set); mpsc_destroy(&i->objc_class_implementations); @@ -4066,6 +4072,21 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) { return true; } + } else if (name == "raddbg_type_view") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Invalid) { + ac->raddbg_type_view = true; + } else if (ev.kind == ExactValue_String) { + ac->raddbg_type_view = true; + ac->raddbg_type_view_string = ev.value_string; + + if (ev.value_string.len == 0) { + error(elem, "Expected a non-empty string for '%.*s'", LIT(name)); + } + } else { + error(elem, "Expected a string or no value for '%.*s'", LIT(name)); + } + return true; } return false; } @@ -7037,6 +7058,155 @@ gb_internal void check_parsed_files(Checker *c) { } } + TIME_SECTION("collate type info stuff"); + { + auto const struct_tag_lookup = [](String tag, char const *key_c, String *value_) -> bool { + String t = tag; + String key = make_string_c(key_c); + while (t.len != 0) { + isize i = 0; + while (i < t.len && t[i] == ' ') { // Skip whitespace + i += 1; + } + t.text += i; + t.len -= i; + if (t.len == 0) { + break; + } + + i = 0; + + while (i < t.len) { + u8 c = t[i]; + if (c == ':' || c == '"') { + break; + } else if ((0 <= c && c < ' ') || (0x7f <= c && c <= 0x9f)) { + // break if control character is found + break; + } + i += 1; + } + + if (i == 0) { + break; + } + if (i+1 >= t.len) { + break; + } + if (t[i] != ':' || t[i+1] != '"') { + break; + } + String name = {t.text, i}; + t = {t.text+i+1, t.len-(i+1)}; + + i = 1; + while (i < t.len && t[i] != '"') { // find closing quote + if (t[i] == '\\') { + i += 1; // Skip escaped characters + } + i += 1; + } + if (i >= t.len) { + break; + } + + String value = {t.text, i+1}; + t = {t.text+i+1, t.len-(i+1)}; + + if (key == name) { + value = {value.text+1, i-1}; + if (value_) *value_ = value; + return true; + } + } + return false; + }; + + for (RaddbgTypeView type_view; mpsc_dequeue(&c->info.raddbg_type_views_queue, &type_view); /**/) { + + Type *type = type_view.type; + if (type == nullptr || type == t_invalid) { + continue; + } + String view = type_view.view; + if (view.len == 0) { + // NOTE(bill): Generate one automatically from the struct field tags if they exist + // If it cannot be generated, it'll be ignored/err + + Type *bt = base_type(type); + if (is_type_struct(type)) { + GB_ASSERT(bt->kind == Type_Struct); + if (bt->Struct.tags != nullptr) { + bool found_any = false; + + for (isize i = 0; i < bt->Struct.fields.count; i++) { + String tag = bt->Struct.tags[i]; + String value = {}; + if (struct_tag_lookup(tag, "raddbg", &value)) { + found_any = true; + } else if (struct_tag_lookup(tag, "fmt", &value)) { + found_any = true; + } + } + + if (!found_any) { + goto raddbg_type_view_end; + } + + gbString s = gb_string_make(heap_allocator(), ""); + + s = gb_string_appendc(s, "rows($"); + + for (isize i = 0; i < bt->Struct.fields.count; i++) { + Entity *field = bt->Struct.fields[i]; + GB_ASSERT(field != nullptr); + String name = field->token.string; + + s = gb_string_appendc(s, ", "); + + bool custom_rule = false; + + String tag = bt->Struct.tags[i]; + String value = {}; + if (struct_tag_lookup(tag, "raddbg", &value)) { + s = gb_string_append_length(s, value.text, value.len); + custom_rule = true; + } else if (struct_tag_lookup(tag, "fmt", &value)) { + auto p = string_partition(value, make_string_c(",")); + String tail = p.tail; + if (tail.len != 0 && tail != "0") { + s = gb_string_appendc(s, "array("); + s = gb_string_append_length(s, name.text, name.len); + s = gb_string_appendc(s, ", "); + s = gb_string_append_length(s, tail.text, tail.len); + s = gb_string_appendc(s, ")"); + custom_rule = true; + } + } + + if (!custom_rule) { + s = gb_string_append_length(s, name.text, name.len); + } + } + + s = gb_string_appendc(s, ")"); + + view = make_string((u8 const *)s, gb_string_length(s)); + } + } + } + + raddbg_type_view_end:; + + if (view.len == 0) { + // Ignore the type, it didn't anything custom + continue; + } + + array_add(&c->info.raddbg_type_views, RaddbgTypeView{type, view}); + } + } + TIME_SECTION("type check finish"); } diff --git a/src/checker.hpp b/src/checker.hpp index dabb7330a..e32250b2f 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -161,6 +161,9 @@ struct AttributeContext { String require_target_feature; // required by the target micro-architecture String enable_target_feature; // will be enabled for the procedure only + + bool raddbg_type_view; + String raddbg_type_view_string; }; gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix, String link_suffix) { @@ -427,6 +430,11 @@ struct Defineable { String pos_str; }; +struct RaddbgTypeView { + Type * type; + String view; +}; + // CheckerInfo stores all the symbol information for a type-checked program struct CheckerInfo { Checker *checker; @@ -487,6 +495,9 @@ struct CheckerInfo { MPSCQueue foreign_imports_to_check_fullpaths; MPSCQueue foreign_decls_to_check; + MPSCQueue raddbg_type_views_queue; + Array raddbg_type_views; + MPSCQueue intrinsics_entry_point_usage; BlockingMutex objc_objc_msgSend_mutex; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index fd3701108..ff17e9c10 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3416,7 +3416,29 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TEMPORARY_ALLOCATOR_GUARD(); + for (RaddbgTypeView const &type_view : gen->info->raddbg_type_views) { + if (type_view.type == nullptr) { + continue; + } + if (type_view.view.len == 0) { + continue; + } + + String t_str = type_to_canonical_string(temporary_allocator(), type_view.type); + + gbString s = gb_string_make(temporary_allocator(), ""); + + s = gb_string_appendc(s, "type_view: {type: \""); + s = gb_string_append_length(s, t_str.text, t_str.len); + s = gb_string_appendc(s, "\", expr: \""); + s = gb_string_append_length(s, type_view.view.text, type_view.view.len); + s = gb_string_appendc(s, "\"}"); + + lb_add_raddbg_string(m, s); + } + + TEMPORARY_ALLOCATOR_GUARD(); u32 global_name_index = 0; for (String str = {}; mpsc_dequeue(&gen->raddebug_section_strings, &str); /**/) { LLVMValueRef data = LLVMConstStringInContext(ctx, cast(char const *)str.text, cast(unsigned)str.len, false);