decomp

how to play

  1. the assembly is what gcc -Og printed for some C function. the C is that function with holes in it.
  2. fill every hole, then check. right answers lock in. any rewrite that means the same thing in its spot counts, so limit < data[i] is as good as data[i] > limit.
  3. each puzzle starts at 10 and every wrong check costs a point.
  4. the daily is three puzzles, one easy, one medium and one hard, with no hints. everyone gets the same three.
  5. each daily puzzle has a clock around its check button, with more time for harder puzzles and more blanks. when it runs out, what you've typed gets checked, and you keep a share of the points for the blanks you got right.
  6. finish the daily and you're on today's board, ranked by points, then time. a name is optional; without one you're anon.
  7. every past daily is in the calendar on the daily button. replays are practice, so they don't change your streak or the board.
  8. infinite never ends and mixes everything. each puzzle you solve without a wrong check adds one to your streak, and any slip resets it.
  9. drill lets you pick the difficulty and topics, and it's the only mode with hints: a hint costs 1, a reveal 2.

keyboard

tab
next blank
enter
check

about

decomp turns fill-in-the-blank assembly problems into a game. every listing is real output from gcc 14.2 -Og for x86-64.

the design is inspired by the games on dialed.gg.

set in Bricolage Grotesque and Commit Mono.

x86-64 cheatsheet

registers

arguments
%rdi %rsi %rdx %rcx %r8 %r9
return
%rax
8 bytes421
%rax%eax%ax%al
%rdi%edi%di%dil
%r8%r8d%r8w%r8b
qlwb
long, T*intshortchar

writing a 4-byte register zeroes the top 4 bytes. 1- and 2-byte writes leave the rest alone.

operands

$8
the number 8
8
the memory at address 8. no $ means memory
D(Rb,Ri,S)
the memory at D + Rb + Ri*S, with S 1, 2, 4 or 8 and D in bytes
lea
works out that address and stops, so it doubles as math: leaq (%rdi,%rdi,2) is 3*x

arithmetic source first, destination last

add s, d
d += s
sub s, d
d -= s
imul s, d
d *= s
and or xor
d &= s d |= s d ^= s
sal k, d
d <<= k, also spelled shl
sar k, d
d >>= k, signed: copies the sign bit
shr k, d
d >>= k, unsigned: shifts in zeros
neg d
d = -d
not d
d = ~d

widening

movz..
fills with zeros: an unsigned source
movs..
copies the sign bit: a signed source
movzbl
source size, then destination size: byte to long word
cltq
movslq %eax, %rax

there's no movzlq. a movl zero-fills the top half anyway.

compare and jump

cmp a, b
sets the flags for b - a. then jX jumps if b X a
test a, b
sets the flags for b & a. test %rax, %rax compares with 0
Csignedunsigned
== !=je jne
> >=jg jgeja jae
< <=jl jlejb jbe
< 0js

set* writes 1 or 0 into one byte, usually followed by movzbl. cmov* copies only when the test holds. a jump over code usually tests the opposite of the C condition, and a jump backward closes a loop.

switch

cmpq  $6, %rdi        # the last entry
ja    .L9             # default, negatives too
jmp   *.L4(,%rdi,8)   # entry x of the table

a sub before the cmp shifts the cases: entry k is case k plus what it took off. two entries with one label are a shared case, the default label means no case, and a block with no jmp or ret falls through.