r/Compilers 8d ago

Behold my Abomination: Written in Pascal, Single Pass(ish), No AST, No IR

Post image

Rockskunk.

Float is the only type. Everything else is QWORD. Shove an "integer" and a string into the same array if you wish.

Compiler written in Pascal. Emits NASM with regex peephole optimization before compilation. Incredibly permissive, you can do whatever you want and are only stopped if there is a syntax error. There are some sassy warnings for unwise choices but it is not the compiler's decision what you do with your code. I have had tons of fun figuring out how things work and learning assembly through a firehose. The IR is nasm source haha. Never going to implement an AST. I didn't read any book but i will need to STUDY the Dragon Book for register allocation. I did a cursory overview and understand nothing.

Backstory. I have been making half-baked transpilers for quite sometime now. Pascal or lisp compiler to C or (a very short) attempt at LLVM but I couldn't get them to behave how i wanted and kept losing interest. I ripped the lexer from one of them and have been using the parsing architecture from the others as inspiration and decided to just buckle down and make what I wanted even though I have been scared of assembly. I am learning as I go and keep making unfortunate choices like trying to track state with a record refactor (arrays only from now on), or routing token evaluation through like 8 redundant functions.

I have always wanted a language like this is because I love systems programming and like to rewrite things like coreutils or make shells and stuff. I love Pascal and dislike C but I have always wanted something that just gets out of my way and lets me do what i want, kinda like a dangerous Lisp. Not in your way, save your thinking for the real puzzle, not which type do you need. I have written cat, non-recursive cp and a (just writes no blocksize or flags) dd. I am going to get those to production quality and also write ls and such. I am about halfway done porting an init system I wrote in Pascal to rockskunk and its gonna be a glorious moment when i start my computer with my own language for the first time.

I finalized the syntax well before I wrote it and there will be no extra concepts, NO OOP, no new types, no restrictions, no guardrails nothing. This is a language that does what you tell it and nothing more. There's tons that i have specced out and not accomplished, but it will always remain like an "Assembly++" incredibly low level language.

Eventual features that will take me 3 years and most of my sanity. Register allocation and first-class vector support. I do not know near enough to even plan how to do these yet but the idea is the compiler uses tests and an IFDEF system that determines by machine (or a flag) what vector unit you want to compile for and sets width and then doing SIMD ops is as simple as a ** b or (a, b) *+ c. Do not count on this ever getting fleshed out but boy am I gonna try.

https://github.com/liam-0398/rockskunk/tree/main

**EDIT when reviewing post just realized my cp doesn't preserve permissions. whoops.

74 Upvotes

8 comments sorted by

13

u/FloweyTheFlower420 8d ago

the phrase "regex peephole optimization" gave me minor brain damage

6

u/slothforestslothbear 8d ago

We should host a support group, I couldn't write one without a little brain damage myself.

Believe it or not, this is what peak performance looks like 'mov ([a-z0-9]+), \1\r?\n'

4

u/FloweyTheFlower420 8d ago

Oh my god this is so cursed. You can also probably get multi line peepholes where you regex your way into instruction combine. Fixed point iteration via repeated regex application...

3

u/slothforestslothbear 8d ago

Right now I'm going after no-ops and back to back store-reloads that my high-quality compiler produces. I also spam the pass three times every compile because more pass more better, i think.I'm trying to come up with ideas to see how far I can take this. Ill do some research into that, Gotta procrastinate register allocation somehow....

3

u/FloweyTheFlower420 8d ago

If you insist on nasm source being your IR maybe just make the regs like %r[num] instead of rax or whatever, and then do regalloc via grepping/regex for stuff? who knows, x86 is also a cursed architecture. No idea how you would like, compute a live range.

1

u/slothforestslothbear 8d ago

Honestly that will probably just confuse me haha. But it does make more sense than my sneak peak at ch8 of the Dragon book.I'm going to have to commit blasphemy either way because I will never give up my arrays and strings for state and output. Its gonna take me some time to get into that. I want to find a standardized benchmark to compare against other languages that scales with processor speed so I can see how i'm really doing.

I had a foray in to RISC-V asm before x86 trying to write a little Forth and it was SO NICE and then i come to x86 and they hit me with the damn cvttsd2si. I'm very much still learning it but luckily 3/4 of my compiler is mov, rax 420 or xmm and i use just flat memory for everything so its just easy labels. I do want to start writing more raw ASM though and really nail it down, maybe a bare metal forth like DuskOS's D List cousin or something.

5

u/slothforestslothbear 7d ago edited 7d ago

Ladies and gentleman, this is what the sweet power of regex gets you.

i7-9750H, plugged in. Sum of squares fixed-point 100000000 iterations. Rockskunk vs gcc -O0 and -O2 cold warmup then one hot run I am not cherry picking.

/NFS/Code/rockskunk $ > time ./sumofsqaures  
672921401752298880  
real    0m0.216s
user    0m0.212s
sys     0m0.000s
/NFS/Code/rockskunk $ > time ./ssO0
672921401752298880

real    0m0.230s
user    0m0.224s
sys     0m0.000s
/NFS/Code/rockskunk $ > time ./ssO2
672921401752298880

real    0m0.080s
user    0m0.074s
sys     0m0.000s

I just beat gcc-O0, mother of god.

6

u/slothforestslothbear 7d ago

#include <stdio.h>

int main() {

long sum = 0;

long i = 0;

while (i <= 100000000) {

long sq = i * i;

sum = sum + sq;

i = i + 1;

}

printf("%ld\n", sum);

return 0;

}

F main() {

sum := 0

i := 0

LW (i <= 100000000) {

sq := i * i

sum := sum + sq

i := i + 1

}

printw(sum)

r := 0

}

section .bss

digitbuf: resb 32

g_argc: resq 1

g_argv: resq 1

section .data

section .text

main:

push rbp

mov rbp, rsp

sub rsp, 0000000032

mov rax, 0

mov [rbp-8], rax

mov rax, 0

mov [rbp-16], rax

LW0:

mov rax, [rbp-16]

cmp rax, 100000000

jg LW1

mov rax, [rbp-16]

imul rax, [rbp-16]

mov [rbp-24], rax

mov rax, [rbp-8]

add rax, [rbp-24]

mov [rbp-8], rax

mov rax, [rbp-16]

add rax, 1

mov [rbp-16], rax

jmp LW0

LW1:

mov rax, [rbp-8]

call print_qword

mov rax, 0

mov [rbp-32], rax

add rsp, 0000000032

pop rbp

ret

global _start

_start:

mov rax, [rsp]

mov [g_argc], rax

lea rax, [rsp + 8]

mov [g_argv], rax

call main

mov rdi, rax

mov rax, 60

syscall