From 9c3cae740664a5043bacfa0b82eace8ed8239bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:16:14 +0900 Subject: [PATCH 1/7] add M7 semantic foundation --- fec/src/m7.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 fec/src/m7.h diff --git a/fec/src/m7.h b/fec/src/m7.h new file mode 100644 index 0000000..ad0f968 --- /dev/null +++ b/fec/src/m7.h @@ -0,0 +1,31 @@ +#ifndef FE_M7_H +#define FE_M7_H + +#include "types.h" + +typedef enum FeM7ContextKind { + FE_M7_CONTEXT_NONE = 0, + FE_M7_CONTEXT_SUCCESS, + FE_M7_CONTEXT_FAILURE +} FeM7ContextKind; + +typedef enum FeM7LazyKind { + FE_M7_LAZY_NONE = 0, + FE_M7_LAZY_ORELSE, + FE_M7_LAZY_CATCH +} FeM7LazyKind; + +FeType *fe_m7_optional_type(FeTypeCtx *ctx, FeType *payload); +int fe_m7_optional_uses_niche(const FeType *payload); +int fe_m7_can_contextual_null(const FeType *expected); + +FeType *fe_m7_error_union_type(FeTypeCtx *ctx, FeType *error_type, + FeType *value_type); +FeType *fe_m7_error_type(FeTypeCtx *ctx, const FeType *error_union); +FeM7ContextKind fe_m7_error_context(FeTypeCtx *ctx, const FeType *expected, + const FeType *actual); + +FeM7LazyKind fe_m7_lazy_kind(const FeNode *node); +int fe_m7_is_try(const FeNode *node); + +#endif From c4f3accbb37016b5d880903e76c351736ab6743d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:16:30 +0900 Subject: [PATCH 2/7] implement M7 semantic foundation --- fec/src/m7.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 fec/src/m7.c diff --git a/fec/src/m7.c b/fec/src/m7.c new file mode 100644 index 0000000..a314fe3 --- /dev/null +++ b/fec/src/m7.c @@ -0,0 +1,114 @@ +#include "m7.h" +#include +#include + +static char *m7_generated_name(FeTypeCtx *ctx, const char *prefix) +{ + char number[24]; + char *p; + unsigned long n; + sprintf(number, "%u", ctx->generated_serial++); + n = (unsigned long)strlen(prefix) + (unsigned long)strlen(number) + 1UL; + p = (char *)fe_arena_alloc(ctx->arena, n); + if (!p) return 0; + strcpy(p, prefix); + strcat(p, number); + return p; +} + +int fe_m7_optional_uses_niche(const FeType *payload) +{ + if (!payload) return 0; + if (payload->kind == FE_TYPE_REF) return 1; + if (payload->kind == FE_TYPE_OWNED && + !(payload->elem && payload->elem->kind == FE_TYPE_SLICE)) + return 1; + return 0; +} + +FeType *fe_m7_optional_type(FeTypeCtx *ctx, FeType *payload) +{ + char key[128]; + FeType *t; + if (!ctx || !payload) return 0; + sprintf(key, "?%s", payload->name); + t = fe_type_intern(ctx, key); + if (!t) return 0; + if (t->kind == FE_TYPE_UNKNOWN) { + t->kind = FE_TYPE_OPTIONAL; + t->elem = payload; + if (!fe_m7_optional_uses_niche(payload)) { + t->cname = m7_generated_name(ctx, "struct fe_option_"); + t->maker = m7_generated_name(ctx, "fe_make_option_"); + } + } + return t; +} + +int fe_m7_can_contextual_null(const FeType *expected) +{ + return expected && expected->kind == FE_TYPE_OPTIONAL; +} + +FeType *fe_m7_error_union_type(FeTypeCtx *ctx, FeType *error_type, + FeType *value_type) +{ + char key[160]; + FeType *t; + if (!ctx || !value_type) return 0; + if (!error_type || strcmp(error_type->name, "core.Error") == 0) + return fe_type_error_union(ctx, value_type); + sprintf(key, "%s!%s", error_type->name, value_type->name); + t = fe_type_intern(ctx, key); + if (!t) return 0; + if (t->kind == FE_TYPE_UNKNOWN) { + t->kind = FE_TYPE_ERROR_UNION; + /* For FE_TYPE_ERROR_UNION, elem is the nominal error identity. + A null elem denotes the built-in core.Error shorthand !T. */ + t->elem = error_type; + t->error_value = value_type; + if (value_type->kind != FE_TYPE_VOID) { + t->cname = m7_generated_name(ctx, "struct fe_result_"); + t->maker = m7_generated_name(ctx, "fe_make_result_"); + t->alloc_cname = m7_generated_name(ctx, "fe_alloc_result_"); + } + } + return t; +} + +FeType *fe_m7_error_type(FeTypeCtx *ctx, const FeType *error_union) +{ + if (!ctx || !error_union || error_union->kind != FE_TYPE_ERROR_UNION) + return 0; + if (error_union->elem) return error_union->elem; + return fe_type_intern(ctx, "core.Error"); +} + +FeM7ContextKind fe_m7_error_context(FeTypeCtx *ctx, const FeType *expected, + const FeType *actual) +{ + FeType *error_type; + if (!ctx || !expected || !actual || + expected->kind != FE_TYPE_ERROR_UNION) + return FE_M7_CONTEXT_NONE; + if (expected->error_value && fe_type_equal(expected->error_value, actual)) + return FE_M7_CONTEXT_SUCCESS; + error_type = fe_m7_error_type(ctx, expected); + if (error_type && fe_type_equal(error_type, actual)) + return FE_M7_CONTEXT_FAILURE; + return FE_M7_CONTEXT_NONE; +} + +FeM7LazyKind fe_m7_lazy_kind(const FeNode *node) +{ + if (!node || !node->text) return FE_M7_LAZY_NONE; + if (strcmp(node->text, "orelse") == 0) return FE_M7_LAZY_ORELSE; + if (strcmp(node->text, "catch") == 0) return FE_M7_LAZY_CATCH; + return FE_M7_LAZY_NONE; +} + +int fe_m7_is_try(const FeNode *node) +{ + return node && node->kind == FE_N_UNARY && node->text && + strcmp(node->text, "try") == 0; +} From 40ec131ffc7f4ccea84c57162efe56221a3b0140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:16:46 +0900 Subject: [PATCH 3/7] add cleanup lowering plan --- fec/src/lower.h | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 fec/src/lower.h diff --git a/fec/src/lower.h b/fec/src/lower.h new file mode 100644 index 0000000..bb2e544 --- /dev/null +++ b/fec/src/lower.h @@ -0,0 +1,61 @@ +#ifndef FE_LOWER_H +#define FE_LOWER_H + +#include "types.h" + +#define FE_LOWER_NO_SCOPE ((unsigned)~0U) + +typedef enum FeLowerExitKind { + FE_LOWER_EXIT_FALLTHROUGH = 0, + FE_LOWER_EXIT_RETURN, + FE_LOWER_EXIT_ERROR_RETURN, + FE_LOWER_EXIT_BREAK, + FE_LOWER_EXIT_CONTINUE +} FeLowerExitKind; + +typedef enum FeLowerCleanupKind { + FE_LOWER_CLEANUP_DROP = 0, + FE_LOWER_CLEANUP_DEFER +} FeLowerCleanupKind; + +typedef struct FeLowerScope { + FeNode *block; + unsigned parent; + unsigned ordinal; +} FeLowerScope; + +typedef struct FeLowerCleanup { + FeLowerCleanupKind kind; + unsigned scope; + unsigned ordinal; + FeNode *node; + FeNode *decl; + FeType *type; +} FeLowerCleanup; + +typedef struct FeLowerPlan { + FeArena *arena; + FeNode *fn; + FeLowerScope *scopes; + unsigned scope_count; + unsigned scope_capacity; + FeLowerCleanup *cleanups; + unsigned cleanup_count; + unsigned cleanup_capacity; + unsigned next_ordinal; +} FeLowerPlan; + +void fe_lower_plan_init(FeLowerPlan *plan, FeArena *arena); +int fe_lower_plan_build(FeLowerPlan *plan, FeNode *fn); +unsigned fe_lower_scope_for_block(const FeLowerPlan *plan, + const FeNode *block); +unsigned fe_lower_collect_cleanups(const FeLowerPlan *plan, + const FeNode *from_block, + const FeNode *stop_block, + const FeLowerCleanup **out, + unsigned out_capacity); +int fe_lower_type_needs_drop(const FeType *type); +int fe_lower_exit_runs_cleanup(FeLowerExitKind kind); +int fe_lower_exit_leaves_function(FeLowerExitKind kind); + +#endif From 8aae7d9a6317d8a9e8adda96b35c6c20e6280d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:17:15 +0900 Subject: [PATCH 4/7] implement cleanup lowering plan --- fec/src/lower.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 fec/src/lower.c diff --git a/fec/src/lower.c b/fec/src/lower.c new file mode 100644 index 0000000..abd6942 --- /dev/null +++ b/fec/src/lower.c @@ -0,0 +1,227 @@ +#include "lower.h" +#include + +static int lower_grow_scopes(FeLowerPlan *plan) +{ + FeLowerScope *items; + unsigned capacity; + if (plan->scope_count < plan->scope_capacity) return 1; + capacity = plan->scope_capacity ? plan->scope_capacity * 2U : 8U; + items = (FeLowerScope *)fe_arena_alloc(plan->arena, + capacity * sizeof(FeLowerScope)); + if (!items) return 0; + if (plan->scopes) + memcpy(items, plan->scopes, + plan->scope_count * sizeof(FeLowerScope)); + plan->scopes = items; + plan->scope_capacity = capacity; + return 1; +} + +static int lower_grow_cleanups(FeLowerPlan *plan) +{ + FeLowerCleanup *items; + unsigned capacity; + if (plan->cleanup_count < plan->cleanup_capacity) return 1; + capacity = plan->cleanup_capacity ? plan->cleanup_capacity * 2U : 16U; + items = (FeLowerCleanup *)fe_arena_alloc(plan->arena, + capacity * sizeof(FeLowerCleanup)); + if (!items) return 0; + if (plan->cleanups) + memcpy(items, plan->cleanups, + plan->cleanup_count * sizeof(FeLowerCleanup)); + plan->cleanups = items; + plan->cleanup_capacity = capacity; + return 1; +} + +static unsigned lower_add_scope(FeLowerPlan *plan, FeNode *block, + unsigned parent) +{ + FeLowerScope *scope; + unsigned index; + if (!lower_grow_scopes(plan)) return FE_LOWER_NO_SCOPE; + index = plan->scope_count++; + scope = &plan->scopes[index]; + scope->block = block; + scope->parent = parent; + scope->ordinal = plan->next_ordinal++; + return index; +} + +static int lower_add_cleanup(FeLowerPlan *plan, unsigned scope, + FeLowerCleanupKind kind, FeNode *node, + FeNode *decl, FeType *type) +{ + FeLowerCleanup *cleanup; + if (!lower_grow_cleanups(plan)) return 0; + cleanup = &plan->cleanups[plan->cleanup_count++]; + cleanup->kind = kind; + cleanup->scope = scope; + cleanup->ordinal = plan->next_ordinal++; + cleanup->node = node; + cleanup->decl = decl; + cleanup->type = type; + return 1; +} + +int fe_lower_type_needs_drop(const FeType *type) +{ + unsigned i; + unsigned j; + if (!type) return 0; + if (type->kind == FE_TYPE_OWNED) return 1; + if (type->kind == FE_TYPE_OPTIONAL) + return fe_lower_type_needs_drop(type->elem); + if (type->kind == FE_TYPE_ERROR_UNION) + return fe_lower_type_needs_drop(type->error_value); + if (type->kind == FE_TYPE_ARRAY) + return fe_lower_type_needs_drop(type->elem); + if (type->kind == FE_TYPE_STRUCT) { + if (type->has_drop) return 1; + for (i = 0; i < type->field_count; ++i) + if (fe_lower_type_needs_drop(type->fields[i].type)) return 1; + return 0; + } + if (type->kind == FE_TYPE_ENUM) { + for (i = 0; i < type->variant_count; ++i) + for (j = 0; j < type->variants[i].field_count; ++j) + if (fe_lower_type_needs_drop(type->variants[i].fields[j].type)) + return 1; + } + return 0; +} + +static int lower_build_node(FeLowerPlan *plan, FeNode *node, + unsigned scope); + +static int lower_build_list(FeLowerPlan *plan, FeNode *node, + unsigned scope) +{ + while (node) { + if (!lower_build_node(plan, node, scope)) return 0; + node = node->next; + } + return 1; +} + +static int lower_build_block(FeLowerPlan *plan, FeNode *block, + unsigned parent) +{ + unsigned scope; + if (!block || block->kind != FE_N_BLOCK) return 1; + scope = lower_add_scope(plan, block, parent); + if (scope == FE_LOWER_NO_SCOPE) return 0; + return lower_build_list(plan, block->children, scope); +} + +static int lower_build_node(FeLowerPlan *plan, FeNode *node, + unsigned scope) +{ + FeNode *child; + FeType *type; + if (!node) return 1; + if (node->kind == FE_N_BLOCK) + return lower_build_block(plan, node, scope); + if (node->kind == FE_N_DEFER) { + if (!lower_add_cleanup(plan, scope, FE_LOWER_CLEANUP_DEFER, + node->a, node, 0)) + return 0; + return lower_build_node(plan, node->a, scope); + } + if (node->kind == FE_N_LET || node->kind == FE_N_VAR || + node->kind == FE_N_CONST) { + type = node->sem_type; + if (fe_lower_type_needs_drop(type)) + if (!lower_add_cleanup(plan, scope, FE_LOWER_CLEANUP_DROP, + node, node, type)) + return 0; + } + if (!lower_build_node(plan, node->a, scope)) return 0; + if (!lower_build_node(plan, node->b, scope)) return 0; + if (!lower_build_node(plan, node->c, scope)) return 0; + for (child = node->children; child; child = child->next) + if (!lower_build_node(plan, child, scope)) return 0; + return 1; +} + +void fe_lower_plan_init(FeLowerPlan *plan, FeArena *arena) +{ + if (!plan) return; + plan->arena = arena; + plan->fn = 0; + plan->scopes = 0; + plan->scope_count = 0; + plan->scope_capacity = 0; + plan->cleanups = 0; + plan->cleanup_count = 0; + plan->cleanup_capacity = 0; + plan->next_ordinal = 0; +} + +int fe_lower_plan_build(FeLowerPlan *plan, FeNode *fn) +{ + if (!plan || !plan->arena || !fn || fn->kind != FE_N_FN) return 0; + plan->fn = fn; + plan->scopes = 0; + plan->scope_count = 0; + plan->scope_capacity = 0; + plan->cleanups = 0; + plan->cleanup_count = 0; + plan->cleanup_capacity = 0; + plan->next_ordinal = 0; + if (!fn->c) return 1; + return lower_build_block(plan, fn->c, FE_LOWER_NO_SCOPE); +} + +unsigned fe_lower_scope_for_block(const FeLowerPlan *plan, + const FeNode *block) +{ + unsigned i; + if (!plan || !block) return FE_LOWER_NO_SCOPE; + for (i = 0; i < plan->scope_count; ++i) + if (plan->scopes[i].block == block) return i; + return FE_LOWER_NO_SCOPE; +} + +unsigned fe_lower_collect_cleanups(const FeLowerPlan *plan, + const FeNode *from_block, + const FeNode *stop_block, + const FeLowerCleanup **out, + unsigned out_capacity) +{ + unsigned scope; + unsigned stop; + unsigned i; + unsigned count; + if (!plan || !from_block) return 0; + scope = fe_lower_scope_for_block(plan, from_block); + stop = stop_block ? fe_lower_scope_for_block(plan, stop_block) : + FE_LOWER_NO_SCOPE; + count = 0; + while (scope != FE_LOWER_NO_SCOPE && scope != stop) { + for (i = plan->cleanup_count; i > 0; --i) { + if (plan->cleanups[i - 1U].scope != scope) continue; + if (out && count < out_capacity) + out[count] = &plan->cleanups[i - 1U]; + ++count; + } + scope = plan->scopes[scope].parent; + } + return count; +} + +int fe_lower_exit_runs_cleanup(FeLowerExitKind kind) +{ + return kind == FE_LOWER_EXIT_FALLTHROUGH || + kind == FE_LOWER_EXIT_RETURN || + kind == FE_LOWER_EXIT_ERROR_RETURN || + kind == FE_LOWER_EXIT_BREAK || + kind == FE_LOWER_EXIT_CONTINUE; +} + +int fe_lower_exit_leaves_function(FeLowerExitKind kind) +{ + return kind == FE_LOWER_EXIT_RETURN || + kind == FE_LOWER_EXIT_ERROR_RETURN; +} From 47ef8420a93bca114cabfe22f0c13ceebf48d964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:17:33 +0900 Subject: [PATCH 5/7] add optional type kind --- fec/src/types.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fec/src/types.h b/fec/src/types.h index 1974a46..5e1c984 100644 --- a/fec/src/types.h +++ b/fec/src/types.h @@ -6,7 +6,7 @@ typedef enum FeTypeKind { FE_TYPE_ERROR, FE_TYPE_ERROR_UNION, FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_CHAR, FE_TYPE_INT, FE_TYPE_STRUCT, FE_TYPE_ENUM, FE_TYPE_ARRAY, FE_TYPE_SLICE, FE_TYPE_STR, - FE_TYPE_REF, FE_TYPE_OWNED, FE_TYPE_UNKNOWN + FE_TYPE_REF, FE_TYPE_OWNED, FE_TYPE_OPTIONAL, FE_TYPE_UNKNOWN } FeTypeKind; typedef struct FeFieldType FeFieldType; @@ -49,7 +49,9 @@ struct FeType { unsigned long size; unsigned align; FeType *elem; - /* Success value for an error union; !void is represented directly. */ + /* Success value for an error union; !void is represented directly. + For a nominal E!T created by M7, elem holds E. A null elem denotes + the built-in core.Error shorthand !T. */ FeType *error_value; int ref_mut; FeFieldType *fields; From bc9792532d150fe0a2472e22e86dca2ae88ec6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:17:45 +0900 Subject: [PATCH 6/7] wire M7 foundation into host build --- fec/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fec/Makefile b/fec/Makefile index ae23bb3..a6eb4c5 100644 --- a/fec/Makefile +++ b/fec/Makefile @@ -1,7 +1,7 @@ CC ?= cc CFLAGS ?= -O2 -Wall -Wextra -std=c89 CPPFLAGS ?= -Isrc -SRC = src/arena.c src/diag.c src/lexer.c src/ast.c src/parser.c src/types.c src/own.c src/check.c src/emit_c.c src/driver.c +SRC = src/arena.c src/diag.c src/lexer.c src/ast.c src/parser.c src/types.c src/m7.c src/own.c src/check.c src/lower.c src/emit_c.c src/driver.c OBJ = $(SRC:.c=.o) .PHONY: all clean dos-build From d43f28d06ae8bc9b1c254c4204083dc2e0ef6133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=8B=9C=EC=9B=90?= Date: Sun, 16 Aug 2026 21:17:57 +0900 Subject: [PATCH 7/7] wire M7 foundation into DOS build --- fec/build-dos.bat | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fec/build-dos.bat b/fec/build-dos.bat index fbb7e5c..02425ec 100644 --- a/fec/build-dos.bat +++ b/fec/build-dos.bat @@ -26,10 +26,14 @@ wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=parser.obj src\parser.c if errorlevel 1 goto build_fail wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=types.obj src\types.c if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=m7.obj src\m7.c +if errorlevel 1 goto build_fail wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=own.obj src\own.c if errorlevel 1 goto build_fail wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=check.obj src\check.c if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=lower.obj src\lower.c +if errorlevel 1 goto build_fail rem Use an unambiguous short object name for the emit_c source. wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=emitc.obj src\emit_c.c if errorlevel 1 goto build_fail