added #init procedures that run before main
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 14 Nov 2021 19:36:30 +0000 (13:36 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 14 Nov 2021 19:36:30 +0000 (13:36 -0600)
13 files changed:
core/builtin.onyx
core/runtime/common.onyx
docs/todo
include/astnodes.h
src/astnodes.c
src/builtins.c
src/checker.c
src/clone.c
src/entities.c
src/parser.c
src/symres.c
src/wasm_emit.c
src/wasm_intrinsics.c

index 3b4c41b046da4ebf8cc45ccb239a6794985180cf..a90184b41078133b7f8feeedcfba6bbba9378c72 100644 (file)
@@ -208,3 +208,6 @@ Code :: struct {_:i32;}
 // is undefined behaviour if it is called more than once.
 __initialize_data_segments :: () -> void ---
 
+// This is also a special compiler generated procedure that calls all procedures specified with
+// #init, in the specified order. It should theoritically only be called once on the main thread.
+__run_init_procedures :: () -> void ---
index 8273ddc38521a275b0b202eb676b60b03c55cdf9..39441d8d73ab4737ea79a96d6ba80ce8ab8d242c 100644 (file)
@@ -19,6 +19,11 @@ __assert_handler :: (msg: str, site: CallSite) {
     __exit(1);
 }
 
+// This procedure should only be called once at the very begining
+// of the program. It initializes the data segments, the global heap
+// allocator, as well as thread-local storage, the context, and stdio
+// on the main thread. Finally it runs initialization procedures, which
+// should only happen once.
 __runtime_initialize :: () {
     __initialize_data_segments();
 
@@ -26,6 +31,8 @@ __runtime_initialize :: () {
     __thread_initialize();
 
     #if Multi_Threading_Enabled do thread.__initialize();
+
+    __run_init_procedures();
 }
 
 // Use this procedure to initialize everything needed in the
index af66de5e275409548e130ade1d7db967ef62a7cc..9944c3d6efabcc5c1c4fa52f17ca97b85213b485 100644 (file)
--- a/docs/todo
+++ b/docs/todo
@@ -207,6 +207,7 @@ Wishlist:
         - Useful for map.get_ptr (^arr[10] or ^people["John"])
     [ ] Complete the set of combinations of procedure declaration syntaxes
         - Currently (x: i32) => { ... } isn't legal.
+    [X] #init functions (run before main, added to list at compile time, emitted with __run_init_procedures())
     [ ] Tests for new language features
         - Do blocks
         - Code blocks
index f6c154bf0da82d85c76b58935ea4a60ebe183c3a..ddb2f36f64fdda1bdf4bfbfd759a9d0c93332ad1 100644 (file)
@@ -39,6 +39,7 @@
     NODE(DirectiveExport)      \
     NODE(DirectiveDefined)     \
     NODE(DirectiveTag)         \
+    NODE(DirectiveInit)        \
                                \
     NODE(Return)               \
     NODE(Jump)                 \
@@ -200,6 +201,7 @@ typedef enum AstKind {
     Ast_Kind_Directive_Export,
     Ast_Kind_Directive_Defined,
     Ast_Kind_Directive_Tag,
+    Ast_Kind_Directive_Init,
     Ast_Kind_Call_Site,
 
     Ast_Kind_Code_Block,
@@ -1165,6 +1167,13 @@ struct AstDirectiveInsert {
     AstTyped *code_expr;
 };
 
+struct AstDirectiveInit {
+    AstNode_base;
+
+    AstTyped *init_proc;
+    bh_arr(AstDirectiveInit *) dependencies;
+};
+
 struct AstMacro {
     AstTyped_base;
 
@@ -1422,6 +1431,8 @@ extern AstType  *builtin_any_type;
 extern AstType  *builtin_code_type;
 extern AstTyped *type_table_node;
 extern AstFunction *builtin_initialize_data_segments;
+extern AstFunction *builtin_run_init_procedures;
+extern bh_arr(AstFunction *) init_procedures;
 
 typedef struct BuiltinSymbol {
     char*    package;
index e91991ffa981202c0bfd83ddb6fd5a9cde06ca46..b5a8d3c7f85fa7981c33629740853410dbbbb2ba 100644 (file)
@@ -90,6 +90,7 @@ static const char* ast_node_names[] = {
     "EXPORT",
     "DEFINED",
     "TAG",
+    "INIT",
     "CALL SITE",
 
     "CODE BLOCK",
index dbeff10908ef0c8e302a11014e46a46d5c50958c..8ddfa417ce342734f584b57b5fb0332171f92b44 100644 (file)
@@ -59,8 +59,10 @@ AstType  *builtin_callsite_type;
 AstType  *builtin_any_type;
 AstType  *builtin_code_type;
 
-AstFunction *builtin_initialize_data_segments = NULL;
 AstTyped    *type_table_node = NULL;
+AstFunction *builtin_initialize_data_segments = NULL;
+AstFunction *builtin_run_init_procedures = NULL;
+bh_arr(AstFunction *) init_procedures = NULL;
 
 const BuiltinSymbol builtin_symbols[] = {
     { NULL, "void",       (AstNode *) &basic_type_void },
@@ -438,6 +440,14 @@ void initialize_builtins(bh_allocator a) {
         return;
     }
 
+    builtin_run_init_procedures = (AstFunction *) symbol_raw_resolve(p->scope, "__run_init_procedures");
+    if (builtin_run_init_procedures == NULL || builtin_run_init_procedures->kind != Ast_Kind_Function) {
+        onyx_report_error((OnyxFilePos) { 0 }, "'__run_init_procedures");
+        return;
+    }
+
+    bh_arr_new(global_heap_allocator, init_procedures, 4);
+
     p = package_lookup("builtin.type_info");
     if (p != NULL) {
         type_table_node = (AstTyped *) symbol_raw_resolve(p->scope, "type_table");
index 093935479ac62db7b9d4da0dc7d20c3365413dff..511747e5b2299213296d20d2a54c51a49464a636 100644 (file)
@@ -1631,6 +1631,10 @@ CheckStatus check_expression(AstTyped** pexpr) {
         return Check_Success;
     }
 
+    if (expr->kind == Ast_Kind_Directive_Init) {
+        ERROR(expr->token->pos, "#init declarations are not in normal expressions, only in #after clauses.");
+    }
+
     fill_in_type(expr);
     current_checking_level = EXPRESSION_LEVEL;
 
@@ -2399,6 +2403,44 @@ CheckStatus check_process_directive(AstNode* directive) {
         }
     }
 
+    if (directive->kind == Ast_Kind_Directive_Init) {
+        AstDirectiveInit *init = (AstDirectiveInit *) directive;
+        if ((init->flags & Ast_Flag_Has_Been_Checked) == 0) {
+            CHECK(expression, &init->init_proc);
+
+            if (init->init_proc->kind != Ast_Kind_Function) {
+                ERROR_(init->token->pos, "#init only works for functions, got '%s'", onyx_ast_node_kind_string(init->init_proc->kind));
+            }
+
+            assert(init->init_proc->type);
+            if (init->init_proc->type->Function.param_count != 0) {
+                ERROR(init->token->pos, "#init expects a function that takes 0 arguments.");
+            }
+        }
+
+        init->flags |= Ast_Flag_Has_Been_Checked;
+
+        if (init->dependencies) {
+            i32 i = 0;
+            bh_arr_each(AstDirectiveInit *, dependency, init->dependencies) {
+                AstTyped *d = (AstTyped *) strip_aliases((AstNode *) *dependency);
+                if (d->kind != Ast_Kind_Directive_Init) {
+                    ERROR_(init->token->pos, "All dependencies of an #init must be another #init. The %d%s dependency was not.", i + 1, bh_num_suffix(i + 1));
+                }
+
+                assert(d->entity);
+                if (d->entity->state != Entity_State_Finalized) {
+                    YIELD(init->token->pos, "Circular dependency in #init nodes. Here are the nodes involved.");
+                }
+
+                i++;
+            }
+        }
+
+        bh_arr_push(init_procedures, (AstFunction *) init->init_proc);
+        return Check_Complete;
+    }
+
     return Check_Success;
 }
 
index b7a5c981e6ae68aae082d3cef58e03c8d57a6f75..753a23e4a3305494a404681a16e3d0d939793222 100644 (file)
@@ -19,6 +19,7 @@ static inline b32 should_clone(AstNode* node) {
         case Ast_Kind_Code_Block:
         case Ast_Kind_Macro:
         case Ast_Kind_File_Contents:
+        case Ast_Kind_Symbol:
             return 0;
 
         default: return 1;
index 6300629a695da1b2d4908014146a06fa99d6cbcf..6d8f94b68bc408cb8653fab375376e75d4bdbc5a 100644 (file)
@@ -322,7 +322,8 @@ void add_entities_for_node(bh_arr(Entity *) *target_arr, AstNode* node, Scope* s
         case Ast_Kind_Directive_Export:
         case Ast_Kind_Directive_Add_Overload:
         case Ast_Kind_Directive_Tag:
-        case Ast_Kind_Directive_Operator: {
+        case Ast_Kind_Directive_Operator:
+        case Ast_Kind_Directive_Init: {
             ent.type = Entity_Type_Process_Directive;
             ent.expr = (AstTyped *) node;
             ENTITY_INSERT(ent);
index 7c15a929d43a8517a6770447100b9d85cf571b03..d336d3ce39076dcc75055e0ac398a51cdcd17281 100644 (file)
@@ -2695,6 +2695,25 @@ static AstMacro* parse_macro(OnyxParser* parser) {
     return NULL;
 }
 
+static AstDirectiveInit* parse_init_directive(OnyxParser *parser, OnyxToken *token) {
+    AstDirectiveInit *init = make_node(AstDirectiveInit, Ast_Kind_Directive_Init);
+    init->token = token;
+
+    parser->parse_calls = 0;
+    while (parse_possible_directive(parser, "after")) {
+        if (parser->hit_unexpected_token) return init;
+        if (init->dependencies == NULL) bh_arr_new(global_heap_allocator, init->dependencies, 2);
+
+        AstTyped *dependency = parse_expression(parser, 0);
+        bh_arr_push(init->dependencies, (AstDirectiveInit *) dependency);
+    }
+    parser->parse_calls = 1;
+
+    init->init_proc = parse_expression(parser, 0);
+    ENTITY_SUBMIT(init);
+    return init;
+}
+
 static AstTyped* parse_top_level_expression(OnyxParser* parser) {
     if (parser->curr->type == Token_Type_Keyword_Global)    return parse_global_declaration(parser);
     if (parser->curr->type == Token_Type_Keyword_Struct)    return (AstTyped *) parse_struct(parser);
@@ -2715,6 +2734,12 @@ static AstTyped* parse_top_level_expression(OnyxParser* parser) {
         return (AstTyped *) ofunc;
     }
 
+    if (parse_possible_directive(parser, "init")) {
+        // :LinearTokenDependent
+        AstDirectiveInit *init = parse_init_directive(parser, parser->curr - 2);
+        return (AstTyped *) init;
+    }
+
     return parse_expression(parser, 1);
 }
 
@@ -2779,6 +2804,8 @@ static AstBinding* parse_top_level_binding(OnyxParser* parser, OnyxToken* symbol
             break;
         }
 
+        case Ast_Kind_Directive_Init: break;
+
         case Ast_Kind_Global: ((AstGlobal *) node)->name = generate_name_within_scope(parser, symbol);
 
         case Ast_Kind_Overloaded_Function:
@@ -3022,6 +3049,11 @@ static void parse_top_level_statement(OnyxParser* parser) {
 
                 goto submit_binding_to_entities;
             }
+            else if (parse_possible_directive(parser, "init")) {
+                // :LinearTokenDependent
+                parse_init_directive(parser, parser->curr - 2);
+                return;
+            }
             else {
                 OnyxToken* directive_token = expect_token(parser, '#');
                 OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol);
index 5042b48a2f0a7c082668ab529e06de8b9556b26e..6005785b8dcdcfe103292cfc376d2c45ca8af2f4 100644 (file)
@@ -1242,6 +1242,19 @@ static SymresStatus symres_process_directive(AstNode* directive) {
             SYMRES(expression, &tag->expr);
             break;
         }
+
+        case Ast_Kind_Directive_Init: {
+            AstDirectiveInit *init = (AstDirectiveInit *) directive;
+            SYMRES(expression, &init->init_proc);
+
+            if (init->dependencies) {
+                bh_arr_each(AstDirectiveInit *, dependency, init->dependencies) {
+                    SYMRES(expression, (AstTyped **) dependency);
+                }
+            }
+
+            break;
+        }
     }
 
     return Symres_Success;
index 477498b568d18e2560b35bc602a926109bf95dce..5589cb7edf5e4331997db34c97eb8f4527ab76a7 100644 (file)
@@ -3193,6 +3193,13 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
         return;
     }
 
+    if (fd == builtin_run_init_procedures) {
+        emit_run_init_procedures(mod, &wasm_func.code);
+        bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 }));
+        bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func);
+        return;
+    }
+
     if (fd->body != NULL) {
         // NOTE: Generate the local map
         u64 localidx = 0;
index f39ffe21362195692eebd7d7ae5cefdefbb7c65d..1d2b229aa0490e229ac0322b3eff4fd6c9573eb1 100644 (file)
@@ -427,4 +427,15 @@ EMIT_FUNC_NO_ARGS(initialize_data_segments_body) {
     }
 
     *pcode = code;
-}
\ No newline at end of file
+}
+
+EMIT_FUNC_NO_ARGS(run_init_procedures) {
+    bh_arr(WasmInstruction) code = *pcode;
+
+    bh_arr_each(AstFunction *, func, init_procedures) {
+        i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) *func);
+        bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx }));
+    }
+
+    *pcode = code;
+}