This chapter ends with two working compilers on your machine and a native executable that one of them produced. Nothing here is a sketch: every command below is run exactly as written.
What you need
A 64-bit Linux machine on x86-64 or ARM64 — or Docker on any host, which gives you one.
If you have Docker, take that road. It is one command, it works identically on both architectures, and it leaves nothing on your machine when you are done:
git clone https://gitlab.com/mica-lang/mica-container.git
cd mica-container
docker run -it --rm -v "$PWD:/work" -w /work micalang/micaEverything from here on runs inside that shell. If you would rather install
natively, Installing Mica is two packages and a .deb; come
back here afterwards and clone the same repository.
Step 1 — the backend
The two compilers in this series produce native code by handing their work to Dragon, the backend that compiles the Mica language. Dragon is not in the image by default, and its absence is deliberate:
mica-install-sdkThat command prints the licence terms, waits for you, and then installs two
files: a static archive /usr/lib/libmicadragon-<arch>.a and a C header
/usr/include/mica_dragon.h.
Read the terms before you accept them. The Mica compiler and the Dragon SDK ship under different licences, and this is the one place in the Mica ecosystem where that matters:
| Mica compiler | Dragon SDK | |
|---|---|---|
| Licence | free for every purpose, commercial work included | PolyForm Noncommercial 1.0.0 |
| Free for | anything | research, teaching, personal study, hobby projects; educational, charitable, public-research and government institutions |
| Commercial use | included | by separate written agreement — info@mica-dev.com |
If you are working through this series as a student, a teacher, or on your own time, you are inside the free grant. If you are at a company and this is work, talk to us first — that is what the address is for.
Step 2 — build the C twin
cd sdk/pl0/c
makeThat compiles seven translation units and links them against the archive:
cc -std=c23 -Wall -Wextra -I /usr/include -o ../build/c/pl0 \
diagnostics.c scanner.c symbols.c parser.c pcode.c spectra.c pl0.c \
/usr/lib/libmicadragon-arm64.a -lpthreadThere is no build system to learn: the Makefile runs one cc command. If you
skipped step 1, make says so in one line rather than burying you in errors
about a missing header:
pl0: the Dragon SDK is not installed - run 'mica-install-sdk'
(needs /usr/include/mica_dragon.h and /usr/lib/libmicadragon-arm64.a)Step 3 — run a program
make runruns all six checked-in programs. To run one by hand:
../build/c/pl0 run ../testdata/square.pl01
1
2
4
3
9
4
16
5
25
6
36
7
49
8
64
9
81
10That is the compiler compiling square.pl0 and then executing it on Wirth’s
stack machine, which lives inside the same binary. Remember from
chapter 1 that PL/0 has no output statement
— every one of those numbers is a store.
Step 4 — look at the code it generated
../build/c/pl0 list ../testdata/square.pl0 0 jmp 0 8
1 jmp 0 2
2 int 0 3
3 lod 1 3
4 lod 1 3
5 opr 0 4
6 sto 1 4
7 opr 0 0
8 int 0 5
9 lit 0 1
10 sto 0 3
11 lod 0 3
12 lit 0 10
13 opr 0 10
14 jpc 0 21
15 cal 0 2
16 lod 0 3
17 lit 0 1
18 opr 0 2
19 sto 0 3
20 jmp 0 11
21 opr 0 0Twenty-two instructions, and that is the entire square program compiled. You
will be able to read every line of this by the end of
chapter 8 — but you can already guess a
lot. Line 5 is opr 0 4, the multiply, sitting between two lods that push
arg twice. Line 20 jumps back to 11, which is the while loop’s test.
Print it and keep it. It is the most useful single page in this series.
Step 5 — native code
Now the other back end:
../build/c/pl0 build square ../testdata/square.pl0validate: 0 findings
build: okThree files appeared:
| File | What it is |
|---|---|
square.il | the program in Spectra, the intermediate language Dragon compiles |
square.s | native assembly for your machine |
square.rt.s | a small runtime unit every Mica-family link joins |
Look at the first one — it is the interesting one:
@main:
prologue @main, 0
t1.1:int64 = copyLiteral 1:int64 #1
storeVariable t1.1:int64, v1.1:int64 #2
t1.2:string = copyLiteral "%lld\n":string #3
t1.3:int64 = loadVariable v1.1:int64 #4
call @wprintf, 0:int32, @a1.1
argument t1.2:string
argument t1.3:int64
@l1.3:
t1.4:int64 = loadVariable v1.1:int64 #5
t1.5:int64 = copyLiteral 10:int64 #6
t1.6:bool = less t1.4:int64, t1.5:int64 #7
jumpNotEqual t1.6:bool, @l1.5
call @f1.1, 0:int32
...That is arg := 1 followed by the store’s print, then the while test. Compare
it with the p-code listing above: same program, two very different
representations. Chapters 8 and
9 are about exactly that difference.
Link and run it. The two .s files join the Dragon runtime — the floor every
emitted program links, installed by the mica-dragon-rt package — and nothing
else: the program’s one import, wprintf, is libc’s own.
gcc -no-pie -o square square.s square.rt.s /usr/lib/libmicadragon-rt-arm64.a
./square1
1
2
4
3
9
...The same numbers. You have now taken a program written in a small language through a real compiler pipeline — scan, parse, intermediate code, optimization, register allocation, instruction selection — and run the machine code that came out. That the output matches the stack machine’s byte for byte is not a coincidence; it is the property chapter 10 is about.
Step 6 — the Mica twin
The same compiler, in the other language:
cd ../mica
make runThe build command is one line, and it names no backend at all:
mica --compile --link --optimize debug --platform linux,arm64,utf-32 --assembly intel \
--source diagnostics.mica,scanner.mica,symbols.mica,pcode.mica,spectra.mica,parser.mica,pl0.mica \
--build ../build/Pl0There is no -I, no archive on the command line, and no configuration file. A
Mica program reaches the engine by importing it:
imp
ContextNew, ContextFree, RegistryNew : dragon;
UnitNew, UnitDump, Validate, Build : dragon;The compiler carries that surface itself, so the program compiles and is diagnosed before any licence question arises — only the link asks for engine code, and the driver finds the archive on its own.
One difference you will notice immediately: the Mica twin takes its command on standard input rather than as arguments.
echo "run ../testdata/square.pl0" | ../build/Pl0/Pl0That is not a design statement about command lines; the Mica standard library does not publish process arguments yet, so the twin asks for its command the way it can. It is the one place where the two compilers differ for a reason that has nothing to do with compilers.
Step 7 — see them agree
Run the same program through both:
../build/c/pl0 run ../testdata/staticlinks.pl0
echo "run ../testdata/staticlinks.pl0" | ../build/Pl0/Pl020
0
40
41Identical, and that is the smallest demonstration of the idea this series keeps coming back to: two independent implementations that agree are evidence; one implementation that looks right is not.
Where things are
sdk/pl0/
├── c/ the C23 twin — seven .c/.h pairs and a Makefile
├── mica/ the Mica twin — seven .mica units, a Makefile and a mica.project
└── testdata/ six PL/0 programs, shared by bothBoth twins carry a diagnostics command that runs their own error table through
their own parser — worth trying now, and worth understanding after
chapter 6:
../build/c/pl0 diagnostics | tail -3code 9 a program without its period: ok
recovery: 5 diagnostics
diagnostics: 25 of 25If something did not work
| Symptom | Cause |
|---|---|
mica-install-sdk: command not found | you are not in the container image; install the SDK natively instead |
the Dragon SDK is not installed | step 1 was skipped, or you are on an architecture the SDK is not published for |
libmicadragon-rt…: No such file at the link in step 5 | the runtime package installs it — check with ls /usr/lib/libmicadragon-rt-*.a |
| the Mica twin hangs | it is waiting for its command on standard input; pipe one in, as step 6 shows |
Next: 3 — The shape of a compiler, where the seven parts get named and put in order.