diff options
| author | Mel <mel@rnrd.eu> | 2026-05-26 00:50:09 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2026-05-26 00:50:09 +0200 |
| commit | 900b412b052a0f745f0853d6703e4bafbb0a3ce5 (patch) | |
| tree | e04b65b395d4761e5b88fa1e9a792c3996ff3dc1 /boot/lower.c | |
| parent | e8400cc704eeb74f3c464f7a0822c556cb52e3fd (diff) | |
| download | catskill-900b412b052a0f745f0853d6703e4bafbb0a3ce5.tar.zst catskill-900b412b052a0f745f0853d6703e4bafbb0a3ce5.zip | |
Lambda function emission, thin/fat calling conventions for (future) closed-over state
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/lower.c')
| -rw-r--r-- | boot/lower.c | 97 |
1 files changed, 85 insertions, 12 deletions
diff --git a/boot/lower.c b/boot/lower.c index 696f3e4..ba83b39 100644 --- a/boot/lower.c +++ b/boot/lower.c @@ -644,10 +644,30 @@ lower_fill_function_signature( } fn->variadic = variadic; fn->main_takes_args = fn->is_main && array_length(&fn->params) > 0; + fn->ast_header = &fn_expr->value.function.header; fn->ast_body = &fn_expr->value.function.body; fn->body = nil; } +// synthesize the function's signature as a structural closure type +// the first time the function is referenced in value context, or reuse already +// synthesized type. +Type_Id +lower_function_closure_type(struct Lower_Context* ctx, struct Function* fn) +{ + if (fn->closure_type_id != 0) return fn->closure_type_id; + + struct Tree_Type wrapper = { + .type = TREE_TYPE_FUNCTION, + .value = { .function = { .header = *fn->ast_header } }, + .span = fn->ast_header->span, + .location = fn->ast_header->location, + }; + struct Type_Ref ref = lower_intern_type_ref(ctx, &wrapper); + fn->closure_type_id = ref.type_id; + return ref.type_id; +} + void lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_Type* tree_type) { @@ -837,6 +857,7 @@ lower_pass_1(struct Lower_Context* ctx, struct Tree* tree) struct Block* lower_block(struct Lower_Context* ctx, struct Tree_Block* tree_block); struct Statement* lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt); struct Expression* lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr); +bool lower_name_is_function_typed_local(struct Lower_Context* ctx, struct String name); struct Expression* lower_expression_integer_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr) @@ -869,7 +890,15 @@ lower_expression_boolean_literal(struct Lower_Context* ctx, struct Tree_Expressi struct Expression* lower_expression_name(struct Lower_Context* ctx, struct Tree_Expression* tree_expr) { - (void)ctx; + // names that resolve to a catskill function become a function reference. + Function_Id fn_id; + if (lower_function_lookup_by_name(ctx->unit, tree_expr->value.name.name, &fn_id)) { + // since this path is only taken outside direct call-positions, + // the function will need a closure type to wrap it as a value. + struct Function* fn = *array_at(struct Function*, &ctx->unit->functions.entries, fn_id); + Type_Id closure_type_id = lower_function_closure_type(ctx, fn); + return ir_make_function_ref(fn_id, closure_type_id, tree_expr->span); + } return ir_make_name(tree_expr->value.name.name, tree_expr->span); } @@ -1092,13 +1121,24 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex // only catskill-side functions are in the function table, calls to libc // and the like go through as positional with no named args allowed. struct Function* callee = nil; + Function_Id callee_id = 0; + bool is_indirect = false; if (subject_tree && subject_tree->kind == TREE_EXPRESSION_NAME) { - Function_Id fn_id; - if (lower_function_lookup_by_name(ctx->unit, subject_tree->value.name.name, &fn_id)) - callee = *array_at(struct Function*, &ctx->unit->functions.entries, fn_id); - } - - struct Expression* subject = lower_expression(ctx, subject_tree); + if (lower_function_lookup_by_name(ctx->unit, subject_tree->value.name.name, &callee_id)) + callee = *array_at(struct Function*, &ctx->unit->functions.entries, callee_id); + else if (lower_name_is_function_typed_local(ctx, subject_tree->value.name.name)) + is_indirect = true; + } else if (subject_tree) { + // any subject that isn't a bare name (member, subscript, call result, closure, anything + // that's not literally just a name) yields a fat closure value at the call site by default. + is_indirect = true; + } + + // for direct calls to a known catskill function, build the reference + // subject directly so we don't drag the function into value-context synthesis! + struct Expression* subject = + callee ? ir_make_function_ref(callee_id, 0, subject_tree->span) + : lower_expression(ctx, subject_tree); Array(struct Call_Argument) arguments; if (callee) { @@ -1126,7 +1166,7 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex } } - return ir_make_call(subject, arguments, tree_expr->span); + return ir_make_call(subject, arguments, is_indirect, tree_expr->span); } struct Expression* @@ -1181,9 +1221,8 @@ lower_expression_type(struct Lower_Context* ctx, struct Tree_Expression* tree_ex } // anonymous function: lift it up as a synthetic function table entry, -// replace the expression with a name reference. -// TODO: should we have a better kind of reference here than a name? -// maybe a function reference somehow? +// and emit a function reference so the use site can wrap it as a fat +// closure value. struct Expression* lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tree_expr) { @@ -1200,7 +1239,10 @@ lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tre fn->synthetic = true; lower_fill_function_signature(ctx, fn, tree_expr); - return ir_make_name(name, tree_expr->span); + // a function literal is always in value context, we can synthesize + // the closure type eagerly right here. + Type_Id closure_type_id = lower_function_closure_type(ctx, fn); + return ir_make_function_ref(id, closure_type_id, tree_expr->span); } struct Expression* @@ -1326,6 +1368,37 @@ lower_is_shadowing(struct Lower_Context* ctx, struct String name) return false; } +// look up a local by name across all active scopes, returning its type. +// returns false if no local with that name exists in scope. +bool +lower_local_lookup(struct Lower_Context* ctx, struct String name, struct Type_Ref* out_type) +{ + for (uint i = ctx->scope_stack.length; i > 0; --i) { + struct Scope* scope = array_at(struct Scope, &ctx->scope_stack, i - 1); + + FOR_EACH_ARRAY (struct Local_Variable, var, &scope->variables) { + if (string_equals(var->name, name)) { + if (out_type) *out_type = var->type; + return true; + } + } + } + + return false; +} + +// is the named local a function-typed value? +// (used to decide direct vs. indirect call dispatch when the call subject is a bare name.) +bool +lower_name_is_function_typed_local(struct Lower_Context* ctx, struct String name) +{ + struct Type_Ref ref; + if (!lower_local_lookup(ctx, name, &ref)) return false; + if (array_length(&ref.mods) > 0) return false; // references are not callable! + struct Type* type = *array_at(struct Type*, &ctx->unit->types.entries, ref.type_id); + return type->kind == TYPE_FUNCTION; +} + struct Statement* lower_statement_return(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt) { |
