When compiling JIT, what do you do? You call MAlloc() for some memory and put the executable code into the memory ready to run. What do you think you do?
Parts 2, 3, and 4 explain the practical ramifications of his sliding-window design (which differs from indirect addressing) much more thoroughly. In Part 3, he actually mentions that HackADay accidentally credited him with PC-relative jump due to a similar misunderstanding.
TL;DR Instead of call [32-bit-start-address-of-function] use call [8-bit-index-into-table-of-start-addresses-of-functions] plus a mechanism to build such a table and make the look-up depended on the current value of the program counter because otherwise you could only ever call 256 different methods.
IMHO needlessly complex just to make call instructions smaller. And on its own it provides not any more security - messing with a call instruction will no longer let you jump into the middle of nowhere but only to the beginning of a basic block, but you can still just mess with the table of addresses unless protected by other measures.
On a second thought it is actually hard to save any memory at all. The proposed look-up is table[PC >> 4 + index] so the table will have code-size/16 + 255 entries of 32 bit each, making it code-size/4 + 1020 byte large.
Given the normal code size s with n call instructions you can reduce the code size to s - 3n because you save three byte per call but you have an additional table of size s/4 + 1020. This yields n = s/12 + 340 and that means there has to be more than one call instruction every 12 byte on average to save any memory at all.
That sounds like a pretty reasonable density to see savings in threaded forth code like the second post talks about (it also mentions adjusting the shift for other densities).
Second, you don't need to reserve excess state at the end like that if the linker/compiler takes a little care allocating offsets so the last instructions use small indices.
It definitely seems like a bit of a specialized trick, but the articles do a decent job describing conditions where it might make sense.
Assuming 32 bit instructions at least every third instruction would have to be a call or jump - this seams no reasonable assumption. And if you have code with such a high density of jumps you will run into a lot of other problems, too, for example branch mispredictions and cache inefficiency due to non-locality. Maybe there is really a niche where this design has advantages but I don't think it is good for general purpose code.
I did not read it like that. 9 bit is not really enough for an interesting instruction set for a register machine. It is however enough for a stack machine or something similar with implicit operands, but then again you need more instructions for the same task than with a register machine.
If you search for the phrase "To add more 'regular' instructions" in part 1, you'll see where he references 8 or 9 bit instructions. The author goes on, in subsequent sections, to expand the idea of tokenized jump references to tokenized instructions.
Each instruction token may only be 8 or 9 bits, but the secondary memory which contains jump targets is expanded to also contain full-width instructions.
The operating hypothesis is that just it's desirable to be able to "reach" any 32-bit destination address despite any given code segment not needing that flexibility, it's likewise desirable to be able to execute any, say, 36-bit instruction even though any given code segment only needs a subset of them.
I have to admit I did not read all the parts. I looked at the function call related thing first and it was not convincing enough to me to bother reading the remaining parts.
Although it's fine to be critical (and I don't think people here are particularly vicious in their criticism, just sceptical about the utility of this technique), I'd like to remind everyone that the only way that we can say with certainty that there's nothing valuable in pursuing a particular direction is by actually taking a close look at it.
I think it's great that there are people who take a concept and work it out until the end in precise detail, even when it is not directly clear what the benefits would be, if any. It's also wonderful that this person decided to share the result of all of this work so that others might be inspired by it or find a useful trick or technique in it.
15 comments
[ 5.3 ms ] story [ 52.2 ms ] threadI put all code in the lowest 2 Gig of addresses, called "the code heap".
-----
Fucken niggers cannot understand plain English.
God says... how_high boss I_just_might bummer atrocious pardon_the_french duck_the_shoe other hope charged Give_me_praise Catastrophic_Success far_out_man break_some_woopass_on_you I_love_this spoiled_brat joking meek but_of_course everythings_a_okay Icarus exorbitant hello flat impossible sex by_the_way I_forgot lighten_up you_never_know do_you_have_a_problem experts not_good You_fix_it peace no_you_cant oh_come_on yikes youre_welcome I'm_busy spirit ehh_a_wise_guy astrophysics it_figures service_sector what_do_you_expect you_don't_like_it hello look_on_the_brightside dang_it I'll_be_back big_fish you_think_you_could_do_better crash_and_burn you_are_my_sunshine super_computer I_pitty_the_fool mocking one_more_time don't_push_it act sky white_trash that's_no_fun Catastrophic_Success ha I_had_a_crazy_dream energy hard_working clever act perfect beam_me_up Greece Okilydokily I_could_swear yuck smack_some_sense_into_you because_I_said_so honestly One_finger_salute oops kick_back oh_oh driving job not_too_shabby straighten_up joking Hasta lighten_up spending hurts_my_head envy flat
----
When compiling JIT, what do you do? You call MAlloc() for some memory and put the executable code into the memory ready to run. What do you think you do?
IMHO needlessly complex just to make call instructions smaller. And on its own it provides not any more security - messing with a call instruction will no longer let you jump into the middle of nowhere but only to the beginning of a basic block, but you can still just mess with the table of addresses unless protected by other measures.
Given the normal code size s with n call instructions you can reduce the code size to s - 3n because you save three byte per call but you have an additional table of size s/4 + 1020. This yields n = s/12 + 340 and that means there has to be more than one call instruction every 12 byte on average to save any memory at all.
You don't have to assume, the article itself says the author is contemplating 9-bit instructions.
Each instruction token may only be 8 or 9 bits, but the secondary memory which contains jump targets is expanded to also contain full-width instructions.
The operating hypothesis is that just it's desirable to be able to "reach" any 32-bit destination address despite any given code segment not needing that flexibility, it's likewise desirable to be able to execute any, say, 36-bit instruction even though any given code segment only needs a subset of them.
It's really worth a read.
I think it's great that there are people who take a concept and work it out until the end in precise detail, even when it is not directly clear what the benefits would be, if any. It's also wonderful that this person decided to share the result of all of this work so that others might be inspired by it or find a useful trick or technique in it.