News for hackers. Stuff that matters.
Re:Writing a register based VM in less than 125 lines of C code by userbinator on Thursday December 09, 2021 @12:12AM (#29494059)
Our machine has W=UINT16_MAX words, of N=16 bits each

Did the seemingly-off-by-one-error catch anyone else? A 16-bit integer has 65536 values, so you'll be going one past the end of the virtual RAM if you attempt to access address FFFFh.

With this simple trick, we can avoid writing a switch statement with 16 (+1) cases

Personally, I prefer having a switch since it's more obvious which opcode goes where. Probably makes the code a little shorter too (and is that another off-by-one, this time in the other direction?)

Also, I understand the need to keep a "learning architecture" simple, but without byte-wide memory access, you'll quickly realise how annoying it is to work with actual bytes. For example, any string handling, unless you decide to use UTF-16 instead. I think a sweet spot is 16-bit address and 8-bit data, with register pairs for 16-bit operations, much like the popular Z80 and the 8080/8085 that preceded it.

The DEC Alpha is a real architecture that made a similar mistake and later had to be extended to fix it: https://en.wikipedia.org/wiki/DEC_Alpha#Byte-Word_Extensions...

Re:Writing a register based VM in less than 125 lines of C code by Joker_vD on Thursday December 09, 2021 @08:19AM (#29496993)
A language lawyer quiz: if the definition is changed to

    uint16_t mem[UINT16_MAX + 1] = {0};
is it guaranteed to work on platforms where unsigned int is 16-bit wide? What about platforms where void* is 16-bit wide (hint: &mem[65536] must be a valid pointer value that must compare unequal from &mem[0])?
Re:Writing a register based VM in less than 125 lines of C code by im3w1l on Thursday December 09, 2021 @09:18AM (#29497570)
> &mem[65536] must be a valid pointer value

explain

Re:Writing a register based VM in less than 125 lines of C code by Joker_vD on Thursday December 09, 2021 @09:47AM (#29497883)
C99 draft, 6.5.6.8:

    Moreover, if the expression P points to the last element of an array object, 
    the expression (P)+1 points one past the last element of the array object,
    and if the expression Q points one past the last element of an array object,
    the expression (Q)-1 points to the last element of the array object. If both
    the pointer operand and the result point to elements of the same array object,
    or one past the last element of the array object, the evaluation shall not
    produce an overflow; otherwise, the behavior is undefined. If the result points
    one past the last element of the array object, it shall not be used as the
    operand of a unary * operator that is evaluated.
Re:Writing a register based VM in less than 125 lines of C code by bluetomcat on Thursday December 09, 2021 @04:32AM (#29495444)
> Personally, I prefer having a switch since it's more obvious which opcode goes where.

In practice, having a switch is not worse or can even be better for performance because any non-naive optimising compiler will emit a nicely ordered indexed jump table, whereas following function pointers involves more indirection through the addresses of the functions, and has the additional overhead of passing args and preparing/restoring the stack.

Re:Writing a register based VM in less than 125 lines of C code by nomemory on Thursday December 09, 2021 @06:01AM (#29496052)
I've thought of that, and to be honest I think it's worth mentioning in the article. But having set this arbitrary limit to 125 lines, before even starting to sketch things down, made me use the function tables. As a plus, article is intended for beginners, and it would be cool if they learn about function pointers from a practical example.

But from a performance perspective, you are right!

Re:Writing a register based VM in less than 125 lines of C code by MaxBarraclough on Sunday December 12, 2021 @05:41PM (#29534435)
Further to this: this kind of thing is something the Forth community takes seriously. They call these threaded code techniques. [0] (The Gforth interpreter, for instance, gives you the choice of various different techniques.)

Anton Ertl of Gforth fame published a microbenchmark performance comparison of toy interpreters, written in C, run on various CPUs. [1] As you say, the approach using function pointers (call threading) scores poorly.

Interestingly, direct threading and indirect threading are extremely close, with the winner seeming to depend on the specific CPU. [1] Branch-prediction differences seem to be the main reason. There was a 2001 paper on this. [2]

[0] https://www.complang.tuwien.ac.at/forth/threaded-code.html

[1] https://www.complang.tuwien.ac.at/forth/threading/

[2] https://github.com/ForthPapersMirror/Papers/blob/master/Conv...

Re:Writing a register based VM in less than 125 lines of C code by WGH_ on Thursday December 09, 2021 @09:03AM (#29497404)
There's also "computed gotos". It's not the same as function pointers, though.

https://eli.thegreenplace.net/2012/07/12/computed-goto-for-e... - article about them being used in CPython

Re:Writing a register based VM in less than 125 lines of C code by MaxBarraclough on Sunday December 12, 2021 @05:42PM (#29534446)
Re:Writing a register based VM in less than 125 lines of C code by p_l on Thursday December 09, 2021 @10:13AM (#29498209)
The real problem solved by BWX wasn't shifting and masking necessary in handling 8-bit values, as that was handled pretty transparently by compilers, it was how complicated it made dealing with hardware I/O which often expected 8bit or 16bit atomic writes - whereas Alpha could not do shorter i/o than halfword, i.e. 32 bits (All I/O on Alpha was MMIO).

This is visible in form of big split in Alpha hw support (drivers etc) on VMS and OSF/1 between "non-BWX" and "BWX" hardware.

Re:Writing a register based VM in less than 125 lines of C code by jstanley on Thursday December 09, 2021 @06:51AM (#29496354)
> without byte-wide memory access, you'll quickly realise how annoying it is to work with actual bytes. For example, any string handling, unless you decide to use UTF-16 instead. I think a sweet spot is 16-bit address and 8-bit data

Even if you don't want to call it UTF-16, you can just ignore the upper byte of your 16-bit cells and pretend they only store bytes when that's more convenient for you: for practical purposes you won't end up with any less memory available than if your cells were only 8 bits wide, but you have the option of using more when you need it.