wip: extend M5 ownership tests and cleanup emission

조건부 이동, 이중 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PQm6oAvWX4Lp3iSN5AHGT
This commit is contained in:
2026-08-16 16:54:41 +09:00
co-authored by Claude Opus 5
parent 41de8aa2dc
commit f88559c635
9 changed files with 136 additions and 29 deletions
+4 -1
View File
@@ -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;
}
}
}
+21
View File
@@ -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)
+1
View File
@@ -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,
+28 -22
View File
@@ -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
+8
View File
@@ -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;
}
+6
View File
@@ -0,0 +1,6 @@
unit m5_bad_double;
fn bad(p: ^i32) -> void {
mem.destroy(p);
mem.destroy(p);
}
+11
View File
@@ -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();
}
+43
View File
@@ -1,9 +1,52 @@
#include <stdlib.h>
#include <stddef.h>
#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;
}
+14 -6
View File
@@ -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);
}