런타임에 open/read/close 와 명령줄을 넣었다. std.io 가 그 위에 파일 열기, 읽기, 쓰기, 그리고 명령줄을 조각으로 나누는 것을 얹는다. 인용부호 처리는 런타임이 알 일이 아니라 라이브러리가 할 일이다. 길에서 고친 것들: - *T 가 타입 시스템에 실체가 없어서 덩어리로 취급됐다. 이제 진짜 종류다 -- 주소일 뿐이고 추적할 대여도 실행할 drop 도 없는 Copy 타입. 그 결과 &u8 이 *u8 에 자동으로 맞지 않게 됐는데, 그게 맞다: R9 는 그 변환을 unsafe 안의 @ptr_cast 로만 허용한다. - raw 포인터에 정수를 더하면 더 뒤의 주소다. 소유자나 대여에는 허용하지 않는다 -- 자기 자리가 있는 것에서 걸어나가는 것이 *T 의 용도다. - @volatile_load / @volatile_store / @ptr_cast 를 내린다. - undefined 가 선언된 타입을 따른다. 없으면 손으로 타이핑할 수 있는 것보다 큰 버퍼를 선언할 방법이 아예 없었다. R8 이 정확히 동작하는 것도 확인했다: 참조성 파라미터가 둘인 함수는 슬라이스를 반환할 수 없다. 어디서 파생됐는지 시그니처가 말하지 않기 때문이다. exec.py 24/24.
396 lines
9.8 KiB
NASM
396 lines
9.8 KiB
NASM
; 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 24 dup(0)
|
|
written dd 0
|
|
allocs dd 0
|
|
frees 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
|
|
|
|
; ---------------------------------------------------------------- primitives
|
|
; The standard library is written in Ferro; these are the few things it cannot
|
|
; say for itself. All cdecl.
|
|
|
|
extern _GetProcessHeap@0 : near
|
|
extern _HeapAlloc@12 : near
|
|
extern _HeapFree@12 : near
|
|
extern _CreateFileA@28 : near
|
|
extern _ReadFile@20 : near
|
|
extern _CloseHandle@4 : near
|
|
extern _GetCommandLineA@0 : near
|
|
|
|
; fe_rt_write(handle, ptr, len) -> bytes written
|
|
public fe_rt_write
|
|
fe_rt_write proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push ebx
|
|
mov eax, [ebp+8] ; 1 = stdout, 2 = stderr
|
|
cmp eax, 2
|
|
je pick_err
|
|
push -11
|
|
jmp pick_done
|
|
pick_err:
|
|
push -12
|
|
pick_done:
|
|
call _GetStdHandle@4
|
|
push 0
|
|
push offset written
|
|
push dword ptr [ebp+16]
|
|
push dword ptr [ebp+12]
|
|
push eax
|
|
call _WriteFile@20
|
|
mov eax, [written]
|
|
pop ebx
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_write endp
|
|
|
|
; fe_rt_alloc(n) -> pointer, or zero
|
|
public fe_rt_alloc
|
|
fe_rt_alloc proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
call _GetProcessHeap@0
|
|
push dword ptr [ebp+8]
|
|
push 8 ; HEAP_ZERO_MEMORY
|
|
push eax
|
|
call _HeapAlloc@12
|
|
inc dword ptr [allocs]
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_alloc endp
|
|
|
|
; fe_rt_free(p)
|
|
public fe_rt_free
|
|
fe_rt_free proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
mov eax, [ebp+8]
|
|
test eax, eax
|
|
je free_done
|
|
call _GetProcessHeap@0
|
|
push dword ptr [ebp+8]
|
|
push 0
|
|
push eax
|
|
call _HeapFree@12
|
|
inc dword ptr [frees]
|
|
free_done:
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_free endp
|
|
|
|
; fe_rt_allocs() / fe_rt_frees() -- what the allocator has been asked to do,
|
|
; so that a test can insist every allocation was released.
|
|
public fe_rt_allocs
|
|
fe_rt_allocs proc near
|
|
mov eax, [allocs]
|
|
ret
|
|
fe_rt_allocs endp
|
|
|
|
public fe_rt_frees
|
|
fe_rt_frees proc near
|
|
mov eax, [frees]
|
|
ret
|
|
fe_rt_frees endp
|
|
|
|
; fe_rt_write_int(handle, value, is_unsigned) -- decimal, with a sign when
|
|
; the value is negative and signed was asked for.
|
|
public fe_rt_write_int
|
|
fe_rt_write_int proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push ebx
|
|
push esi
|
|
push edi
|
|
mov edi, offset numbuf + 15
|
|
mov byte ptr [edi], 0
|
|
mov eax, [ebp+12]
|
|
xor ebx, ebx ; ebx = 1 when a '-' is needed
|
|
cmp dword ptr [ebp+16], 0
|
|
jne int_digits
|
|
test eax, eax
|
|
jge int_digits
|
|
neg eax
|
|
mov ebx, 1
|
|
int_digits:
|
|
mov ecx, 10
|
|
int_loop:
|
|
xor edx, edx
|
|
div ecx
|
|
add dl, '0'
|
|
dec edi
|
|
mov [edi], dl
|
|
test eax, eax
|
|
jnz int_loop
|
|
test ebx, ebx
|
|
je int_write
|
|
dec edi
|
|
mov byte ptr [edi], '-'
|
|
int_write:
|
|
mov esi, offset numbuf + 15
|
|
sub esi, edi
|
|
push esi
|
|
push edi
|
|
push dword ptr [ebp+8]
|
|
call fe_rt_write
|
|
add esp, 12
|
|
pop edi
|
|
pop esi
|
|
pop ebx
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_write_int endp
|
|
|
|
; fe_rt_write_hex(handle, value)
|
|
public fe_rt_write_hex
|
|
fe_rt_write_hex proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push ebx
|
|
push esi
|
|
push edi
|
|
mov edi, offset numbuf + 15
|
|
mov byte ptr [edi], 0
|
|
mov eax, [ebp+12]
|
|
hex_loop:
|
|
mov edx, eax
|
|
and edx, 15
|
|
cmp dl, 10
|
|
jb hex_digit
|
|
add dl, 'a' - 10 - '0'
|
|
hex_digit:
|
|
add dl, '0'
|
|
dec edi
|
|
mov [edi], dl
|
|
shr eax, 4
|
|
test eax, eax
|
|
jnz hex_loop
|
|
mov esi, offset numbuf + 15
|
|
sub esi, edi
|
|
push esi
|
|
push edi
|
|
push dword ptr [ebp+8]
|
|
call fe_rt_write
|
|
add esp, 12
|
|
pop edi
|
|
pop esi
|
|
pop ebx
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_write_hex endp
|
|
|
|
; fe_rt_open(path, write) -> handle, or -1
|
|
; `path` is a NUL-terminated byte string. Reading opens what is there; writing
|
|
; creates or truncates.
|
|
public fe_rt_open
|
|
fe_rt_open proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push 0 ; hTemplateFile
|
|
push 128 ; FILE_ATTRIBUTE_NORMAL
|
|
cmp dword ptr [ebp+12], 0
|
|
jne open_write
|
|
push 3 ; OPEN_EXISTING
|
|
push 0
|
|
push 1 ; FILE_SHARE_READ
|
|
push 80000000h ; GENERIC_READ
|
|
jmp open_call
|
|
open_write:
|
|
push 2 ; CREATE_ALWAYS
|
|
push 0
|
|
push 0
|
|
push 40000000h ; GENERIC_WRITE
|
|
open_call:
|
|
push dword ptr [ebp+8]
|
|
call _CreateFileA@28
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_open endp
|
|
|
|
; fe_rt_read(handle, buf, len) -> bytes read, or -1
|
|
public fe_rt_read
|
|
fe_rt_read proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push 0
|
|
push offset written
|
|
push dword ptr [ebp+16]
|
|
push dword ptr [ebp+12]
|
|
push dword ptr [ebp+8]
|
|
call _ReadFile@20
|
|
test eax, eax
|
|
jne read_ok
|
|
mov eax, -1
|
|
jmp read_done
|
|
read_ok:
|
|
mov eax, [written]
|
|
read_done:
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_read endp
|
|
|
|
; fe_rt_close(handle)
|
|
public fe_rt_close
|
|
fe_rt_close proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push dword ptr [ebp+8]
|
|
call _CloseHandle@4
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret
|
|
fe_rt_close endp
|
|
|
|
; fe_rt_cmdline() -> pointer to the whole command line, NUL terminated.
|
|
; Splitting it is the standard library's job, not the runtime's.
|
|
public fe_rt_cmdline
|
|
fe_rt_cmdline proc near
|
|
call _GetCommandLineA@0
|
|
ret
|
|
fe_rt_cmdline endp
|
|
|
|
; fe_rt_exit(code) -- never returns
|
|
public fe_rt_exit
|
|
fe_rt_exit proc near
|
|
push ebp
|
|
mov ebp, esp
|
|
push dword ptr [ebp+8]
|
|
call _ExitProcess@4
|
|
fe_rt_exit endp
|
|
|
|
public fe_start_
|
|
fe_start_ proc near
|
|
call fe_main_
|
|
push eax
|
|
call _ExitProcess@4
|
|
fe_start_ endp
|
|
|
|
_TEXT ends
|
|
|
|
end fe_start_
|