From f88559c6355b7333531915d6b0cd7b10c64f75d9 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 16:54:41 +0900 Subject: [PATCH] wip: extend M5 ownership tests and cleanup emission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 조건부 이동, 이중 destroy, 직접 drop 호출에 대한 실패 fixture를 추가하고 runtime harness와 test-dos.bat를 그에 맞춰 갱신한다. M5는 아직 완료가 아니다. 모든 경로에서 정확히 1회 cleanup, defer/drop의 선언 역순 병합, try 전파 경로 cleanup, MaybeMoved 런타임 live flag, struct drop과 필드 역순 drop, 분기/루프 상태 합류, 누수/이중해제 카운터 harness가 남아 있다. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012PQm6oAvWX4Lp3iSN5AHGT --- fec/src/check.c | 5 +++- fec/src/emit_c.c | 21 ++++++++++++++ fec/src/emit_c.h | 1 + fec/test-dos.bat | 50 ++++++++++++++++++--------------- fec/tests/m5/bad-conditional.fe | 8 ++++++ fec/tests/m5/bad-double.fe | 6 ++++ fec/tests/m5/bad-drop.fe | 11 ++++++++ fec/tests/m5/runtime.c | 43 ++++++++++++++++++++++++++++ fec/tests/m5/runtime.fe | 20 +++++++++---- 9 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 fec/tests/m5/bad-conditional.fe create mode 100644 fec/tests/m5/bad-double.fe create mode 100644 fec/tests/m5/bad-drop.fe diff --git a/fec/src/check.c b/fec/src/check.c index 28d95d9..912936c 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -79,7 +79,10 @@ static void mark_moved(FeCheckerState *s, FeNode *n, FeType *t) if (sym->decl) sym->decl->flags |= 0x200U; } else { sym->moved=1; - if (sym->decl) sym->decl->flags |= 0x100U; + /* Mark this consuming expression, not the declaration. Branches + may move conditionally; the declaration's runtime live flag + must remain available to guard cleanup on the other path. */ + n->flags |= 0x100U; } } } diff --git a/fec/src/emit_c.c b/fec/src/emit_c.c index ecba29f..102a2c5 100644 --- a/fec/src/emit_c.c +++ b/fec/src/emit_c.c @@ -932,9 +932,17 @@ static void emit_cleanup_to(FeEmitter *e, unsigned floor) for (i=e->block_depth; i>floor; --i) emit_cleanup_block(e,e->block_stack[i-1]); } +static void emit_param_cleanup(FeEmitter *e) +{ + FeNode *p; + if (!e->current_fn || !e->current_fn->a) return; + for (p=e->current_fn->a->children; p; p=p->next) emit_value_drop(e,p); +} + static void emit_cleanup_all(FeEmitter *e) { emit_cleanup_to(e,0); + emit_param_cleanup(e); } static void emit_error_return(FeEmitter *e, const char *error_expr) @@ -974,6 +982,14 @@ static void emit_block(FeEmitter *e, FeNode *n) for (x = n->children; x; x = x->next) if (x->kind == FE_N_LET || x->kind == FE_N_VAR || x->kind == FE_N_CONST) emit_decl(e, x); + if (e->current_fn && e->current_fn->c==n && e->current_fn->a) { + FeNode *param; + for (param=e->current_fn->a->children; param; param=param->next) + if (param->sem_type && param->sem_type->kind==FE_TYPE_OWNED) { + pad(e); fputs("unsigned char fe_live_",e->out); + fputs(cname(param,"owned"),e->out); fputs("=1;\n",e->out); + } + } if (e->current_ret && e->current_ret->kind!=FE_TYPE_VOID) { pad(e); fputs(fe_type_c_name(e->current_ret,e->pointer_bits),e->out); fputs(" fe_return_value;\n",e->out); @@ -988,6 +1004,7 @@ static void emit_block(FeEmitter *e, FeNode *n) } --e->indent; emit_cleanup_block(e,n); + if (e->current_fn && e->current_fn->c==n) emit_param_cleanup(e); if (e->block_depth) --e->block_depth; if (e->fallthrough_block==n) { pad(e); @@ -1246,7 +1263,9 @@ static void emit_fn(FeEmitter *e, FeNode *fn, int prototype) if (prototype) fputs(";\n", e->out); else { FeType *old_ret=e->current_ret; + FeNode *old_fn=e->current_fn; e->current_ret=fn->sem_type; + e->current_fn=fn; fputs(" ", e->out); if (fn->sem_type && fn->sem_type->kind==FE_TYPE_ERROR_UNION && fn->sem_type->error_value && @@ -1254,6 +1273,7 @@ static void emit_fn(FeEmitter *e, FeNode *fn, int prototype) e->fallthrough_block=fn->c; emit_block(e, fn->c); e->current_ret=old_ret; + e->current_fn=old_fn; fputc('\n', e->out); } } @@ -1285,6 +1305,7 @@ void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, e->block_depth = 0; e->loop_depth = 0; e->current_ret = 0; + e->current_fn = 0; } void fe_emit_c_program(FeEmitter *e) diff --git a/fec/src/emit_c.h b/fec/src/emit_c.h index 93a9bca..10634a8 100644 --- a/fec/src/emit_c.h +++ b/fec/src/emit_c.h @@ -17,6 +17,7 @@ typedef struct FeEmitter { unsigned loop_floor[16]; unsigned loop_depth; FeType *current_ret; + FeNode *current_fn; } FeEmitter; void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, diff --git a/fec/test-dos.bat b/fec/test-dos.bat index 5418842..70026fb 100644 --- a/fec/test-dos.bat +++ b/fec/test-dos.bat @@ -15,9 +15,9 @@ fec.exe --dump-ast TESTS\PASS\BASIC.FE > nul if errorlevel 1 goto test_fail fec.exe --dump-ast TESTS\PASS\LITERALS.FE > nul if errorlevel 1 goto test_fail -fec.exe --dump-ast TESTS\PASS\KEYWOR.FE > nul +fec.exe --dump-ast TESTS\PASS\KEYWORDS-AND-BUILTINS.FE > nul if errorlevel 1 goto test_fail -fec.exe --dump-ast TESTS\PASS\V012-F.FE > nul +fec.exe --dump-ast TESTS\PASS\V012-FORMS.FE > nul if errorlevel 1 goto test_fail fec.exe --dump-ast STD\CORE.FE > nul @@ -37,11 +37,11 @@ if errorlevel 1 goto test_fail fec.exe --dump-ast STD\SYS.FE > nul if errorlevel 1 goto test_fail -fec.exe --dump-ast TESTS\FAIL\MISSIN.FE > nul +fec.exe --dump-ast TESTS\FAIL\MISSING-SEMI.FE > nul if not errorlevel 1 goto test_fail -fec.exe --dump-ast TESTS\FAIL\UNCLOS.FE > nul +fec.exe --dump-ast TESTS\FAIL\UNCLOSED-COMMENT.FE > nul if not errorlevel 1 goto test_fail -fec.exe --dump-ast TESTS\FAIL\LOGICA.FE > nul +fec.exe --dump-ast TESTS\FAIL\LOGICAL-SYMBOLS.FE > nul if not errorlevel 1 goto test_fail if exist TESTS\M2\HELLO.C del TESTS\M2\HELLO.C @@ -67,30 +67,30 @@ TESTS\M2\SCOPES.EXE if errorlevel 1 goto test_fail rem M2 bits16 regression path remains on compiler A (wcl). -fec.exe --target=bits16 --emit-c TESTS\M2\CAST-W.FE -o TESTS\M2\CAST16.C > nul +fec.exe --target=bits16 --emit-c TESTS\M2\CAST-WHILE.FE -o TESTS\M2\CAST16.C > nul if errorlevel 1 goto test_fail wcl -q -za -bt=dos -fe=TESTS\M2\CAST16.EXE TESTS\M2\CAST16.C if errorlevel 1 goto test_fail TESTS\M2\CAST16.EXE if errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-CO.FE -o TESTS\M2\BAD-CO.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-CONDITION.FE -o TESTS\M2\BAD-CO.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-CA.FE -o TESTS\M2\BAD-CA.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-CAST.FE -o TESTS\M2\BAD-CA.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-AS.FE -o TESTS\M2\BAD-AS.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-ASSIGN.FE -o TESTS\M2\BAD-AS.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-UN.FE -o TESTS\M2\BAD-UN.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-UNKNOWN.FE -o TESTS\M2\BAD-UN.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-AR.FE -o TESTS\M2\BAD-AR.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-ARITY.FE -o TESTS\M2\BAD-AR.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-TY.FE -o TESTS\M2\BAD-TY.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-TYPES.FE -o TESTS\M2\BAD-TY.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-RE.FE -o TESTS\M2\BAD-RE.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-RETURN.FE -o TESTS\M2\BAD-RE.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-UI.FE -o TESTS\M2\BAD-UI.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-UNINIT.FE -o TESTS\M2\BAD-UI.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M2\BAD-VO.FE -o TESTS\M2\BAD-VO.C > nul +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-VOID.FE -o TESTS\M2\BAD-VO.C > nul if not errorlevel 1 goto test_fail if exist TESTS\M3\STRUCT.C del TESTS\M3\STRUCT.C @@ -197,7 +197,7 @@ wcl386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\M4\FORMAT.EXE TESTS\M4\FORMAT.C if errorlevel 1 goto test_fail TESTS\M4\FORMAT.EXE > nul if errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M4\TRY-FPR.FE -o TESTS\M4\TRY-FPR.C > nul +fec.exe --target=bits32 --emit-c TESTS\M4\TRY-FPRINT.FE -o TESTS\M4\TRY-FPR.C > nul if errorlevel 1 goto test_fail wcl386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\M4\TRY-FPR.EXE TESTS\M4\TRY-FPR.C if errorlevel 1 goto test_fail @@ -209,17 +209,17 @@ wcl386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\M4\PROP.EXE TESTS\M4\PROPTEST.C if errorlevel 1 goto test_fail TESTS\M4\PROP.EXE > nul if errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M4\BAD-ARI.FE -o TESTS\M4\BAD-ARI.C > nul +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-ARITY.FE -o TESTS\M4\BAD-ARI.C > nul if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M4\BAD-VERB.FE -o TESTS\M4\BAD-VERB.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M4\BAD-RUN.FE -o TESTS\M4\BAD-RUN.C > nul +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-RUNTIME.FE -o TESTS\M4\BAD-RUN.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M4\BAD-TYP.FE -o TESTS\M4\BAD-TYP.C > nul +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-TYPE.FE -o TESTS\M4\BAD-TYP.C > nul if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M4\BAD-TRY.FE -o TESTS\M4\BAD-TRY.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M4\BAD-WRI.FE -o TESTS\M4\BAD-WRI.C > nul +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-WRITER.FE -o TESTS\M4\BAD-WRI.C > nul if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M4\BAD-MANY.FE -o TESTS\M4\BAD-MANY.C > nul if not errorlevel 1 goto test_fail @@ -233,7 +233,13 @@ fec.exe --target=bits32 --emit-c TESTS\M5\OWNED.FE -o TESTS\M5\OWNED.C > nul if errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M5\BAD-MOVE.FE -o TESTS\M5\BAD-MOVE.C > nul if not errorlevel 1 goto test_fail -fec.exe --target=bits32 --emit-c TESTS\M5\BAD-DES.FE -o TESTS\M5\BAD-DES.C > nul +fec.exe --target=bits32 --emit-c TESTS\M5\BAD-DESTROY.FE -o TESTS\M5\BAD-DES.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M5\BAD-DROP.FE -o TESTS\M5\BAD-DROP.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M5\BAD-DOUBLE.FE -o TESTS\M5\BAD-DBL.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M5\BAD-CONDITIONAL.FE -o TESTS\M5\BAD-COND.C > nul if not errorlevel 1 goto test_fail if exist TESTS\M5\RUNTIME-G.C del TESTS\M5\RUNTIME-G.C if exist TESTS\M5\RUNTIME.O del TESTS\M5\RUNTIME.O @@ -242,7 +248,7 @@ fec.exe --target=bits32 --emit-c TESTS\M5\RUNTIME.FE -o TESTS\M5\RUNTIME-G.C > n if errorlevel 1 goto test_fail rem Compile generated source and the C89 runtime harness in one WCL386 invocation rem so both objects use the same DOS/4GW startup and runtime library. -wcl386 -q -za -bt=dos -fe=TESTS\M5\RUNTIME.EXE TESTS\M5\RUNTIME-G.C TESTS\M5\RUNTIME.C +wcl386 -q -za -bt=dos -dmalloc=m5_malloc -dfree=m5_free -fe=TESTS\M5\RUNTIME.EXE TESTS\M5\RUNTIME-G.C TESTS\M5\RUNTIME.C if errorlevel 1 goto test_fail TESTS\M5\RUNTIME.EXE if errorlevel 1 goto test_fail diff --git a/fec/tests/m5/bad-conditional.fe b/fec/tests/m5/bad-conditional.fe new file mode 100644 index 0000000..90582bc --- /dev/null +++ b/fec/tests/m5/bad-conditional.fe @@ -0,0 +1,8 @@ +unit m5_bad_conditional; + +fn take(p: ^i32) -> void { mem.destroy(p); } + +fn bad(p: ^i32, flag: bool) -> void { + if flag { take(p); } + p.^ = 3; +} diff --git a/fec/tests/m5/bad-double.fe b/fec/tests/m5/bad-double.fe new file mode 100644 index 0000000..32f4b2b --- /dev/null +++ b/fec/tests/m5/bad-double.fe @@ -0,0 +1,6 @@ +unit m5_bad_double; + +fn bad(p: ^i32) -> void { + mem.destroy(p); + mem.destroy(p); +} diff --git a/fec/tests/m5/bad-drop.fe b/fec/tests/m5/bad-drop.fe new file mode 100644 index 0000000..7587db6 --- /dev/null +++ b/fec/tests/m5/bad-drop.fe @@ -0,0 +1,11 @@ +unit m5_bad_drop; + +struct Box { + value: i32, + fn drop(self: &mut Self) { self.value = 0; } +} + +fn bad() -> void { + var b: Box = Box{ value: 1 }; + b.drop(); +} diff --git a/fec/tests/m5/runtime.c b/fec/tests/m5/runtime.c index 2f3a110..04d5c10 100644 --- a/fec/tests/m5/runtime.c +++ b/fec/tests/m5/runtime.c @@ -1,9 +1,52 @@ +#include +#include + +#undef malloc +#undef free + extern long fe_m5_runtime_run(long mode); +extern void fe_m5_runtime_conditional(unsigned char flag); +extern void fe_m5_runtime_argument_cleanup(void); + +static void *live_ptrs[64]; +static unsigned live_count; +static unsigned alloc_count; +static unsigned free_count; +static unsigned double_free_count; + +void *m5_malloc(size_t size) +{ + void *p = malloc(size); + if (p && live_count < 64) live_ptrs[live_count++] = p; + if (p) ++alloc_count; + return p; +} + +void m5_free(void *p) +{ + unsigned i; + if (!p) return; + for (i = 0; i < live_count; ++i) { + if (live_ptrs[i] == p) { + live_ptrs[i] = live_ptrs[--live_count]; + ++free_count; + free(p); + return; + } + } + ++double_free_count; +} int main(void) { if (fe_m5_runtime_run(0) != 0) return 1; if (fe_m5_runtime_run(1) != 9) return 2; if (fe_m5_runtime_run(2) != 0) return 3; + fe_m5_runtime_conditional(0); + fe_m5_runtime_conditional(1); + fe_m5_runtime_argument_cleanup(); + if (double_free_count != 0) return 4; + if (live_count != 0) return 5; + if (alloc_count != free_count) return 6; return 0; } diff --git a/fec/tests/m5/runtime.fe b/fec/tests/m5/runtime.fe index be03a9d..7df03b6 100644 --- a/fec/tests/m5/runtime.fe +++ b/fec/tests/m5/runtime.fe @@ -1,5 +1,7 @@ unit m5_runtime; +fn take(p: ^i32) -> void { mem.destroy(p); } + pub fn run(mode: i32) -> i32 { var p: ^i32 = try mem.create(i32); defer { mem.destroy(p); } @@ -9,11 +11,17 @@ pub fn run(mode: i32) -> i32 { p.^ = 9; return p.^; } - while true { - break; - } - if mode == 2 { - return 0; - } + while true { break; } + if mode == 2 { return 0; } return p.^ - 7; } + +pub fn conditional(flag: bool) -> void { + var p: ^i32 = try mem.create(i32); + if flag { take(p); } +} + +pub fn argument_cleanup() -> void { + let p: ^i32 = try mem.create(i32); + take(p); +}