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/transpile.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/transpile.c')
| -rw-r--r-- | boot/transpile.c | 188 |
1 files changed, 160 insertions, 28 deletions
diff --git a/boot/transpile.c b/boot/transpile.c index 9c94706..9b8f420 100644 --- a/boot/transpile.c +++ b/boot/transpile.c @@ -146,7 +146,9 @@ transpile_visit_type_ref(struct Visit* visit, struct Type_Ref* ref) if (primitive) { TRANSPILE_WRITE("%s", primitive); - } else if (target && (target->kind == TYPE_STRUCTURE || target->kind == TYPE_VARIANT)) { + } else if (target + && (target->kind == TYPE_STRUCTURE || target->kind == TYPE_VARIANT + || target->kind == TYPE_FUNCTION)) { TRANSPILE_WRITE("struct %s", string_c_str(target->name)); } else if (target) { TRANSPILE_WRITE("%s", string_c_str(target->name)); @@ -174,27 +176,23 @@ transpile_visit_type_forward_variant(struct Visit* visit, struct Type* type) TRANSPILE_WRITE("struct %s;\n", string_c_str(type->name)); } +// emit the fat closure struct that represents any function-typed value. +// bare (normal) functions wrap into this via a thunk with a nil state. void transpile_visit_type_forward_function(struct Visit* visit, struct Type* type) { TRANSPILE_PREAMBLE - - // referenced function types are emitted as a typedef - TRANSPILE_WRITE("typedef "); + TRANSPILE_WRITE("struct %s {\n", string_c_str(type->name)); + TRANSPILE_WRITE(" void* state;\n"); + TRANSPILE_WRITE(" "); VISIT(visit_type_ref, &type->value.function.return_type); - TRANSPILE_WRITE(" (*%s)(", string_c_str(type->name)); - if (array_length(&type->value.function.params) == 0) { - TRANSPILE_WRITE("void"); - } else { - bool first = true; - FOR_EACH_ARRAY (struct Type_Ref, param, &type->value.function.params) { - if (!first) TRANSPILE_WRITE(", "); - VISIT(visit_type_ref, param); - first = false; - } - if (type->value.function.variadic) TRANSPILE_WRITE(", ..."); + TRANSPILE_WRITE(" (*call)(void* state"); + FOR_EACH_ARRAY (struct Type_Ref, param, &type->value.function.params) { + TRANSPILE_WRITE(", "); + VISIT(visit_type_ref, param); } - TRANSPILE_WRITE(");\n"); + if (type->value.function.variadic) TRANSPILE_WRITE(", ..."); + TRANSPILE_WRITE(");\n};\n"); } void @@ -280,6 +278,52 @@ transpile_function_c_name(struct Function* function) return string_c_str(function->name); } +// the c-side name of the thunk that wraps a function as a fat closure value. +struct String +transpile_function_thunk_name(struct Function* function) +{ + struct String_Buffer buf = string_buffer_new(64); + string_buffer_append_c_str(&buf, "__cat_thunk_"); + string_buffer_append_c_str(&buf, transpile_function_c_name(function)); + return string_buffer_to_string(&buf); +} + +// emit thunk wrapper for a normal function, allowing them to be used +// like closures. +void +transpile_function_emit_thunk(struct Visit* visit, struct Function* function) +{ + TRANSPILE_PREAMBLE + + VISIT(visit_type_ref, &function->return_type); + struct String thunk_name = transpile_function_thunk_name(function); + TRANSPILE_WRITE(" %s(void* state", string_c_str(thunk_name)); + FOR_EACH_ARRAY (struct Param, param, &function->params) { + TRANSPILE_WRITE(", "); + VISIT(visit_type_ref, ¶m->type); + TRANSPILE_WRITE(" %s", string_c_str(param->name)); + } + if (function->variadic) TRANSPILE_WRITE(", ..."); + TRANSPILE_WRITE(") {\n (void)state;\n "); + + // non-returning functions can't return a value, crazy! + struct Type* return_type = transpile_type_at(transpiler, function->return_type.type_id); + bool returns_void = + return_type && return_type->id == transpiler->unit->types.primitive_void_id + && array_length(&function->return_type.mods) == 0; + if (!returns_void) TRANSPILE_WRITE("return "); + TRANSPILE_WRITE("%s(", transpile_function_c_name(function)); + bool first = true; + FOR_EACH_ARRAY (struct Param, param, &function->params) { + if (!first) TRANSPILE_WRITE(", "); + TRANSPILE_WRITE("%s", string_c_str(param->name)); + first = false; + } + TRANSPILE_WRITE(");\n}\n"); +} + +void transpile_emit_indent(struct Transpiler* transpiler); + // emits c-side format of a function header. // shared by the signature and body passes. void @@ -289,15 +333,21 @@ transpile_function_emit_signature(struct Visit* visit, struct Function* function VISIT(visit_type_ref, &function->return_type); TRANSPILE_WRITE(" %s(", transpile_function_c_name(function)); - if (array_length(&function->params) == 0) { + bool need_separator = false; + if (function->synthetic) { + // synthetic functions implicitly take in state + TRANSPILE_WRITE("void* state"); + need_separator = true; + } + + if (array_length(&function->params) == 0 && !need_separator) { TRANSPILE_WRITE("void"); } else { - bool first = true; FOR_EACH_ARRAY (struct Param, param, &function->params) { - if (!first) TRANSPILE_WRITE(", "); + if (need_separator) TRANSPILE_WRITE(", "); VISIT(visit_type_ref, ¶m->type); TRANSPILE_WRITE(" %s", string_c_str(param->name)); - first = false; + need_separator = true; } if (function->variadic) TRANSPILE_WRITE(", ..."); } @@ -310,6 +360,19 @@ transpile_visit_function_signature(struct Visit* visit, struct Function* functio TRANSPILE_PREAMBLE transpile_function_emit_signature(visit, function); TRANSPILE_WRITE(";\n"); + + // a thunk only exists for thin, named functions referenced as values! + if (function->synthetic || function->closure_type_id == 0) return; + + VISIT(visit_type_ref, &function->return_type); + struct String thunk_name = transpile_function_thunk_name(function); + TRANSPILE_WRITE(" %s(void* state", string_c_str(thunk_name)); + FOR_EACH_ARRAY (struct Param, param, &function->params) { + TRANSPILE_WRITE(", "); + VISIT(visit_type_ref, ¶m->type); + } + if (function->variadic) TRANSPILE_WRITE(", ..."); + TRANSPILE_WRITE(");\n"); } void @@ -317,13 +380,32 @@ transpile_visit_function_body(struct Visit* visit, struct Function* function) { TRANSPILE_PREAMBLE transpile_function_emit_signature(visit, function); - TRANSPILE_WRITE(" "); - if (function->body) { - VISIT(visit_block, function->body); - } else { - TRANSPILE_WRITE("{\n}"); + + if (!function->body) { + TRANSPILE_WRITE(" {\n}\n"); + return; } + + // add a void cast for the implicit state argument, in case synthetic + // function does not need to use it + if (function->synthetic) { + TRANSPILE_WRITE(" {\n"); + transpiler->indent_level++; + transpile_emit_indent(transpiler); + TRANSPILE_WRITE("(void)state;\n"); + FOR_EACH_ARRAY (struct Statement*, statement, &function->body->statements) + VISIT(visit_statement, *statement); + transpiler->indent_level--; + transpile_emit_indent(transpiler); + TRANSPILE_WRITE("}\n"); + return; + } + + TRANSPILE_WRITE(" "); + VISIT(visit_block, function->body); TRANSPILE_WRITE("\n"); + + if (function->closure_type_id != 0) transpile_function_emit_thunk(visit, function); } void @@ -448,14 +530,33 @@ transpile_visit_expression_call(struct Visit* visit, struct Expression* expressi { TRANSPILE_PREAMBLE - transpile_emit_expression(visit, expression->value.call.subject, TRANSPILE_PRECEDENCE_POSTFIX); - TRANSPILE_WRITE("("); + struct Expression* subject = expression->value.call.subject; + bool is_indirect = expression->value.call.is_indirect; + + if (!is_indirect && subject && subject->kind == EXPRESSION_FUNCTION_REF) { + // direct calls emit the subject bare + struct Function* fn = *array_at( + struct Function*, &transpiler->unit->functions.entries, + subject->value.function_ref.function_id); + TRANSPILE_WRITE("%s", transpile_function_c_name(fn)); + } else if (is_indirect) { + // route through the fat closure value's call slot, passing state + transpile_emit_expression(visit, subject, TRANSPILE_PRECEDENCE_POSTFIX); + TRANSPILE_WRITE(".call("); + transpile_emit_expression(visit, subject, TRANSPILE_PRECEDENCE_POSTFIX); + TRANSPILE_WRITE(".state"); + } else { + transpile_emit_expression(visit, subject, TRANSPILE_PRECEDENCE_POSTFIX); + } + + if (!is_indirect) TRANSPILE_WRITE("("); + Array(struct Call_Argument) args = expression->value.call.arguments; uint max_slot = 0; FOR_EACH_ARRAY (struct Call_Argument, a, &args) { if (a->slot > max_slot) max_slot = a->slot; } - bool first = true; + bool first = !is_indirect; for (uint s = 0; s <= max_slot; ++s) { FOR_EACH_ARRAY (struct Call_Argument, a, &args) { if (a->slot != s) continue; @@ -519,6 +620,36 @@ transpile_visit_expression_construct(struct Visit* visit, struct Expression* exp } void +transpile_visit_expression_function_ref(struct Visit* visit, struct Expression* expression) +{ + TRANSPILE_PREAMBLE + + struct Function* fn = *array_at( + struct Function*, &transpiler->unit->functions.entries, + expression->value.function_ref.function_id); + + // we only get here when the function reference is explicitly a *value*, + // any direct calls are filtered out earlier. + struct Type* closure_type = + transpile_type_at(transpiler, expression->value.function_ref.closure_type_id); + + const ascii* call_name; + struct String thunk_name = string_empty(); + if (fn->synthetic) { + // synthetic functions are already fat-by-default, can call directly + call_name = transpile_function_c_name(fn); + } else { + // thin named functions go through their generated thunk to match the + // fat calling convention + thunk_name = transpile_function_thunk_name(fn); + call_name = string_c_str(thunk_name); + } + + TRANSPILE_WRITE( + "(struct %s){ .state = nil, .call = %s }", string_c_str(closure_type->name), call_name); +} + +void transpile_visit_expression_increment_decrement(struct Visit* visit, struct Expression* expression) { TRANSPILE_PREAMBLE @@ -718,6 +849,7 @@ struct Visit_Table transpile_body_visit_functions = { .visit_expression_cast = transpile_visit_expression_cast, .visit_expression_construct = transpile_visit_expression_construct, .visit_expression_increment_decrement = transpile_visit_expression_increment_decrement, + .visit_expression_function_ref = transpile_visit_expression_function_ref, }; // walk a lowered translation unit and emit c source into the transpiler's output. |
