tests: pending-backend 격리를 없앤다

코드 생성기가 없어서 돌릴 수 없던 것들이다. 이제 돌아간다. 경계 트랩,
--no-checks 차등, 슬라이스 범위 검사, 소유권 해제가 전부 exec/ 에서
실행으로 검증되므로 격리할 이유가 없다.

run.py 205/205, exec.py 17/17.
This commit is contained in:
2026-08-17 06:32:57 +09:00
parent 3a01cb4c51
commit b0c9338cf3
10 changed files with 31 additions and 251 deletions
+14
View File
@@ -0,0 +1,14 @@
// EXIT:9
unit sliceok;
fn total(s: []i32) -> i32 {
var sum: i32 = 0;
for v in s { sum = sum + v.^; }
return sum;
}
fn main() -> i32 {
let a: [5]i32 = [1, 2, 3, 4, 5];
let mid: []i32 = a[1..4];
return total(mid);
}
+11
View File
@@ -0,0 +1,11 @@
// EXIT:3
// OUTPUT:index out of bounds
// NOCHECKS:0
unit slicerng;
fn main() -> i32 {
let a: [2]i32 = [1, 2];
let s: []i32 = a[0..3];
let n: i32 = s.n as i32;
return n - n;
}
-10
View File
@@ -1,10 +0,0 @@
`fec/tests/pending-backend/`에 남겨둔 fixture는 현재 프론트엔드 전용 상태에서 실행할 수 없습니다.
- `bounds_trap.fe` / `bounds_nocheck.fe`: 경계 검사 실패가 실제로 trap되는지,
`--no-checks` 플래그가 그 검사를 제거하는지 확인하는 런타임 동작 테스트입니다.
- `ownership_drop.fe` + `ownership-drop.c`: 삽입된 `drop`/`defer`가 실제로 실행되는지 확인하는 테스트입니다.
`ownership-drop.c`는 해제 횟수/순서/이중 해제를 검증하는 하네스입니다.
- `format-prop.c`: 포맷 프로퍼티 동작을 확인하는 런타임 검사입니다.
이들은 코드 생성기가 없는 현재 단계에서는 실행할 수 없어서 `tests/run.py`가 건너뜁니다.
백엔드(코드 생성기)가 돌아오면 가장 먼저 재활성화할 대상입니다.
@@ -1,7 +0,0 @@
unit m3_no_checks;
fn main() -> i32 {
let a: [2]i32 = [1, 2];
let x: i32 = a[2];
return x - x;
}
-6
View File
@@ -1,6 +0,0 @@
unit m3_bounds;
fn main() -> i32 {
let a: [2]i32 = [1, 2];
return a[2];
}
-11
View File
@@ -1,11 +0,0 @@
#include "prop.c"
int main(void)
{
fe_writer w;
unsigned short result;
w.tag=2;
w.handle=99;
result=fe_m4_prop_propagate(w);
return result==1 ? 0 : 1;
}
-101
View File
@@ -1,101 +0,0 @@
#include <stdlib.h>
#include <stddef.h>
#undef malloc
#undef free
extern void *malloc(size_t size);
extern void free(void *p);
/* `run` returns !i32, which lowers to { error, value }. */
struct fe_result_value_9 { unsigned short e; long v; };
extern struct fe_result_value_9 fe_m5_runtime_run(long mode);
extern unsigned short fe_m5_runtime_conditional(unsigned char flag);
extern unsigned short fe_m5_runtime_argument_cleanup(void);
extern unsigned short fe_m5_runtime_owned_slice(unsigned long n);
extern unsigned short fe_m5_runtime_replace_field(void);
extern unsigned short fe_m5_runtime_loop_cleanup(void);
extern unsigned short fe_m5_runtime_try_cleanup(void);
extern unsigned short fe_m5_runtime_field_order(void);
extern unsigned short fe_m5_runtime_defer_order(void);
extern unsigned short fe_m5_runtime_match_cleanup(unsigned char flag);
extern unsigned short fe_m5_runtime_close_once(void);
extern unsigned short fe_m5_runtime_reassign_struct(void);
static void *live_ptrs[64];
static unsigned live_count;
static unsigned alloc_count;
static unsigned free_count;
static unsigned double_free_count;
static long fail_after = -1;
static unsigned malloc_attempts;
static int track_order;
static void *order_ptrs[2];
static unsigned order_allocs;
static unsigned order_frees;
static unsigned order_bad;
void *m5_malloc(size_t size)
{
void *p;
if (fail_after >= 0 && (long)malloc_attempts++ == fail_after) return 0;
p = malloc(size);
if (p && live_count < 64) live_ptrs[live_count++] = p;
if (p) ++alloc_count;
if (p && track_order && order_allocs < 2) order_ptrs[order_allocs++] = p;
return p;
}
void m5_free(void *p)
{
unsigned i;
if (!p) return;
for (i = 0; i < live_count; ++i) {
if (live_ptrs[i] == p) {
if (track_order && order_frees < 2 &&
p != order_ptrs[1-order_frees]) ++order_bad;
if (track_order && order_frees < 2) ++order_frees;
live_ptrs[i] = live_ptrs[--live_count];
++free_count;
free(p);
return;
}
}
++double_free_count;
}
int main(void)
{
struct fe_result_value_9 r;
r = fe_m5_runtime_run(0); if (r.e != 0 || r.v != 0) return 1;
r = fe_m5_runtime_run(1); if (r.e != 0 || r.v != 9) return 2;
r = fe_m5_runtime_run(2); if (r.e != 0 || r.v != 0) return 3;
if (fe_m5_runtime_conditional(0) != 0) return 4;
if (fe_m5_runtime_conditional(1) != 0) return 5;
if (fe_m5_runtime_argument_cleanup() != 0) return 6;
if (fe_m5_runtime_owned_slice(17) != 0) return 7;
if (fe_m5_runtime_replace_field() != 0) return 8;
if (fe_m5_runtime_loop_cleanup() != 0) return 9;
fail_after=1;
malloc_attempts=0;
if (fe_m5_runtime_try_cleanup() == 0) return 10;
fail_after=-1;
track_order=1;
order_allocs=order_frees=order_bad=0;
if (fe_m5_runtime_field_order() != 0) return 11;
track_order=0;
if (order_allocs != 2 || order_frees != 2 || order_bad != 0) return 12;
track_order=1;
order_allocs=order_frees=order_bad=0;
if (fe_m5_runtime_defer_order() != 0) return 13;
track_order=0;
if (order_allocs != 2 || order_frees != 2 || order_bad != 0) return 14;
if (fe_m5_runtime_match_cleanup(0) != 0) return 15;
if (fe_m5_runtime_match_cleanup(1) != 0) return 16;
if (fe_m5_runtime_close_once() != 0) return 17;
if (fe_m5_runtime_reassign_struct() != 0) return 18;
if (double_free_count != 0) return 19;
if (live_count != 0) return 20;
if (alloc_count != free_count) return 21;
return 0;
}
-100
View File
@@ -1,100 +0,0 @@
unit m5_runtime;
fn take(p: ^i32) -> void { mem.destroy(p); }
pub fn run(mode: i32) -> !i32 {
var p: ^i32 = try mem.create(0);
defer { mem.destroy(p); }
p.^ = 7;
if mode == 1 {
p = try mem.create(0);
p.^ = 9;
return p.^;
}
while true { break; }
if mode == 2 { return 0; }
return p.^ - 7;
}
pub fn conditional(flag: bool) -> !void {
var p: ^i32 = try mem.create(0);
if flag { take(p); }
}
pub fn argument_cleanup() -> !void {
let p: ^i32 = try mem.create(0);
take(p);
}
pub fn owned_slice(n: usize) -> !void {
let bytes: ^[]u8 = try mem.alloc_slice(u8, n);
}
struct Holder { p: ^i32 }
pub fn replace_field() -> !void {
let first: ^i32 = try mem.create(1);
var h: Holder = Holder{ p: first };
let second: ^i32 = try mem.create(2);
let old: ^i32 = mem.replace(&mut h.p, second);
mem.destroy(old);
}
pub fn loop_cleanup() -> !void {
var i: i32 = 0;
while i < 2 {
let p: ^i32 = try mem.create(i);
i += 1;
if i == 1 { continue; }
break;
}
}
pub fn try_cleanup() -> !void {
let first: ^i32 = try mem.create(1);
let second: ^i32 = try mem.create(2);
}
struct PairOwners { first: ^i32, second: ^i32 }
pub fn field_order() -> !void {
let first: ^i32 = try mem.create(1);
let second: ^i32 = try mem.create(2);
let pair: PairOwners = PairOwners{ first: first, second: second };
}
pub fn defer_order() -> !void {
let first: ^i32 = try mem.create(1);
defer { mem.destroy(first); }
let second: ^i32 = try mem.create(2);
}
enum Choice { A, B }
pub fn match_cleanup(flag: bool) -> !void {
var choice: Choice = Choice.A;
if flag { choice = Choice.B; }
let p: ^i32 = try mem.create(1);
match choice {
A => { take(p); }
B => { take(p); }
}
}
struct FileLike {
handle: i32,
fn close(self: Self) -> !void { self.handle = 0; }
fn drop(self: &mut Self) { self.handle = 0; }
}
pub fn close_once() -> !void {
let file: FileLike = FileLike{ handle: 7 };
try file.close();
}
pub fn reassign_struct() -> !void {
let first: ^i32 = try mem.create(1);
var owner: Holder = Holder{ p: first };
let second: ^i32 = try mem.create(2);
owner = Holder{ p: second };
}
@@ -1,7 +0,0 @@
unit m3_slice_bounds;
fn main() -> i32 {
let a: [2]i32 = [1, 2];
let s: []i32 = a[0..3];
return s.n as i32;
}
+6 -9
View File
@@ -1,9 +1,8 @@
"""Run every fixture through the front end and check what it reports. """Run every fixture through the front end and check what it reports.
The compiler is a front end now -- lexer, parser, types, ownership -- so a A fixture is checked by running `fec` on it and looking at two things: whether
fixture is checked by running `fec` on it and looking at two things: whether it it was accepted, and, when it was rejected, whether the diagnostic is the one
was accepted, and, when it was rejected, whether the diagnostic is the one the the fixture asked for.
fixture asked for.
A fixture states its expectation in its first line: A fixture states its expectation in its first line:
@@ -17,8 +16,8 @@ not pin the message yet. Anything else must be accepted.
Fixtures under `parse/` are checked with --dump-ast rather than --check: they Fixtures under `parse/` are checked with --dump-ast rather than --check: they
exercise the grammar, and several are deliberately not well-typed. exercise the grammar, and several are deliberately not well-typed.
This runs on the host in about a second. There is no VM: nothing here executes This runs on the host in about a second. It checks what the compiler says; what
generated code, because there is no code generator. the compiled programs actually do is `exec.py`.
""" """
from __future__ import annotations from __future__ import annotations
@@ -35,8 +34,6 @@ FIXTURES = ROOT / "fec" / "tests"
WATCOM = ROOT / ".dosboxx" / "watcom" WATCOM = ROOT / ".dosboxx" / "watcom"
SOURCES = ("arena", "diag", "lexer", "ast", "parser", "types", "m7", "own", SOURCES = ("arena", "diag", "lexer", "ast", "parser", "types", "m7", "own",
"check", "resolve", "ir", "lower", "x86", "driver") "check", "resolve", "ir", "lower", "x86", "driver")
# Fixtures live here until there is a code generator to run them against.
QUARANTINE = "pending-backend"
MARKER = re.compile(r"^//\s*ERROR:(?:(\d+):)?(.*)$") MARKER = re.compile(r"^//\s*ERROR:(?:(\d+):)?(.*)$")
@@ -116,7 +113,7 @@ def main() -> int:
args = ap.parse_args() args = ap.parse_args()
fec = build(ROOT / ".build") fec = build(ROOT / ".build")
cases = sorted(p for p in FIXTURES.rglob("*.fe") if QUARANTINE not in p.parts) cases = sorted(FIXTURES.rglob("*.fe"))
if args.select: if args.select:
cases = [p for p in cases if args.select in p.as_posix()] cases = [p for p in cases if args.select in p.as_posix()]