backend: i386 어셈블리를 내고 Windows 11 실행 파일을 만든다

fec --emit-asm -> wasm -> wlink -> .exe. 툴체인은 고정된 Open Watcom 그대로다.

레지스터 할당기가 없다. 임시값마다 스택 슬롯을 주고, 명령마다 피연산자를
고정 레지스터로 읽어 계산하고 다시 저장한다. 느린 코드지만 명백히 옳은
코드이고, 옳은 것이 먼저다. 나중에 할당기를 끼워도 나머지는 모른다 --
임시값이 어디 사는지만 바뀐다.

런타임 fec/rt/start.asm 은 진입 스텁과 fe_trap 이다. trap 은 이유와 파일과
줄을 stderr 에 쓰고 3으로 끝낸다.

처음으로 Ferro 프로그램이 실행됐다:

  1..10 합         -> 55
  (7*6-2)/4        -> 10
  루프+호출+분기   -> 1

tests/build.py 가 컴파일하고 링크하고 돌린다.
This commit is contained in:
2026-08-17 05:55:34 +09:00
parent 6ee3764667
commit e5093e690d
9 changed files with 662 additions and 7 deletions
+134
View File
@@ -0,0 +1,134 @@
; Ferro runtime: process entry and the trap handler.
;
; The entry point calls the program's `main` and hands its result to
; ExitProcess, so a Ferro program is an ordinary console executable.
; `fe_trap` prints where the program stopped and why, then exits 3.
.386
.model flat
extern _ExitProcess@4 : near
extern _GetStdHandle@4 : near
extern _WriteFile@20 : near
extern fe_main_ : near
_DATA segment dword public 'DATA'
reasons dd offset r_bounds, offset r_overflow, offset r_divide
dd offset r_unreach, offset r_explicit
r_bounds db 'index out of bounds',0
r_overflow db 'integer overflow',0
r_divide db 'divide by zero',0
r_unreach db 'reached unreachable code',0
r_explicit db 'trap',0
r_unknown db 'trap',0
prefix db 'ferro: ',0
at_word db ' at ',0
colon db ':',0
newline db 13,10,0
numbuf db 16 dup(0)
written dd 0
_DATA ends
_TEXT segment dword public 'CODE'
; write_cstr(esi = pointer to a NUL-terminated string) -> void
write_cstr proc near
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov edi, esi
xor ecx, ecx
count_loop:
cmp byte ptr [edi], 0
je count_done
inc edi
inc ecx
jmp count_loop
count_done:
test ecx, ecx
je write_done
push -11 ; STD_ERROR_HANDLE
call _GetStdHandle@4
push 0 ; lpOverlapped
push offset written
push ecx
push esi
push eax
call _WriteFile@20
write_done:
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
write_cstr endp
; write_uint(eax = value) -> void
write_uint proc near
push ebp
mov ebp, esp
push ebx
mov edi, offset numbuf + 15
mov byte ptr [edi], 0
mov ebx, 10
digit_loop:
xor edx, edx
div ebx
add dl, '0'
dec edi
mov [edi], dl
test eax, eax
jnz digit_loop
mov esi, edi
call write_cstr
pop ebx
mov esp, ebp
pop ebp
ret
write_uint endp
; fe_trap(reason, file, line) -- cdecl, never returns
public fe_trap
fe_trap proc near
push ebp
mov ebp, esp
mov esi, offset prefix
call write_cstr
mov eax, [ebp+8] ; reason
cmp eax, 5
jb reason_ok
mov esi, offset r_unknown
jmp reason_write
reason_ok:
mov esi, [reasons + eax*4]
reason_write:
call write_cstr
mov esi, offset at_word
call write_cstr
mov esi, [ebp+12] ; file
call write_cstr
mov esi, offset colon
call write_cstr
mov eax, [ebp+16] ; line
call write_uint
mov esi, offset newline
call write_cstr
push 3
call _ExitProcess@4
fe_trap endp
public fe_start_
fe_start_ proc near
call fe_main_
push eax
call _ExitProcess@4
fe_start_ endp
_TEXT ends
end fe_start_