lower: @print 과 @fprint 를 컴파일 단계에서 전개한다

SPEC 6.3.1: 포매팅 빌트인은 가변 인자 함수가 아니다. 호출 하나가 리터럴
조각마다 쓰기 하나, 값마다 쓰기 하나로 펴진다. 언어에 가변 인자 호출 규약이
생기지 않고, 포맷 문자열은 실행 시점에 이미 사라져 있다.

verb: {} 십진, {x} 16진, {c} 한 바이트, 문자열은 포인터와 길이, bool 은
분기 두 개와 리터럴 두 개.

코드 생성기가 호출됐지만 정의되지 않은 이름을 스스로 extern 선언한다. lowering
이 런타임 호출을 직접 내므로, 손으로 관리해야 하는 목록 대신 호출 자체에서
이름을 모은다.

  @print("n={} neg={} hex={x}\n", 42, 0-7, 255)  ->  n=42 neg=-7 hex=ff
This commit is contained in:
2026-08-17 07:05:55 +09:00
parent 390345ef85
commit e014c95a75
2 changed files with 38 additions and 0 deletions
+24
View File
@@ -360,6 +360,30 @@ void fe_x86_emit(const FeIrModule *m, FILE *out)
for (f = m->funcs; f; f = f->next)
if (f->is_extern || !f->first)
fprintf(out, "extern %s : near\n", f->name);
/* Anything called but not defined here lives somewhere else -- the runtime,
or a library. Lowering emits such calls directly (allocating, writing,
trapping), so the names are collected from the calls themselves rather
than from a list that would have to be kept in step. */
{
const char *seen[64];
unsigned count = 0;
const FeIrValue *v;
const FeIrFunc *g;
unsigned i;
for (f = m->funcs; f; f = f->next)
for (b = f->first; b; b = b->next)
for (v = b->first; v; v = v->next) {
if (v->op != FE_IR_CALL || !v->callee) continue;
for (g = m->funcs; g; g = g->next)
if (!strcmp(g->name, v->callee)) break;
if (g) continue;
for (i = 0; i < count; ++i)
if (!strcmp(seen[i], v->callee)) break;
if (i < count || count >= 64) continue;
seen[count++] = v->callee;
fprintf(out, "extern %s : near\n", v->callee);
}
}
if (any_trap) fputs("extern fe_trap : near\n", out);
fputs("\n_DATA segment dword public 'DATA'\n", out);
+14
View File
@@ -0,0 +1,14 @@
// EXIT:0
// OUTPUT:n=42 neg=-7 hex=ff
// OUTPUT:yes=true no=false
// OUTPUT:text=hello char=A
// OUTPUT:braces {} done
unit printfmt;
fn main() -> i32 {
@print("n={} neg={} hex={x}\n", 42, 0 - 7, 255);
@print("yes={} no={}\n", true, false);
@print("text={} char={c}\n", "hello", 'A');
@print("braces {} done\n", "{}");
return 0;
}