rv-compressed already measured the encoding side of the C extension: how many code points are reserved, which displacement families share five bit positions and are assigned seven different orders, and the four assembler refusals in the compressed register file. None of that is repeated here. What is measured here is the consequence, which is a different subject with a different kind of evidence: a spilling sequence, a hot loop, a jal/ret pair, and what a disassembler has to do to find an instruction boundary.
Every number on this page is an INSTRUCTION COUNT or a BYTE COUNT. They are NOT a speedup, and the reason is the whole of the first section of this course: there is no RISC-V machine, no emulator and no RISC-V linker on this host, so there is nothing here that could measure one. The x86-64 ABI course measured ratios on hardware it could run; this course has no counterpart and does not estimate one.
Almost every “what does compression cost” measurement in the wild is a comparison of two -march settings that differ in more than one letter. Here, two pairs, and the difference between them is the lesson:
$ python3 rvabi.py --run | sed -n '/file without C/,+8p'
file without C insns with C d-insns bytes bytes+c saved 2-byte
cpc.c rv64i 84 80 -4 336 200 40% 75%
cpc.c rv64imafd 74 80 +6 296 200 32% 75%
regs.c rv64i 165 127 -38 660 350 46% 62%
regs.c rv64imafd 127 127 +0 508 350 31% 62%
abi.c rv64i 389 368 -21 1556 1296 16% 23%
abi.c rv64imafd 368 368 +0 1472 1296 11% 23%
rv64i vs rv64gc is CONFOUNDED.rv64i has no F and no D, so every double in the source becomes a CALL to a soft-float helper. The instruction count moves because the arithmetic changed, not because the encoding did. The d-insns column for that pair is large — −38 for regs.c — and every bit of that is the SOFT-FLOAT library and not the encoding. A reader who reported that difference as “what compression costs” would be reporting a floating-point software-library decision as an encoding fact, and both numbers would be real, which is exactly what makes the mistake survive review.
rv64imafd vs rv64imafdc is CLEAN. Same base, same M, same A, same F, same D — and the only difference is the trailing C. There the d-insns column is +0, +0 and +6, and the honest answer is that it is ZERO OR ALMOST: the C extension changes how many BYTES an operation takes, and it does not change how many operations the compiler chose to perform.
And the +6 is the interesting cell, because it falsifies “compression never changes the instruction count” just as surely as the two zeros falsify the opposite. cpc.c at rv64imafdc emits six more instructions than at rv64imafd. The reason is a register constraint: the compressed forms can only name x8 to x15, and a register allocator that has to respect that will sometimes reach for a register the uncompressed encoding would not have needed. So the claim “adding C changes bytes and not instructions” is TRUE for two files of three and FALSE for the third, and the artifact prints that as retraction R5 rather than rounding it away.
Read the two pairs side by side and the lesson is general: a -march comparison is a measurement of ONE extension only when every other letter is held fixed, and “hold every other letter fixed” has to be said out loud because the alternative — comparing the base against a gc build and calling the difference a compression result — is a category error that survives review because both numbers are real.
Same source, -O2, and the same ISA apart from the C. MEASURED-ON-BYTES, decoded rather than disassembled:
$ python3 rvabi.py --run | sed -n '/THE THREE SEQUENCES/,+6p'
rv64imafd spill 11 instructions, 44 bytes:
slli a1,a0,1 | slli a2,a0,32 | add a1,a1,a0 | slli a0,a0,35 |
add a0,a0,a2 | addi a2,zero,1 | slli a2,a2,32 | add a0,a0,a2 |
srai a0,a0,32 | add a0,a0,a1 | jalr zero,0(ra)
rv64imafdc spill 11 instructions, 26 bytes:
slli a1,a0,1 | slli a2,a0,32 | c.add a1,a0 | c.slli a0,35 |
c.add a0,a2 | c.li a2,1 | c.slli a2,32 | c.add a0,a2 |
c.srai a0,32 | c.add a0,a1 | c.jr ra
rv64imafd callret 4 instructions, 16 bytes:
slli a1,a0,1 | add a0,a1,a0 | addiw a0,a0,2 | jalr zero,0(ra)
rv64imafdc callret 4 instructions, 10 bytes:
slli a1,a0,1 | c.add a0,a1 | c.addiw a0,2 | c.jr ra
Eleven instructions and 44 bytes become eleven instructions and 26 bytes. Four and sixteen become four and ten. Every saving here is a c.add for an add, a c.jr ra for a jalr, and a c.addi for an addi — and the instruction count does not move at all.
Read the call/return pair twice, because it is the most compressed thing in the corpus and for a reason that is about the ABI rather than about the encoding. jalr zero, 0(ra) becomes c.jr ra: the compressed form has no operand for the link register because the cell is the return, and that is the same design decision that put the return address in a register in the first place. And addiw a0, a0, 2 becomes c.addiw a0, 2 — the compressed word form exists precisely because RV64 has a 32-bit addw and 32-bit operations are overwhelmingly the common case in compiled code.
$ python3 rvabi.py --run | sed -n '/AND THE SPILL, WHICH/,+6p'
rv64imafd spill 11 instructions, 44 bytes, 0 stores
rv64imafdc spill 11 instructions, 26 bytes, 0 stores
rv64imafd spilln 43 instructions, 172 bytes, 1 stores
rv64imafdc spilln 49 instructions, 124 bytes, 4 stores
spilln is the function that spills eight callee-saved registers, and the numbers are the most interesting on this page: 124 bytes against 172 is a 28% saving, and the instruction count went UP, from 43 to 49. Six more instructions, four more stores, and a frame that is smaller in the compressed build.
So a callee that spills eight registers does more work in the compressed build than the same callee in the uncompressed one, and the reason is not that compression made anything slower. The compressed store forms have register constraints — c.sd and c.sw can only name x8 to x15 — and when the value being saved is not in that window the allocator has to move it there first, and the move is an instruction. The compressed encoding is not uniformly shorter per operation: it is shorter per operation THAT IT ENCODES, and the register allocator reacts to that by storing differently. That is retraction R7, and it is a retraction because the obvious prediction “the callee that spills less does less work” measured the other way round.
A whole-function byte count measures the wrong code: the prologue runs once and the body runs n times. So here is the body — the instructions between the first branch and the backward branch, which is the part that repeats:
$ python3 rvabi.py --run | sed -n '/THE HOT LOOP/,+5p'
rv64imafd hot: 12 instructions, 48 bytes whole; the loop body is
11 instructions and 44 bytes
rv64imafdc hot: 12 instructions, 30 bytes whole; the loop body is
11 instructions and 28 bytes
44 bytes of body become 28. And read which instructions did not compress: lw a3, 0(a0) stays four bytes as c.lw a3, 0(a0) — because the register constraints refuse it — and slli a4, a3, 1 stays four bytes as well, because the shift amount does not fit the compressed six-bit field. Compression is not a uniform discount on a function; it is a per-instruction question, and the answer is “no” a surprising fraction of the time. That is the encoding side arriving as a consequence.
On x86-64 and AArch64 an instruction’s length is a CONSTANT, so a disassembler’s loop is:
p += 4
and the loop cannot be wrong. On RISC-V an instruction is TWO or FOUR bytes and the loop is:
if (halfword & 3) == 3: p += 4
else: p += 2
which needs TWO BITS OF THE CURRENT POSITION and nothing else. That is the whole of the guarantee, and it is enormous: a word the decoder cannot name at all still advances p by its own length, so an unmodelled instruction is a gap in the NAMES rather than a gap in the WALK. This is rvdec.py’s central problem and it is the reason a RISC-V disassembler is a different program from an AArch64 one.
And the demonstration is measured rather than asserted. The artifact walks every code section of every corpus object with the rule, and again with the length SUPPLIED from outside, and reports how many instructions each walk found:
$ python3 rvabi.py --run | sed -n '/instructions found by the RULE/,+6p'
object by the RULE with the length SUPPLIED verdict
probe.o 18 13 DIFFERENT
abi_O0.o 927 748 DIFFERENT
abi_O2.o 368 324 DIFFERENT
noflags_O2.o 74 46 DIFFERENT
regs_O2.o 127 87 DIFFERENT
...
6048 instructions found by the two-bit rule and 5012 found with the
length handed in, across 25 code sections of 25 objects.
6,048 against 5,012 — and they are NOT equal, which is the point. If they were equal, the rule would be doing no work: the count would be entirely explained by the symbol table’s instruction sizes. The supplied version is what a disassembler with a symbol table can do and the rule version is what one without must do, and the difference of 1,036 instructions is the population where the two disagree about where a boundary is. So the rule is load-bearing, and it is TWO BITS.
A reader arriving from AArch64 — where the loop is p += 4 and cannot be wrong — has to learn this from an encoding rather than from an argument, because there is no RISC-V machine here to run the wrong answer on. And that is the honest end of the concept: the cost of compression is measurable in bytes, and the cost of NOT having compression is measurable in a two-bit rule, and neither of them needs hardware.
And what none of this can show, stated where a reader would otherwise expect the number: fewer bytes is a statement about instruction FETCH, and even that needs a memory system this course cannot measure. Fewer bytes, the same operations, and no timing. The honest sentence under that table is exactly that one, and a course that put a ratio there would be inventing a measurement it did not take.
p += 4 with p += 2 in the rule and re-run: the walk will desynchronise, and the cross-check’s named-instruction count will collapse while the disagreement count stays at zero — because the two readers would be reading the same wrong boundaries. That is the failure mode a decoder has that a timing measurement does not: it can be confidently wrong and self-consistent. Then restore it and confirm the count returns.)regs.c at -march=rv64i and at -march=rv64gc, count instructions, and get −38. Then compile at -march=rv64imafd and -march=rv64imafdc and get zero. The two answers differ by 38 and the difference is entirely floating-point software. Hold every other letter fixed or do not report the comparison — and the way to know you did not hold them fixed is that a number is suspiciously large.)hot loop above, lw a3, 0(a0) does not become two bytes. It is not because the instruction is important; it is because c.lw can only name rd' in x8–x15 and a0 and a3 are outside that window. Now write the same load with a pointer in s0 and a destination in a2 and watch the same instruction become two bytes. Compression is a property of the register numbers a program happens to use, and that is why the “percentage of bytes saved” column is a statement about one compiler’s register allocation.)Backwards, rv-compressed is the encoding half this page is the consequence of, and the two are deliberately not merged: reserved code points and displacement permutations are one subject, and what a spill and a hot loop actually become is another. Concept 3 is where the x8–x15 window is introduced as a register-file fact, and this page is where it turns into an instruction-count difference.
Across architectures, isa-length is the general question this page answers for one architecture, and a64-encoding is the case where the answer was p += 4 and the field map is the curriculum. RISC-V has the same goal as AArch64 — keep the decoder simple — and pays for it differently: not with a constant length, but with a two-bit rule that is cheap to state and impossible to verify by eye.
And rv-verify is where the unmodelled words in that 6,048-versus-5,012 difference get named. This page counts the gap; that one decodes what is in it.
Forwards, this is the last RISC-V concept, and the thing it hands to the next architecture is a habit rather than a fact: when you see a size comparison, ask which other things changed at the same time. The -march confound above is the same shape as the soft-float surprise in rv-isa and as the F/D removal in the ABI course before it, and all three are the same mistake wearing different clothes.