From 2bdae52fed2ac0845ebb24348f3834dbc63f6d34 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 3 Oct 2021 11:53:32 +0100 Subject: [PATCH] Add @(init) attribute for procedures, allowing for procedures to be called at startup These procedures will be called after global variables have been initialized as normal --- src/check_decl.cpp | 3 ++ src/checker.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++- src/checker.hpp | 18 +++++---- src/common.cpp | 33 +++++++++++++++++ src/entity.cpp | 1 + src/llvm_backend.cpp | 14 +++++-- src/parser.hpp | 1 + 7 files changed, 145 insertions(+), 13 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index d9229ee9b..0591eca4d 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -722,6 +722,9 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (ac.test) { e->flags |= EntityFlag_Test; } + if (ac.init) { + e->flags |= EntityFlag_Init; + } if (ac.set_cold) { e->flags |= EntityFlag_Cold; } diff --git a/src/checker.cpp b/src/checker.cpp index fd3de3552..e1ccf5d96 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -862,6 +862,7 @@ void init_checker_info(CheckerInfo *i) { string_map_init(&i->packages, a); array_init(&i->variable_init_order, a); array_init(&i->testing_procedures, a, 0, 0); + array_init(&i->init_procedures, a, 0, 0); array_init(&i->required_foreign_imports_through_force, a, 0, 0); @@ -1266,7 +1267,7 @@ void add_entity_flags_from_file(CheckerContext *c, Entity *e, Scope *scope) { AstPackage *pkg = c->file->pkg; if (pkg->kind == Package_Init && e->kind == Entity_Procedure && e->token.string == "main") { // Do nothing - } else if (e->flags & EntityFlag_Test) { + } else if (e->flags & (EntityFlag_Test|EntityFlag_Init)) { // Do nothing } else { e->flags |= EntityFlag_Lazy; @@ -1340,7 +1341,7 @@ bool could_entity_be_lazy(Entity *e, DeclInfo *d) { return false; } - if (e->flags & EntityFlag_Test) { + if (e->flags & (EntityFlag_Test|EntityFlag_Init)) { return false; } else if (e->kind == Entity_Variable && e->Variable.is_export) { return false; @@ -1374,6 +1375,8 @@ bool could_entity_be_lazy(Entity *e, DeclInfo *d) { return false; } else if (name == "export") { return false; + } else if (name == "init") { + return false; } } } @@ -2054,6 +2057,29 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { if (e->Procedure.is_export) { add_dependency_to_set(c, e); } + if (e->flags & EntityFlag_Init) { + Type *t = base_type(e->type); + GB_ASSERT(t->kind == Type_Proc); + + bool is_init = true; + + if (t->Proc.param_count != 0 || t->Proc.result_count != 0) { + gbString str = type_to_string(t); + error(e->token, "@(init) procedures must have a signature type with no parameters nor results, got %s", str); + gb_string_free(str); + is_init = false; + } + + if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) { + error(e->token, "@(init) procedures must be declared at the file scope"); + is_init = false; + } + + if (is_init) { + add_dependency_to_set(c, e); + array_add(&c->info.init_procedures, e); + } + } break; } } @@ -2611,6 +2637,12 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) { return false; } return true; + } else if (name == "init") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter, or a string literal containing \"file\" or \"package\"", LIT(name)); + } + ac->init = true; + return true; } else if (name == "deferred") { if (value != nullptr) { Operand o = {}; @@ -3154,6 +3186,7 @@ void check_collect_value_decl(CheckerContext *c, Ast *decl) { EntityVisiblityKind entity_visibility_kind = c->foreign_context.visibility_kind; bool is_test = false; + bool is_init = false; for_array(i, vd->attributes) { Ast *attr = vd->attributes[i]; @@ -3211,6 +3244,8 @@ void check_collect_value_decl(CheckerContext *c, Ast *decl) { j -= 1; } else if (name == "test") { is_test = true; + } else if (name == "init") { + is_init = true; } } } @@ -3337,6 +3372,9 @@ void check_collect_value_decl(CheckerContext *c, Ast *decl) { if (is_test) { e->flags |= EntityFlag_Test; } + if (is_init) { + e->flags |= EntityFlag_Init; + } } else if (init->kind == Ast_ProcGroup) { ast_node(pg, ProcGroup, init); e = alloc_entity_proc_group(d->scope, token, nullptr); @@ -4342,6 +4380,7 @@ void check_import_entities(Checker *c) { for (isize pkg_index = 0; pkg_index < package_order.count; pkg_index++) { ImportGraphNode *node = package_order[pkg_index]; AstPackage *pkg = node->pkg; + pkg->order = 1+pkg_index; for_array(i, pkg->files) { AstFile *f = pkg->files[i]; @@ -5060,6 +5099,48 @@ void check_merge_queues_into_arrays(Checker *c) { check_add_definitions_from_queues(c); } +GB_COMPARE_PROC(init_procedures_cmp) { + int cmp = 0; + Entity *x = *(Entity **)a; + Entity *y = *(Entity **)b; + if (x == y) { + cmp = 0; + return cmp; + } + + if (x->pkg != y->pkg) { + isize order_x = x->pkg ? x->pkg->order : 0; + isize order_y = y->pkg ? y->pkg->order : 0; + cmp = isize_cmp(order_x, order_y); + if (cmp) { + return cmp; + } + } + if (x->file != y->file) { + String fullpath_x = x->file ? x->file->fullpath : (String{}); + String fullpath_y = y->file ? y->file->fullpath : (String{}); + String file_x = filename_from_path(fullpath_x); + String file_y = filename_from_path(fullpath_y); + + cmp = string_compare(file_x, file_y); + if (cmp) { + return cmp; + } + } + + + cmp = u64_cmp(x->order_in_src, y->order_in_src); + if (cmp) { + return cmp; + } + return i32_cmp(x->token.pos.offset, y->token.pos.offset); +} + + +void check_sort_init_procedures(Checker *c) { + gb_sort_array(c->info.init_procedures.data, c->info.init_procedures.count, init_procedures_cmp); +} + void check_parsed_files(Checker *c) { #define TIME_SECTION(str) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0) @@ -5227,6 +5308,9 @@ void check_parsed_files(Checker *c) { TIME_SECTION("sanity checks"); GB_ASSERT(c->info.entity_queue.count.load(std::memory_order_relaxed) == 0); GB_ASSERT(c->info.definition_queue.count.load(std::memory_order_relaxed) == 0); + + TIME_SECTION("sort init procedures"); + check_sort_init_procedures(c); TIME_SECTION("type check finish"); diff --git a/src/checker.hpp b/src/checker.hpp index 1b2ad0575..97631c547 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -99,14 +99,6 @@ struct DeferredProcedure { struct AttributeContext { - bool is_export; - bool is_static; - bool require_results; - bool require_declaration; - bool has_disabled_proc; - bool disabled_proc; - bool test; - bool set_cold; String link_name; String link_prefix; String link_section; @@ -115,6 +107,15 @@ struct AttributeContext { String deprecated_message; String warning_message; DeferredProcedure deferred_procedure; + bool is_export : 1; + bool is_static : 1; + bool require_results : 1; + bool require_declaration : 1; + bool has_disabled_proc : 1; + bool disabled_proc : 1; + bool test : 1; + bool init : 1; + bool set_cold : 1; u32 optimization_mode; // ProcedureOptimizationMode }; @@ -284,6 +285,7 @@ struct CheckerInfo { Array testing_procedures; + Array init_procedures; Array definitions; Array entities; diff --git a/src/common.cpp b/src/common.cpp index 3216a8725..0b622eea4 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -47,6 +47,39 @@ void debugf(char const *fmt, ...); #include "range_cache.cpp" +int isize_cmp(isize x, isize y) { + if (x < y) { + return -1; + } else if (x > y) { + return +1; + } + return 0; +} +int u64_cmp(u64 x, u64 y) { + if (x < y) { + return -1; + } else if (x > y) { + return +1; + } + return 0; +} +int i64_cmp(i64 x, i64 y) { + if (x < y) { + return -1; + } else if (x > y) { + return +1; + } + return 0; +} +int i32_cmp(i32 x, i32 y) { + if (x < y) { + return -1; + } else if (x > y) { + return +1; + } + return 0; +} + u32 fnv32a(void const *data, isize len) { u8 const *bytes = cast(u8 const *)data; u32 h = 0x811c9dc5; diff --git a/src/entity.cpp b/src/entity.cpp index b79a38a1a..86fefcf89 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -73,6 +73,7 @@ enum EntityFlag : u64 { EntityFlag_SwitchValue = 1ull<<29, EntityFlag_Test = 1ull<<30, + EntityFlag_Init = 1ull<<31, EntityFlag_Overridden = 1ull<<63, diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 840c7b4a5..e2141b6f1 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -653,7 +653,7 @@ lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *start lb_populate_function_pass_manager(main_module, default_function_pass_manager, false, build_context.optimization_level); LLVMFinalizeFunctionPassManager(default_function_pass_manager); - Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_CDecl); + Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_Odin); lbProcedure *p = lb_create_dummy_procedure(main_module, str_lit(LB_STARTUP_RUNTIME_PROC_NAME), proc_type); p->is_startup = true; @@ -731,6 +731,13 @@ lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *start var->is_initialized = true; } } + + CheckerInfo *info = main_module->gen->info; + + for (Entity *e : info->init_procedures) { + lbValue value = lb_find_procedure_value_from_entity(main_module, e); + lb_emit_call(p, value, {}, ProcInlining_none, false); + } lb_end_procedure_body(p); @@ -799,8 +806,9 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) lbAddr args = lb_addr(lb_find_runtime_value(p->module, str_lit("args__"))); lb_fill_slice(p, args, argv, argc); } - - LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(m, startup_runtime->type)), startup_runtime->value, nullptr, 0, ""); + + lbValue startup_runtime_value = {startup_runtime->value, startup_runtime->type}; + lb_emit_call(p, startup_runtime_value, {}, ProcInlining_none, false); if (build_context.command_kind == Command_test) { Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test")); diff --git a/src/parser.hpp b/src/parser.hpp index 6ce877a6c..f1779bdbc 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -174,6 +174,7 @@ struct AstPackage { Array files; Array foreign_files; bool is_single_file; + isize order; MPMCQueue exported_entity_queue;