Re:Mugo, a toy compiler for a subset of Go that can compile ...
by yencabulator on Monday April 12, 2021 @02:26PM
(#26783134)
Now do that with the rest of the compilation in one pass, without an AST.
[ Parent
| on HN ]
Re:Mugo, a toy compiler for a subset of Go that can compile ...
by Someone on Monday April 12, 2021 @04:07PM
(#26784488)
If you target a simple CPU (no out-of-order execution, no pipelines), and are ignoring performance (as early C compilers did. If you wanted performance, you inserted ‘register’ in the right places (and you knew what the right places where because you wrote the compiler or were setting next to the guy who did), or dropped down to assembly), it’s not that difficult.
The tricky bits are the stack offsets to local variables. If you compile
int a = foo()
int b = a + 1;
to call foo
push
add 1
push
the addresses of variables relative to the stack pointer change when you create new locals, when locals go out of scope, or when you push function arguments onto the stack. The trick is for the compiler to track the address of locals relative to the top of the stack frame and keep track of the depth of the stack frame. Then, when generating code to access a stack-relative location, combine the two to translate ‘relative to top of stack frame’ to ‘relative to stack pointer’. A bit tedious, but doable.Now, when you want performance (especially on modern CPUs), and don’t want to use stack space for variables kept in registers, things do get significantly more complicated.
Re:Mugo, a toy compiler for a subset of Go that can compile ...
by Joker_vD on Tuesday April 13, 2021 @10:38AM
(#26792749)
push ebp
move ebp, esp
sub esp, fun_0_stacksize
; calculating foo() and saving the temporary
call foo
push eax
; loading the temporary and...
pop eax
; ...assigning it to "a"
mov [ebp+fun_0_var_a], eax
; calculating "a+1" starts by evaluating "a"...
mov eax, [ebp+fun_0_var_a]
; ...and saving it as a temporary...
push eax
; ..then by evaluating 1
mov eax, 1
; ...and saving it as a temporary
push eax
; then you load two temporaries, add them together...
pop ebx
pop eax
add eax, ebx
; ...and save the temporary result
push eax
; then you load the temporary result and...
pop eax
; ...save it as "b"
mov [ebp+fun_0_var_b], eax
; at this point, you know the total number of local variables
; the "fun_NNN_" prefix is used because EQU constants cannot be
; redefined (obviously, or they wouldn't support forward uses)
fun_0_stacksize EQU 8
fun_0_var_a EQU -4
fun_0_var_b EQU -8
; epilogue
mov esp, ebp
pop ebp
retn
Notice how accessing the locals is done via a separate Base Pointer register EBP instead of the Stack Pointer register ESP (in fact, ESP-based addressing mode is encoded really, really ugly on x86), so that pushing&popping of temporaries doesn't mess up the loads/stores of local variables.Yes, the code quality is appalling but... it works. In fact, I think Nils M. Holm's T3X compiler uses a similar approach as well.
Of course, if you can allow yourself to use some IR, you can actually assign registers to variables and temporaries and do a much better job.