Metal ARM x05 - Linker and Modules (Lab Notes)
Project 5 introduces a primitive module system and a linker to support it.
Work Completed
Checkpoint commit for this phase is 538eb031d77d44ed7fe6cc28f56c35aee624c410.
-
Module imports:
; main.cas ; ... ; make `other-module` public definitions and labels accessible import!(other-module) -
Public definitions and labels:
- The syntax for definition references (those with
$) is admittedly noisy. I'll probably revisit it.
; module.cas define32-pub!(some-special-addr, xAABB.CCDD) define16-pub!(some-thing, xAABB) define8-pub!(some-byte, xAA) @pub my-public-label @pub my-other-label @my-private-label
; main.cas import!(module) ; sizes for pseudo definitions are explicitly declared $32:module::some-special-addr $16:module::some-thing $8:module::some-byte ; sizes for labels are pointer sized &module::my-public-label &module::my-other-label - The syntax for definition references (those with
-
A hybrid binary/human-readable format for compiled modules serialized to disk:
- I wanted something with a human-readable header that would enable me to debug without writing a custom
objdump-like program right off the bat. - I also didn't want to waste a ton of space making all compiled code human readable.
- Given the constraints, I ended up with a hybrid format containing three parts (for now):
-
Binary prelude:
CHASM MODULE\n version 1\n headerlen <8 byte length>\n\n -
Text header, containing the serialized fields of an
AssemblyModule(currently JSON until I have a good reason to switch. I tried TOML, not a fan of how it presents nested structures):{ "modname": "test-module", "imports": [ "other-mod", "my-mod" ], "definitions": { "CONST": { "U16": 4660 } }, "pub_definitions": { "START": { "U32": 536870912 }, "OTHER": { "U32": 74565 } }, "labels": { "PRIVATE": [ false, 70000 ], "PUBLIC": [ true, 0 ] }, "linker_patches": [ { "LabelNewOffset": { "patch_at": 100, "patch_size": "U16", "unpatched_value": 50 } }, { "ImportLabelRef": { "patch_at": 200, "patch_size": "U32", "import_module": { "module": "my-mod", "member": "LABEL" } } } ] } \n\n -
The module's code as binary, followed by
\n\n.
-
- I wanted something with a human-readable header that would enable me to debug without writing a custom
-
A linker, with compiler driver support for linking both at build time and as a standalone step:
- Having imports and cross-module references implies the existence of a tool to resolve them, so here we have it.
chasm assemblewill link the provided assembly modules into a single binary by default.- If
--no-linkis specified, the modules will be serialized and written to disk. chasm linktakes a list of paths pointing to serialized assembly modules and links them into a single binary.
Next Steps
Adding the module system was a significant step for Chasm and I'm overall pretty happy with the result. I'm sure there are things that will need cleanup or refactoring, but for now it's working as intended. Now that we have it in place there's a couple directions to go:
-
Venture back to the embedded side of things and integrate with more of the hardware. The TM4C123GXL kit I have has some pluggable modules, including a screen, that I may try to draw simple graphics on.
- A small embedded project would be a good opportunity to start writing a board-specific standard library, e.g. startup code, vtable setup, GPIO initialization, etc.
-
Build an editor. I really want the ultimate Chasm development experience to be tuned specifically to the language and toolchain. I've developed some language plugins for VSCode in the past and feel it's just too large of a dependency to take, let alone the design choices I'd be forced to make that wouldn't really reflect the spirit of this project.
-
Start the high-level Chasm language. Probably won't tackle this yet, but some ideas are cooking.