Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ARM64 Assembler+

Assembler+ is a two-pass ARM64 assembler that supports three input formats:

  1. Pre-tokenized (--tokenized, default) — the CS241 scanner output format (TOKEN_TYPE lexeme)
  2. Raw assembly (--raw) — standard ARM64 assembly text
  3. High-level pseudocode (--high) — a readable, C-like syntax

All three modes produce identical machine code for equivalent programs.

Building

make        # produces ./asm
make clean  # removes the binary

Requires a C++20-compatible compiler (e.g. g++ or clang++).

Usage

asm [OPTIONS] [FILE]
Flag Description
--tokenized Input is pre-tokenized format (default)
--raw Input is raw ARM64 assembly
--high Input is high-level pseudocode
--dump-ir (--high only) Print IR to stderr instead of assembling
--help, -h Show usage

If FILE is omitted or is -, reads from stdin. Binary output goes to stdout; labels are printed to stderr.

./asm --raw program.s > program.bin
./asm --high program.hl > program.bin
./asm --high --dump-ir program.hl        # inspect the IR without assembling
cat tokens.txt | ./asm > program.bin

Supported Instructions

Instruction Syntax Description
add add xd, xn, xm xd = xn + xm
sub sub xd, xn, xm xd = xn − xm
mul mul xd, xn, xm xd = xn × xm (low 64 bits)
smulh smulh xd, xn, xm xd = (xn × xm) >> 64 (signed)
umulh umulh xd, xn, xm xd = (xn × xm) >> 64 (unsigned)
sdiv sdiv xd, xn, xm xd = xn ÷ xm (signed)
udiv udiv xd, xn, xm xd = xn ÷ xm (unsigned)
cmp cmp xn, xm Set flags for xn − xm
b b label Unconditional branch
b.cond b.eq label Conditional branch (eq, ne, lt, le, gt, ge, hs, lo, hi, ls)
br br xn Branch to register
blr blr xn Branch-with-link to register
ldr ldr xd, offset PC-relative load
ldur ldur xd, [xn, imm] Load from base + offset
stur stur xd, [xn, imm] Store to base + offset
.8byte .8byte value Emit a 64-bit constant

High-Level Syntax

The --high mode accepts a pseudocode language that is lowered to ARM64 instructions before assembly.

Syntax Lowers to
label <name> <name>:
x1 = x2 + x3 add x1, x2, x3
x1 = x2 - x3 sub x1, x2, x3
x1 = x2 * x3 mul x1, x2, x3
x1 = x2 / x3 sdiv x1, x2, x3
x1 = x2 % x3 sdiv → mul → sub sequence
x1 = x2 add x1, x2, xzr (move)
x1 = *x2 ldur x1, [x2, 0]
x1 = *(x2 + 8) ldur x1, [x2, 8]
*x1 = x2 stur x2, [x1, 0]
*(x1 + 8) = x2 stur x2, [x1, 8]
if x1 == x2 goto lbl cmp x1, x2 + b.eq lbl
if x1 != x2 goto lbl cmp x1, x2 + b.ne lbl
if x1 < x2 goto lbl cmp x1, x2 + b.lt lbl
if x1 <= x2 goto lbl cmp x1, x2 + b.le lbl
if x1 > x2 goto lbl cmp x1, x2 + b.gt lbl
if x1 >= x2 goto lbl cmp x1, x2 + b.ge lbl
goto lbl b lbl
call x1 blr x1
ret br x30
.8byte val .8byte val
# comment ignored

Example

# sum x1 += x3 while x1 != x2
label loop
if x1 == x2 goto done
x1 = x1 + x3
goto loop
label done
ret

With --dump-ir, this produces the following target-independent IR:

loop:
  CMP_BRANCH x1 == x2, done
  ADD x1, x1, x3
  BRANCH loop
done:
  RET

Which is then lowered to the equivalent raw assembly:

loop:
    cmp x1, x2
    b.eq done
    add x1, x1, x3
    b loop
done:
    br x30

Project Structure

The --high pipeline follows a classic compiler architecture with an explicit IR lowering pass:

 Source code          IR                  Tokens              Binary
┌──────────┐    ┌───────────┐    ┌──────────────┐    ┌──────────────┐
│HighLevel │───▶│ IR        │───▶│ IRCodeGen    │───▶│ Assembler    │
│Parser    │    │ (target-  │    │ (instruction │    │ (two-pass    │
│          │    │  indep.)  │    │  selection)  │    │  encode)     │
└──────────┘    └───────────┘    └──────────────┘    └──────────────┘
                  --dump-ir

The --raw and --tokenized pipelines skip the IR and feed tokens directly into the assembler.

├── main.cpp           # Entry point — mode selection & I/O
├── token.h            # Token struct, TokenType enum, I/O operators
├── lexer.h            # TokenizedLexer (CS241 format), RawAsmLexer (raw text)
├── ir.h               # IRInstruction — target-independent intermediate representation
├── highlevel.h        # HighLevelParser — pseudocode → IR
├── ir_codegen.h       # IRCodeGen — IR → ARM64 Token lowering (instruction selection)
├── symbol_table.h     # SymbolTable — label definition & lookup
├── encoder.h          # Encoder — instruction validation & machine code encoding
├── assembler.h        # Assembler — two-pass orchestration
├── Makefile
└── README.md
Module Responsibility
Token Data types shared across all stages
Lexer Convert input text → Token stream (two strategies)
IR Target-independent intermediate representation (IRInstruction)
HighLevelParser Parse pseudocode → vector<IRInstruction> (frontend)
IRCodeGen Lower IR → ARM64 Token stream (instruction selection)
SymbolTable Track label → address mappings
Encoder Validate operands and emit 32-bit machine code per instruction
Assembler Group tokens into lines, run pass 1 (symbols) and pass 2 (encode + emit)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages