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; if (sym->decl) sym->decl->flags |= 0x200U;
} else { } else {
sym->moved=1; 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]); 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) static void emit_cleanup_all(FeEmitter *e)
{ {
emit_cleanup_to(e,0); emit_cleanup_to(e,0);
emit_param_cleanup(e);
} }
static void emit_error_return(FeEmitter *e, const char *error_expr) 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) for (x = n->children; x; x = x->next)
if (x->kind == FE_N_LET || x->kind == FE_N_VAR || if (x->kind == FE_N_LET || x->kind == FE_N_VAR ||
x->kind == FE_N_CONST) emit_decl(e, x); 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) { 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); pad(e); fputs(fe_type_c_name(e->current_ret,e->pointer_bits),e->out);
fputs(" fe_return_value;\n",e->out); fputs(" fe_return_value;\n",e->out);
@@ -988,6 +1004,7 @@ static void emit_block(FeEmitter *e, FeNode *n)
} }
--e->indent; --e->indent;
emit_cleanup_block(e,n); 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->block_depth) --e->block_depth;
if (e->fallthrough_block==n) { if (e->fallthrough_block==n) {
pad(e); pad(e);
@@ -1246,7 +1263,9 @@ static void emit_fn(FeEmitter *e, FeNode *fn, int prototype)
if (prototype) fputs(";\n", e->out); if (prototype) fputs(";\n", e->out);
else { else {
FeType *old_ret=e->current_ret; FeType *old_ret=e->current_ret;
FeNode *old_fn=e->current_fn;
e->current_ret=fn->sem_type; e->current_ret=fn->sem_type;
e->current_fn=fn;
fputs(" ", e->out); fputs(" ", e->out);
if (fn->sem_type && fn->sem_type->kind==FE_TYPE_ERROR_UNION && if (fn->sem_type && fn->sem_type->kind==FE_TYPE_ERROR_UNION &&
fn->sem_type->error_value && fn->sem_type->error_value &&
@@ -1254,6 +1273,7 @@ static void emit_fn(FeEmitter *e, FeNode *fn, int prototype)
e->fallthrough_block=fn->c; e->fallthrough_block=fn->c;
emit_block(e, fn->c); emit_block(e, fn->c);
e->current_ret=old_ret; e->current_ret=old_ret;
e->current_fn=old_fn;
fputc('\n', e->out); fputc('\n', e->out);
} }
} }
@@ -1285,6 +1305,7 @@ void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check,
e->block_depth = 0; e->block_depth = 0;
e->loop_depth = 0; e->loop_depth = 0;
e->current_ret = 0; e->current_ret = 0;
e->current_fn = 0;
} }
void fe_emit_c_program(FeEmitter *e) void fe_emit_c_program(FeEmitter *e)
+1
View File
@@ -17,6 +17,7 @@ typedef struct FeEmitter {
unsigned loop_floor[16]; unsigned loop_floor[16];
unsigned loop_depth; unsigned loop_depth;
FeType *current_ret; FeType *current_ret;
FeNode *current_fn;
} FeEmitter; } FeEmitter;
void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, 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 if errorlevel 1 goto test_fail
fec.exe --dump-ast TESTS\PASS\LITERALS.FE > nul fec.exe --dump-ast TESTS\PASS\LITERALS.FE > nul
if errorlevel 1 goto test_fail 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 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 if errorlevel 1 goto test_fail
fec.exe --dump-ast STD\CORE.FE > nul 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 fec.exe --dump-ast STD\SYS.FE > nul
if errorlevel 1 goto test_fail 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 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 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 not errorlevel 1 goto test_fail
if exist TESTS\M2\HELLO.C del TESTS\M2\HELLO.C 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 if errorlevel 1 goto test_fail
rem M2 bits16 regression path remains on compiler A (wcl). 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 if errorlevel 1 goto test_fail
wcl -q -za -bt=dos -fe=TESTS\M2\CAST16.EXE TESTS\M2\CAST16.C wcl -q -za -bt=dos -fe=TESTS\M2\CAST16.EXE TESTS\M2\CAST16.C
if errorlevel 1 goto test_fail if errorlevel 1 goto test_fail
TESTS\M2\CAST16.EXE TESTS\M2\CAST16.EXE
if errorlevel 1 goto test_fail 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 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 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 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 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 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 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 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 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 not errorlevel 1 goto test_fail
if exist TESTS\M3\STRUCT.C del TESTS\M3\STRUCT.C 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 if errorlevel 1 goto test_fail
TESTS\M4\FORMAT.EXE > nul TESTS\M4\FORMAT.EXE > nul
if errorlevel 1 goto test_fail 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 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 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 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 if errorlevel 1 goto test_fail
TESTS\M4\PROP.EXE > nul TESTS\M4\PROP.EXE > nul
if errorlevel 1 goto test_fail 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 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 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 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 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 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 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 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 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 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 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 if errorlevel 1 goto test_fail
fec.exe --target=bits32 --emit-c TESTS\M5\BAD-MOVE.FE -o TESTS\M5\BAD-MOVE.C > nul 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 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 not errorlevel 1 goto test_fail
if exist TESTS\M5\RUNTIME-G.C del TESTS\M5\RUNTIME-G.C if exist TESTS\M5\RUNTIME-G.C del TESTS\M5\RUNTIME-G.C
if exist TESTS\M5\RUNTIME.O del TESTS\M5\RUNTIME.O 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 if errorlevel 1 goto test_fail
rem Compile generated source and the C89 runtime harness in one WCL386 invocation 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. 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 if errorlevel 1 goto test_fail
TESTS\M5\RUNTIME.EXE TESTS\M5\RUNTIME.EXE
if errorlevel 1 goto test_fail 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 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) int main(void)
{ {
if (fe_m5_runtime_run(0) != 0) return 1; if (fe_m5_runtime_run(0) != 0) return 1;
if (fe_m5_runtime_run(1) != 9) return 2; if (fe_m5_runtime_run(1) != 9) return 2;
if (fe_m5_runtime_run(2) != 0) return 3; 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; return 0;
} }
+14 -6
View File
@@ -1,5 +1,7 @@
unit m5_runtime; unit m5_runtime;
fn take(p: ^i32) -> void { mem.destroy(p); }
pub fn run(mode: i32) -> i32 { pub fn run(mode: i32) -> i32 {
var p: ^i32 = try mem.create(i32); var p: ^i32 = try mem.create(i32);
defer { mem.destroy(p); } defer { mem.destroy(p); }
@@ -9,11 +11,17 @@ pub fn run(mode: i32) -> i32 {
p.^ = 9; p.^ = 9;
return p.^; return p.^;
} }
while true { while true { break; }
break; if mode == 2 { return 0; }
}
if mode == 2 {
return 0;
}
return p.^ - 7; 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);
}