From 6ec66f3ee6b97d22037ec4ac45d6317611d21727 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sun, 14 Nov 2021 13:36:30 -0600 Subject: [PATCH] added #init procedures that run before main --- core/builtin.onyx | 3 +++ core/runtime/common.onyx | 7 +++++++ docs/todo | 1 + include/astnodes.h | 11 +++++++++++ src/astnodes.c | 1 + src/builtins.c | 12 +++++++++++- src/checker.c | 42 ++++++++++++++++++++++++++++++++++++++++ src/clone.c | 1 + src/entities.c | 3 ++- src/parser.c | 32 ++++++++++++++++++++++++++++++ src/symres.c | 13 +++++++++++++ src/wasm_emit.c | 7 +++++++ src/wasm_intrinsics.c | 13 ++++++++++++- 13 files changed, 143 insertions(+), 3 deletions(-) diff --git a/core/builtin.onyx b/core/builtin.onyx index 3b4c41b0..a90184b4 100644 --- a/core/builtin.onyx +++ b/core/builtin.onyx @@ -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 --- diff --git a/core/runtime/common.onyx b/core/runtime/common.onyx index 8273ddc3..39441d8d 100644 --- a/core/runtime/common.onyx +++ b/core/runtime/common.onyx @@ -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 diff --git a/docs/todo b/docs/todo index af66de5e..9944c3d6 100644 --- 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 diff --git a/include/astnodes.h b/include/astnodes.h index f6c154bf..ddb2f36f 100644 --- a/include/astnodes.h +++ b/include/astnodes.h @@ -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; diff --git a/src/astnodes.c b/src/astnodes.c index e91991ff..b5a8d3c7 100644 --- a/src/astnodes.c +++ b/src/astnodes.c @@ -90,6 +90,7 @@ static const char* ast_node_names[] = { "EXPORT", "DEFINED", "TAG", + "INIT", "CALL SITE", "CODE BLOCK", diff --git a/src/builtins.c b/src/builtins.c index dbeff109..8ddfa417 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -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"); diff --git a/src/checker.c b/src/checker.c index 09393547..511747e5 100644 --- a/src/checker.c +++ b/src/checker.c @@ -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; } diff --git a/src/clone.c b/src/clone.c index b7a5c981..753a23e4 100644 --- a/src/clone.c +++ b/src/clone.c @@ -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; diff --git a/src/entities.c b/src/entities.c index 6300629a..6d8f94b6 100644 --- a/src/entities.c +++ b/src/entities.c @@ -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); diff --git a/src/parser.c b/src/parser.c index 7c15a929..d336d3ce 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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); diff --git a/src/symres.c b/src/symres.c index 5042b48a..6005785b 100644 --- a/src/symres.c +++ b/src/symres.c @@ -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; diff --git a/src/wasm_emit.c b/src/wasm_emit.c index 477498b5..5589cb7e 100644 --- a/src/wasm_emit.c +++ b/src/wasm_emit.c @@ -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; diff --git a/src/wasm_intrinsics.c b/src/wasm_intrinsics.c index f39ffe21..1d2b229a 100644 --- a/src/wasm_intrinsics.c +++ b/src/wasm_intrinsics.c @@ -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; +} -- 2.25.1