It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are a few basic problems to be solved. One that comes to
mind immediately is branch addresses. If the instruction stream
is compressed, any bit of the compressed stream can start an
instruction, and calculating the target of a branch instruction
is not straightforward. To avoid having to decode the whole
program, it would be necessary to create blocks of some sort,
which could be decoded on loading into a suitable cache level.
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
Branch targets could then be in the form block,instruction in
block.
There would have to be an upper limit on the number
of instructions per block, leading to some waste for easily
compressible instructions. Also, instructions which no
longer fit in a block would reduce the efficieny.
One would also have to look at PC-relative branches; these
would probably become block-relative + instruction in block.
Writing binutils for this kind of architecture would be fun,
in the sense of absoutely no fun at all. Object code would
probably be uncompressed, with the poor guy implementing the
linker having to do all the dirty work.
The ISA to be compressed would not be limited by having to
fit into a container determined by byte or word sizes.
If one so desired, one could chose an ISA based on any--- Synchronet 3.22a-Linux NewsLink 1.2
bit size, although having instructions based on an n-bit
package size (with multiples) would still make sense for
further decoding of the micro-ops.
Other thoughts?
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute.
Thomas Koenig <tkoenig@netcologne.de> posted:
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
Pitfalls...
There are a few basic problems to be solved. One that comes to
mind immediately is branch addresses. If the instruction stream
is compressed, any bit of the compressed stream can start an
instruction, and calculating the target of a branch instruction
is not straightforward. To avoid having to decode the whole
program, it would be necessary to create blocks of some sort,
which could be decoded on loading into a suitable cache level.
Realistically, adding 3-or-4-bits to IP (or equally restricting
IP to 60-DoubleWord address-bits would allow bit indexing at
FETCH.
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
What happens when somebody (ld.so) writes into code space ??
What happens when SSD writes into code space ??
Branch targets could then be in the form block,instruction in
block.
Or bit addresses (see above) and leave out blocks altogether.
There would have to be an upper limit on the number
of instructions per block, leading to some waste for easily
compressible instructions. Also, instructions which no
longer fit in a block would reduce the efficieny.
Packet and trace caches have this problem. After building a
packet-cache (1991) and seeing the instruction density (60%
filled and less than 1/2 as dense as normal instruction
cache--I decided to go a different direction for my current
6-wide machine.
One would also have to look at PC-relative branches; these
would probably become block-relative + instruction in block.
Bit addressing solves this, too !!
Writing binutils for this kind of architecture would be fun,
For a perverse definition of "fun".
in the sense of absoutely no fun at all. Object code would
probably be uncompressed, with the poor guy implementing the
linker having to do all the dirty work.
Imaging the poor guy trying to debug::
ADD IP,R7,#1234567
The ISA to be compressed would not be limited by having to
fit into a container determined by byte or word sizes.
Not at all--see Mill.
If one so desired, one could chose an ISA based on any
bit size, although having instructions based on an n-bit
package size (with multiples) would still make sense for
further decoding of the micro-ops.
Other thoughts?
On Thu, 24 Sep 2026 20:04:24 -0000 (UTC), Thomas Koenig <tkoenig@netcologne.de> wrote:
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute.
That's certainly true of the schemes I've been coming up with.
But since it takes so much time to fetch stuff from DRAM, some
machines offer a mode where instructions are actually decrypted using
a block cipher after being fetched!
Given that, presumably the time cost of doing "real" compression on
program code is not excessive.
So, why doesn't anyone do this?
I can think of many reasons why such a
scheme could cause agony... but I also see a simple, plain
alternative.
Why not just have a compressed disk format for executable files in an operating system? Unpack them into conventional format when loading
them into memory.
That saves space where it is more doable, where the programs are lying idle, so they can be compressed with context
obtained from their whole length.
It doesn't achieve as much, but it avoids all the branching headaches.--- Synchronet 3.22a-Linux NewsLink 1.2
John Savard
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are a few basic problems to be solved. One that comes to
mind immediately is branch addresses. If the instruction stream
is compressed, any bit of the compressed stream can start an
instruction, and calculating the target of a branch instruction
is not straightforward. To avoid having to decode the whole
program, it would be necessary to create blocks of some sort,
which could be decoded on loading into a suitable cache level.
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
Branch targets could then be in the form block,instruction in
block. There would have to be an upper limit on the number
of instructions per block, leading to some waste for easily
compressible instructions. Also, instructions which no
longer fit in a block would reduce the efficieny.
One would also have to look at PC-relative branches; these
would probably become block-relative + instruction in block.
Writing binutils for this kind of architecture would be fun,
in the sense of absoutely no fun at all. Object code would
probably be uncompressed, with the poor guy implementing the
linker having to do all the dirty work.
The ISA to be compressed would not be limited by having to
fit into a container determined by byte or word sizes.
If one so desired, one could chose an ISA based on any
bit size, although having instructions based on an n-bit
package size (with multiples) would still make sense for
further decoding of the micro-ops.
Other thoughts?
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
Thomas Koenig <tkoenig@netcologne.de> posted:<snip>
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
What happens when somebody (ld.so) writes into code space ??
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are a few basic problems to be solved. One that comes to
mind immediately is branch addresses. If the instruction stream
is compressed, any bit of the compressed stream can start an
instruction, and calculating the target of a branch instruction
is not straightforward. To avoid having to decode the whole
program, it would be necessary to create blocks of some sort,
which could be decoded on loading into a suitable cache level.
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
Branch targets could then be in the form block,instruction in
block. There would have to be an upper limit on the number
of instructions per block, leading to some waste for easily
compressible instructions. Also, instructions which no
longer fit in a block would reduce the efficieny.
One would also have to look at PC-relative branches; these
would probably become block-relative + instruction in block.
Writing binutils for this kind of architecture would be fun,
in the sense of absoutely no fun at all. Object code would
probably be uncompressed, with the poor guy implementing the
linker having to do all the dirty work.
The ISA to be compressed would not be limited by having to
fit into a container determined by byte or word sizes.
If one so desired, one could chose an ISA based on any
bit size, although having instructions based on an n-bit
package size (with multiples) would still make sense for
further decoding of the micro-ops.
Other thoughts?
quadibloc@invalid.com (John Savard) posted:
On Thu, 24 Sep 2026 20:04:24 -0000 (UTC), Thomas Koenig
<tkoenig@netcologne.de> wrote:
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute.
That's certainly true of the schemes I've been coming up with.
But since it takes so much time to fetch stuff from DRAM, some
machines offer a mode where instructions are actually decrypted using
a block cipher after being fetched!
I remember (I think) IBM compressing pages, but not cache lines.
On Thu, 24 Sep 2026 20:04:24 -0000 (UTC)
Thomas Koenig <tkoenig@netcologne.de> wrote:
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
Can I ask what you find special in AMD's micro-op cache relatively to Intel's?
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
On 2026-09-24 4:04 p.m., Thomas Koenig wrote:
It seems that the different CISC and compressed instruction sets
are ad-hoc (and not very good) attempts at compression of the
actual instructions that machines execute. Pure RISCS have very
little compression, RISC-V has some, CISC has some more. AMD's
micro-op cache goes some way towards this when it splits
x86_64 instructions into micro-ops.
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are a few basic problems to be solved. One that comes to
mind immediately is branch addresses. If the instruction stream
is compressed, any bit of the compressed stream can start an
instruction, and calculating the target of a branch instruction
is not straightforward. To avoid having to decode the whole
program, it would be necessary to create blocks of some sort,
which could be decoded on loading into a suitable cache level.
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
Branch targets could then be in the form block,instruction in
block. There would have to be an upper limit on the number
of instructions per block, leading to some waste for easily
compressible instructions. Also, instructions which no
longer fit in a block would reduce the efficieny.
One would also have to look at PC-relative branches; these
would probably become block-relative + instruction in block.
Writing binutils for this kind of architecture would be fun,
in the sense of absoutely no fun at all. Object code would
probably be uncompressed, with the poor guy implementing the
linker having to do all the dirty work.
The ISA to be compressed would not be limited by having to
fit into a container determined by byte or word sizes.
If one so desired, one could chose an ISA based on any
bit size, although having instructions based on an n-bit
package size (with multiples) would still make sense for
further decoding of the micro-ops.
Other thoughts?
Micro-ops are the opposite of compressed instructions. They are
typically wider than the instructions encoded.
Writing software for dumps of code and other such things is much more expensive (takes more time) when things are not byte aligned.
Even with compressed instructions the savings in memory space is
typically < 40%. The savings in execution time is far less.
<snip>
Thomas Koenig <tkoenig@netcologne.de> posted:
For example, one could make the Icache into a micro-op-store
and decode on fetching from L2.
What happens when somebody (ld.so) writes into code space ??
Generally it doesn't. The code space is mapped into
read-only pages. The RTLD updates the PLT and GOT, not
the code.
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are different levels at (and ways in) which compression can be
applied, with very different tradeoffs.
E.g. I remember an article years ago (maybe from ETHZ?) doing
compression on bytecode where common sequences of bytecode were turned
into something akin to "functions", IOW a kind of "common sub-expression elimination" that's also reminiscent of LempelZiv. The result was
compressed bytecode executable "without decompression".
You can gain some savings by playing at the bit-level to take advantage
of Hufmann-style compression, but another way to attack the problem is
to design an ISA where you can easily share common sequences of
instructions (I guess you could call it "outlining"), for example by providing a very concise way to call and write short functions.
And maybe the micro-architecture could be taught to inline those calls
(I guess it would naturally happen if you use a trace cache).
=== Stefan
On 9/25/2026 10:46 AM, Stefan Monnier wrote:
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are different levels at (and ways in) which compression can be applied, with very different tradeoffs.
E.g. I remember an article years ago (maybe from ETHZ?) doing
compression on bytecode where common sequences of bytecode were turned
into something akin to "functions", IOW a kind of "common sub-expression elimination" that's also reminiscent of LempelZiv. The result was compressed bytecode executable "without decompression".
BGBCC often does something similar with prologs and epilogs:
If more than a certain number of registers are saved/restored, the
sequences are folded off and then a call/branch is used to do the actual bulk save/restore.
Luckily, works regardless of ISA.
Don't want to go too small though for the limit, or else it may reduce performance and/or make code density worse (if very small).
So, a usual cutoff is around 6 or 8 loads/stores (or 12 or 16 registers
if one has Load/Store Pair).
BGB <cr88192@gmail.com> posted:
On 9/25/2026 10:46 AM, Stefan Monnier wrote:
So, the idea of using actual compression seems interesting.
This has been tried in the past, but it didn't catch on.
There are different levels at (and ways in) which compression can be
applied, with very different tradeoffs.
E.g. I remember an article years ago (maybe from ETHZ?) doing
compression on bytecode where common sequences of bytecode were turned
into something akin to "functions", IOW a kind of "common sub-expression >>> elimination" that's also reminiscent of LempelZiv. The result was
compressed bytecode executable "without decompression".
BGBCC often does something similar with prologs and epilogs:
If more than a certain number of registers are saved/restored, the
sequences are folded off and then a call/branch is used to do the actual
bulk save/restore.
Luckily, works regardless of ISA.
Don't want to go too small though for the limit, or else it may reduce
performance and/or make code density worse (if very small).
So, a usual cutoff is around 6 or 8 loads/stores (or 12 or 16 registers
if one has Load/Store Pair).
How does the hardware recognize that this sequence of STs (prologue)
or LDs (epilogue) are using a dense set of addresses so that one only
needs to access the tag once (or twice) instead of (12-16) times?
How does the hardware recognize that this sequence stays all within
one page and thus one TLB hit suffices for 12-16 memory references ??
And if performed with Call/Return semantics, how do non-memory instructions get through the Decoder and into Reservation Stations--while those strings
of memory references get performed ??
Instead, if one uses ENTER/EXIT, one address calculation satisfies many accesses, one TLB lookup satisfies all those accesses, and the cache tag
is only accesses once (or twice).
You also get the ability to scoreboard all the registers being written, scoreboard all the registers being read, read the return address before reading the preserved registers, and begin fetch before all the loads
have been performed.
And, ENTER is only 1 32-bit instruction without any flow control while
EXIT is 1 32-bit instruction with flow control faster than data access.
*: RP2's design essentially assumes a positive correlation between match length and distance. This is true for general data, but less true for executable code (where all matches tend to be shorter, but have a wider distribution). LZ4 does better for "hey, here is 12 bytes from 60K
ago" (which it can encode in 3 bytes, vs needing 4 in RP2; however an 8K sliding window means RP2 matches could operate within the 3-byte regime; l=4..67, d=8K).
Ironically, I also found that RP2 was less ideal for matches entirely
within fixed-size 256 byte blocks (with a window limited to a single
block), as again, the length and distance became negatively correlated
(such a negative correlation was also noted in raster image compression, typically in the form of long matches with short distances).
The most common RLE case was partly addressed in the more recent RP2B variant by adding a 2-byte Len=4..131, Dist=1 case.
Debatable:
Maybe could reuse the retired "Long Match" case for a new 2-byte cases,
say:
-a dddlllll-rrr01111-a (len=4..35, dist=1..8, raw=0..7)
Which could hit on some cases that miss with the added RP2B case:
-a lllllll0-01111111-a (RP2B, len=4..131, dist=1, raw=0)
And, complements the existing 2-byte match:
-a dddddddd-dlllrrr0-a (len=3..10, dist=0..511, raw=0..7)
Though, maybe a case could be made for 3-byte, say:
-a dddddddd-llllllll-rrr01111-a (l=4..259, d=1..256)
Or, errm, both:
-a-a-a-a-a-a-a-a-a-a ddlllll0-rrr01111-a (len=4..35, dist=1..4, raw=0..7)
-a dddddddl-lllllll1-rrr01111-a (len=4..259, d=1..128, raw=0..7)
May need to do some testing to figure out which possibility would work better here.
On 9/25/2026 5:59 PM, MitchAlsup wrote:
BGB <cr88192@gmail.com> posted:
How does the hardware recognize that this sequence of STs (prologue)
or LDs (epilogue) are using a dense set of addresses so that one only
needs to access the tag once (or twice) instead of (12-16) times?
Load/Store Pair gets it down to 6/8 ...
I guess in theory a CPU could recognize that pairs of LDP/SDP
instructions are, say, actually LDQ/SDQ operations.
BGB <cr88192@gmail.com> writes:
On 9/25/2026 5:59 PM, MitchAlsup wrote:
BGB <cr88192@gmail.com> posted:
How does the hardware recognize that this sequence of STs (prologue)
or LDs (epilogue) are using a dense set of addresses so that one only
needs to access the tag once (or twice) instead of (12-16) times?
Load/Store Pair gets it down to 6/8 ...
I guess in theory a CPU could recognize that pairs of LDP/SDP
instructions are, say, actually LDQ/SDQ operations.
It's not theory. Many of the ARM server-grade CPUs in the last decade have supported converting LDP/SDP into a single twice as wide potentially
*atomic* memory operation.
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 74 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 121:08:33 |
| Calls: | 1,194 |
| Files: | 1,352 |
| Messages: | 290,208 |