2
\$\begingroup\$

This is another follow up from my previous follow up and this time I segmented the stack and memory into bytes so that it follows more closely to most machines. As I stated on my previous peer reviews, this virtual machine is designed to be embedded in a C++ game engine as a static library. I should very much say that this VM will power a subset of C as a scripting language, thus a lot of what the opcodes do is more or less designed around it for C.

I'm still aware that I shouldn't be using dispatch tables because it's non-standard and non-portable but I'm ignoring that.

My questions for this reviewing is: does the VM have enough features to be able to support even a subset of the C programming language?

GitHub (if necessary)

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>


/*  here's the deal ok? make an opcode for each and erry n-bytes!
 * 'l' - int32
 * 's' - int16
 * 'b' - byte | push and pop do not take bytes
 * 'f' - float32
*/

#define INSTR_SET   \
    X(halt) \
    X(pushl) X(pushs) X(pushb) X(pushsp) X(puship)\
    X(popl) X(pops) X(popb) \
    X(wrtl) X(wrts) X(wrtb) \
    X(storel) X(stores) X(storeb) \
    X(loadl) X(loads) X(loadb) \
    X(copyl) X(copys) X(copyb) \
    X(addl) X(uaddl) X(addf) X(subl) X(usubl) X(subf) \
    X(mull) X(umull) X(mulf) X(divl) X(udivl) X(divf) X(modl) X(umodl)\
    X(andl) X(orl) X(xorl) X(notl) X(shl) X(shr) X(incl) X(decl) \
    X(ltl) X(ultl) X(ltf) X(gtl) X(ugtl) X(gtf) X(cmpl) X(ucmpl) X(compf) \
    X(leql) X(uleql) X(leqf) X(geql) X(ugeql) X(geqf) \
    X(jmp) X(jzl) X(jzs) X(jzb) X(jnzl) X(jnzs) X(jnzb) \
    X(call) X(ret) \
    X(nop) \

#define X(x) x,
enum InstrSet{ INSTR_SET };
#undef X

#define X(x) #x ,
const char *opcode2str[] = { INSTR_SET };
#undef X

#define WORD_SIZE       4
#define STK_SIZE        1024*WORD_SIZE  // 4096 4Kb
#define CALLSTK_SIZE    256             // 1024 bytes
#define MEM_SIZE        256*WORD_SIZE   // 1024 bytes

// 'b' for Byte, not bool
struct vm_cpu {
    uint8_t     bStack[STK_SIZE];           // 4096 bytes
    uint32_t    bCallstack[CALLSTK_SIZE];   // 1024 bytes
    uint8_t     bMemory[MEM_SIZE];          // 1024 bytes
    uint32_t    ip, sp, callsp, callbp;     // 16 bytes
};

void debug_print_memory(const struct vm_cpu *restrict vm)
{
    if( !vm )
        return;
    printf("DEBUG ...---===---... Printing Memory...\n");
    uint32_t i;
    for( i=0 ; i<MEM_SIZE ; i++ )
        if( vm->bMemory[i] )
            printf("Memory Index: 0x%x | data: %u\n", i, vm->bMemory[i]);
    printf("\n");
}
void debug_print_stack(const struct vm_cpu *restrict vm)
{
    if( !vm )
        return;
    printf("DEBUG ...---===---... Printing Stack...\n");
    uint32_t i;
    for( i=0 ; i<STK_SIZE ; i++ )
        if( vm->bStack[i] )
            printf("Stack Index: 0x%x | data: %u\n", i, vm->bStack[i]);
    printf("\n");
}
void debug_print_callstack(const struct vm_cpu *restrict vm)
{
    if( !vm )
        return;
    printf("DEBUG ...---===---... Printing Call Stack...\n");
    uint32_t i;
    for( i=0 ; i<CALLSTK_SIZE ; i++ )
        if( vm->bCallstack[i] )
            printf("Call Stack Index: 0x%x | data: %u\n", i, vm->bCallstack[i]);
    printf("\n");
}
void debug_print_ptrs(const struct vm_cpu *restrict vm)
{
    if( !vm )
        return;
    printf("DEBUG ...---===---... Printing Pointers...\n");
    printf("Instruction Pointer: %u\
            \nStack Pointer: %u\
            \nCall Stack Pointer: %u\
            \nCall Stack Frame Pointer: %u\n", vm->ip, vm->sp, vm->callsp, vm->callbp);
    printf("\n");
}

//#include <unistd.h>   // sleep() func
void vm_exec(const uint8_t *code, struct vm_cpu *const vm)
{
    union {
        uint32_t ui;
        int32_t i;
        float f;
        uint16_t us;
        int16_t s;
        uint8_t c[4];
    } conv;
    uint32_t b, a;
    float fa, fb;
    uint16_t usa, usb;

#define X(x) &&exec_##x ,
    static const void *dispatch[] = { INSTR_SET };
#undef INSTR_SET

    if( code[vm->ip] > nop) {
        printf("illegal instruction exception! instruction == \'%" PRIu8 "\' @ %u\n", code[vm->ip], vm->ip);
        goto *dispatch[halt];
        return;
    }
    //printf( "current instruction == \"%s\" @ ip == %u\n", opcode2str[code[vm->ip]], vm->ip );
#ifdef _UNISTD_H
    #define DISPATCH()  sleep(1); goto *dispatch[ code[++vm->ip] ]
#else
    #define DISPATCH()  goto *dispatch[ code[++vm->ip] ]
#endif
    goto *dispatch[ code[vm->ip] ];

exec_nop:;
    DISPATCH();
exec_halt:;
    printf("===================== vm done\n");
    return;

// opcodes for longs
exec_pushl:;    // push 4 bytes onto the stack
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    printf("pushl: pushed %u\n", conv.ui);
    DISPATCH();
exec_pushs:;    // push 2 bytes onto the stack
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    printf("pushs: pushed %u\n", conv.us);
    DISPATCH();
exec_pushb:;    // push a byte onto the stack
    vm->bStack[++vm->sp] = code[++vm->ip];
    printf("pushb: pushed %u\n", vm->bStack[vm->sp]);
    DISPATCH();
exec_pushsp:;   // push sp onto the stack, uses 4 bytes since 'sp' is uint32
    conv.ui = vm->sp;
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    printf("pushsp: pushed sp index: %u\n", conv.ui);
    DISPATCH();
exec_puship:;
    conv.ui = vm->ip;
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    printf("puship: pushed ip index: %u\n", conv.ui);
    DISPATCH();
exec_popl:;     // pop 4 bytes to eventually be overwritten
    vm->sp -= 4;
    printf("popl\n");
    DISPATCH();
exec_pops:;     // pop 2 bytes
    vm->sp -= 2;
    printf("pops\n");
    DISPATCH();
exec_popb:;     // pop a byte
    --vm->sp;
    printf("popb\n");
    DISPATCH();
exec_wrtl:; // writes an int to memory, First operand is the memory address as 4 byte number, second is the int of data.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    vm->bMemory[a] = code[++vm->ip];
    vm->bMemory[a+1] = code[++vm->ip];
    vm->bMemory[a+2] = code[++vm->ip];
    vm->bMemory[a+3] = code[++vm->ip];
    conv.c[0] = vm->bMemory[a];
    conv.c[1] = vm->bMemory[a+1];
    conv.c[2] = vm->bMemory[a+2];
    conv.c[3] = vm->bMemory[a+3];
    printf("wrote int data - %u @ address 0x%x\n", conv.ui, a);
    DISPATCH();
exec_wrts:; // writes a short to memory. First operand is the memory address as 4 byte number, second is the short of data.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    vm->bMemory[a] = code[++vm->ip];
    vm->bMemory[a+1] = code[++vm->ip];
    conv.c[0] = vm->bMemory[a];
    conv.c[1] = vm->bMemory[a+1];
    printf("wrote short data - %u @ address 0x%x\n", conv.us, a);
    DISPATCH();
exec_wrtb:; // writes a byte to memory. First operand is the memory address as 32-bit number, second is the byte of data.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->bMemory[conv.ui] = code[++vm->ip];
    printf("wrote byte data - %u @ address 0x%x\n", vm->bMemory[conv.ui], conv.ui);
    DISPATCH();
exec_storel:;   // pops 4-byte value off stack and into a memory address.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    vm->bMemory[a+3] = vm->bStack[vm->sp--];
    vm->bMemory[a+2] = vm->bStack[vm->sp--];
    vm->bMemory[a+1] = vm->bStack[vm->sp--];
    vm->bMemory[a] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bMemory[a];
    conv.c[1] = vm->bMemory[a+1];
    conv.c[2] = vm->bMemory[a+2];
    conv.c[3] = vm->bMemory[a+3];
    printf("stored int data - %u @ address 0x%x\n", conv.ui, a);
    DISPATCH();
exec_stores:;   // pops 2-byte value off stack and into a memory address.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    vm->bMemory[a+1] = vm->bStack[vm->sp--];
    vm->bMemory[a] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bMemory[a];
    conv.c[1] = vm->bMemory[a+1];
    printf("stored short data - %u @ address 0x%x\n", conv.us, a);
    DISPATCH();
exec_storeb:;   // pops byte value off stack and into a memory address.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    vm->bMemory[a] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bMemory[a];
    printf("stored byte data - %u @ address 0x%x\n", conv.c[0], a);
    DISPATCH();
exec_loadl:;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    conv.c[0] = vm->bStack[++vm->sp] = vm->bMemory[a];
    conv.c[1] = vm->bStack[++vm->sp] = vm->bMemory[a+1];
    conv.c[2] = vm->bStack[++vm->sp] = vm->bMemory[a+2];
    conv.c[3] = vm->bStack[++vm->sp] = vm->bMemory[a+3];
    printf("loaded int data to T.O.S. - %u from address 0x%x\n", conv.ui, a);
    DISPATCH();
exec_loads:;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    conv.c[1] = vm->bStack[++vm->sp] = vm->bMemory[a];
    conv.c[0] = vm->bStack[++vm->sp] = vm->bMemory[a+1];
    printf("loaded short data to T.O.S. - %u from address 0x%x\n", conv.us, a);
    DISPATCH();
exec_loadb:;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    a = conv.ui;
    vm->bStack[++vm->sp] = vm->bMemory[a];
    printf("loaded byte data to T.O.S. - %u from address 0x%x\n", vm->bStack[vm->sp], a);
    DISPATCH();
exec_copyl:;    // copy 4 bytes of top of stack and put as new top of stack.
    conv.c[0] = vm->bStack[vm->sp-3];
    conv.c[1] = vm->bStack[vm->sp-2];
    conv.c[2] = vm->bStack[vm->sp-1];
    conv.c[3] = vm->bStack[vm->sp];
    printf("copied int data from T.O.S. - %u\n", conv.ui);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_copys:;
    conv.c[0] = vm->bStack[vm->sp-1];
    conv.c[1] = vm->bStack[vm->sp];
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    printf("copied short data from T.O.S. - %u\n", conv.us);
    DISPATCH();
exec_copyb:;
    conv.c[0] = vm->bStack[vm->sp];
    vm->bStack[++vm->sp] = conv.c[0];
    printf("copied byte data from T.O.S. - %u\n", conv.c[0]);
    DISPATCH();
exec_addl:;     // pop 8 bytes, signed addition, and push 4 byte result to top of stack
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.i = (int32_t)a + (int32_t)b;
    printf("signed 4 byte addition result: %i == %i + %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_uaddl:;    // In C, all integers in an expression are promoted to int32, if number is bigger then unsigned int32 or int64
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a+b;
    printf("unsigned 4 byte addition result: %u == %u + %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_addf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.f = fa+fb;
    printf("float addition result: %f == %f + %f\n", conv.f, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_subl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.i = (int32_t)a - (int32_t)b;
    printf("signed 4 byte subtraction result: %i == %i - %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_usubl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a-b;
    printf("unsigned 4 byte subtraction result: %u == %u - %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_subf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.f = fa-fb;
    printf("float subtraction result: %f == %f - %f\n", conv.f, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_mull:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.i = (int32_t)a * (int32_t)b;
    printf("signed 4 byte mult result: %i == %i * %i\n", conv.i, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_umull:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a*b;
    printf("unsigned 4 byte mult result: %u == %u * %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_mulf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.f = fa*fb;
    printf("float mul result: %f == %f * %f\n", conv.f, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_divl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    if( !b ) {
        printf("divl: divide by 0 error.\n");
        goto *dispatch[halt];
    }
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.i = (int32_t)a / (int32_t)b;
    printf("signed 4 byte division result: %i == %i / %i\n", conv.i, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_udivl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    if( !b ) {
        printf("udivl: divide by 0 error.\n");
        goto *dispatch[halt];
    }
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a/b;
    printf("unsigned 4 byte division result: %u == %u / %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_divf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    if( !fb ) {
        printf("divf: divide by 0.0 error.\n");
        goto *dispatch[halt];
    }
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.f = fa/fb;
    printf("float division result: %f == %f / %f\n", conv.f, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_modl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    if( !b ) {
        printf("modl: divide by 0 error.\n");
        goto *dispatch[halt];
    }
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.i = (int32_t)a % (int32_t)b;
    printf("signed 4 byte modulo result: %i == %i %% %i\n", conv.i, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_umodl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    if( !b ) {
        printf("umodl: divide by 0 error.\n");
        goto *dispatch[halt];
    }
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a%b;
    printf("unsigned 4 byte modulo result: %u == %u %% %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_andl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a & b;
    printf("4 byte AND result: %u == %i & %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_orl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a | b;
    printf("4 byte OR result: %u == %i | %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_xorl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a ^ b;
    printf("4 byte XOR result: %u == %i ^ %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_notl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = ~a;
    printf("4 byte NOT result: %u\n", conv.ui);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_shl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a << b;
    printf("4 byte Shift Left result: %u == %i >> %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_shr:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a >> b;
    printf("4 byte Shift Right result: %u == %i >> %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_incl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = ++a;
    printf("4 byte Increment result: %u\n", conv.ui);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_decl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = --a;
    printf("4 byte Decrement result: %u\n", conv.ui);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_ltl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = (int32_t)a < (int32_t)b;
    printf("4 byte Signed Less Than result: %u == %i < %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_ultl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a < b;
    printf("4 byte Unsigned Less Than result: %u == %u < %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_ltf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.ui = fa < fb;
    printf("4 byte Less Than Float result: %u == %f < %f\n", conv.ui, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_gtl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = (int32_t)a > (int32_t)b;
    printf("4 byte Signed Greater Than result: %u == %i > %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_ugtl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a > b;
    printf("4 byte Signed Greater Than result: %u == %u > %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_gtf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.ui = fa > fb;
    printf("4 byte Greater Than Float result: %u == %f > %f\n", conv.ui, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_cmpl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = (int32_t)a == (int32_t)b;
    printf("4 byte Signed Compare result: %u == %i == %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_ucmpl:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a == b;
    printf("4 byte Unsigned Compare result: %u == %u == %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_compf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.ui = fa == fb;
    printf("4 byte Compare Float result: %u == %f == %f\n", conv.ui, fa,fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_leql:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = (int32_t)a <= (int32_t)b;
    printf("4 byte Signed Less Equal result: %u == %i <= %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_uleql:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a <= b;
    printf("4 byte Unsigned Less Equal result: %u == %u <= %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_leqf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.ui = fa <= fb;
    printf("4 byte Less Equal Float result: %u == %f <= %f\n", conv.ui, fa, fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_geql:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = (int32_t)a >= (int32_t)b;
    printf("4 byte Signed Greater Equal result: %u == %i >= %i\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_ugeql:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    b=conv.ui;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    a=conv.ui;
    conv.ui = a >= b;
    printf("4 byte Unsigned Greater Equal result: %u == %u >= %u\n", conv.ui, a,b);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_geqf:;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fb=conv.f;
    conv.c[3] = vm->bStack[vm->sp--];
    conv.c[2] = vm->bStack[vm->sp--];
    conv.c[1] = vm->bStack[vm->sp--];
    conv.c[0] = vm->bStack[vm->sp--];
    fa=conv.f;
    conv.ui = fa >= fb;
    printf("4 byte Greater Equal Float result: %u == %f >= %f\n", conv.ui, fa, fb);
    vm->bStack[++vm->sp] = conv.c[0];
    vm->bStack[++vm->sp] = conv.c[1];
    vm->bStack[++vm->sp] = conv.c[2];
    vm->bStack[++vm->sp] = conv.c[3];
    DISPATCH();
exec_jmp:;  // addresses are 4 bytes.
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = conv.ui;
    printf("jmping to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_jzl:;  // check if the first 4 bytes on stack are zero, if yes then jump it.
    conv.c[3] = vm->bStack[vm->sp];
    conv.c[2] = vm->bStack[vm->sp-1];
    conv.c[1] = vm->bStack[vm->sp-2];
    conv.c[0] = vm->bStack[vm->sp-3];
    a = conv.ui;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = (!a) ? conv.ui : vm->ip+1 ;
    printf("jzl'ing to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_jzs:;
    conv.c[1] = vm->bStack[vm->sp];
    conv.c[0] = vm->bStack[vm->sp-1];
    a = conv.ui;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = (!a) ? conv.ui : vm->ip+1 ;
    printf("jzs'ing to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_jzb:;
    conv.c[0] = vm->bStack[vm->sp];
    a = conv.ui;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = (!a) ? conv.ui : vm->ip+1 ;
    printf("jzb'ing to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_jnzl:;
    conv.c[3] = vm->bStack[vm->sp];
    conv.c[2] = vm->bStack[vm->sp-1];
    conv.c[1] = vm->bStack[vm->sp-2];
    conv.c[0] = vm->bStack[vm->sp-3];
    a = conv.ui;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = (a) ? conv.ui : vm->ip+1 ;
    printf("jnzl'ing to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_jnzs:;
    conv.c[1] = vm->bStack[vm->sp];
    conv.c[0] = vm->bStack[vm->sp-1];
    a = conv.ui;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = (a) ? conv.ui : vm->ip+1 ;
    printf("jnzs'ing to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_jnzb:;
    conv.c[0] = vm->bStack[vm->sp];
    a = conv.ui;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    vm->ip = (a) ? conv.ui : vm->ip+1 ;
    printf("jnzb'ing to instruction address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
exec_call:;
    vm->ip++;
    printf("calling address: %u\n", vm->ip);
    vm->bCallstack[++vm->callsp] = vm->ip + 1;
    vm->callbp = vm->callsp;
    vm->ip = code[vm->ip];
    printf("call return addr: %u | frame ptr == %u\n", vm->bCallstack[vm->callsp], vm->callbp);
    goto *dispatch[ code[vm->ip] ];
exec_ret:;
    vm->callsp = vm->callbp;
    printf("callsp set to callbp, callsp == %u\n", vm->callsp);
    vm->ip = vm->bCallstack[vm->callsp--];
    vm->callbp = vm->callsp;
    printf("returning to address: %u\n", vm->ip);
    goto *dispatch[ code[vm->ip] ];
}

uint64_t get_file_size(FILE *pFile)
{
    uint64_t size = 0;
    if( !pFile )
        return size;

    if( !fseek(pFile, 0, SEEK_END) ) {
        size = (uint64_t)ftell(pFile);
        rewind(pFile);
    }
    return size;
}

int main(void)
{
    typedef uint8_t     bytecode[] ;

    /*
    FILE *pFile = fopen("./myfile.cbyte", "rb");
    if( !pFile )
        return 0;

    uint64_t size = get_file_size(pFile);
    uint8_t *program = malloc(sizeof(uint8_t)*size);
    fread(program, sizeof(uint8_t), size, pFile);
    */
    const bytecode test1 = {
        nop,
        //pushl, 255, 1, 0, 0x0,
        //pushs, 0xDD, 0xDD,
        pushb, 0xAA,
        //pushs, 0x0D, 0x0C,
        //pops, popl,
        //wrtl, 4,0,0,0, 0xFF,0xFF,0,0,
        //storeb, 0,0,0,0,
        //stores, 1,0,0,0,
        //storel, 4,0,0,0,
        //loadb, 4,0,0,0,
        //storel, 0,0,0,0,
        //loadl, 0,0,0,0,
        //pushsp, puship, copyl, copys, copyb,
        halt
    };
    const bytecode test2 = {
        nop,
        //jmp, 17,0,0,0,
        // -16776961
        //pushl, 255,0,0,255,
        //pushl, 1,0,0,0,
        // -855637761
        //pushl, 255,0,0,205,
        //pushl, 255,255,0,0,
        //uaddl,
        // 10.f in 4 bytes form
        pushl, 0,0,32,65,
        jzb, 17,0,0,0,
        // 2.f in 4 bytes form
        pushl, 0,0,0,64,
        addf,   // 12.f
        //leqf,
        halt
    };
    const bytecode test3 = {
        nop,
        call, 7,0,0,0,  // 1
        halt,           // 6
    // func1:
        pushl, 9,0,0,0, // 7
        call, 18,0,0,0, // 12
        ret,    // 17
    // func2:
        pushl, 5,0,0,0, // 18
        pushl, 10,0,0,0,    // 23
        mull,   // 28
        mull,
        call, 36,0,0,0, // 30
        ret,    
    // func3:
        pushl, 40,0,0,0,    // 36
        divl,   // 41
        ret,
    };
    struct vm_cpu *p_vm = malloc( sizeof(struct vm_cpu) ); //&(struct vm_cpu){ 0 };
    if( p_vm ) { //printf("size == %u\n", sizeof(struct vm_cpu));
        vm_exec( test3, p_vm );
        debug_print_memory(p_vm);
        debug_print_stack(p_vm);
        //debug_print_callstack(p_vm);
        //debug_print_ptrs(p_vm);
        free(p_vm);
    }
    p_vm=NULL;
    /*
    fclose(pFile); pFile=NULL;
    free(program); program=NULL;
    */
    return 0;
}
\$\endgroup\$
3
\$\begingroup\$

Call/ret is bugged now

When you switched from code being uint64_t to uint8_t, you didn't modify the exec_call handler correctly:

exec_call:;
    vm->ip++;
    printf("calling address: %u\n", vm->ip);
    vm->bCallstack[++vm->callsp] = vm->ip + 1; // <-- Wrong ret addr
    vm->callbp = vm->callsp;
    vm->ip = code[vm->ip];     // <- Only 1 byte!

Here, you can see two problems:

  1. The call address is only 1 byte instead of 4.
  2. The return address is being set to somewhere inside the 4 byte of call address instead of just after the call address.

This causes the test3 program to terminate right after the first function return:

returning to address: 32
===================== vm done

After I fixed exec_call to be like this:

exec_call:;
    vm->ip++;
    printf("calling address: %u\n", vm->ip);
    vm->bCallstack[++vm->callsp] = vm->ip + 4;
    vm->callbp = vm->callsp;
    conv.c[0] = code[vm->ip];
    conv.c[1] = code[vm->ip+1];
    conv.c[2] = code[vm->ip+2];
    conv.c[3] = code[vm->ip+3];
    vm->ip = conv.ui;
    printf("call return addr: %u | frame ptr == %u\n", vm->bCallstack[vm->callsp], vm->callbp);
    goto *dispatch[ code[vm->ip] ];

Then the program was able to return correctly from nested function call:

returning to address: 35
callsp set to callbp, callsp == 2
returning to address: 17
callsp set to callbp, callsp == 1
returning to address: 6
===================== vm done

callbp is useless

You have something called vm->callbp that serves no purpose. It is always set to the current value of vm->callsp. You could eliminate that variable and just use vm->callsp instead.

Endianness issues

I'm not sure what your plans are with this virtual machine, but currently the virtual machine's endianness depends on the host machine's endianness. This means that each program you write will only work if the endianness it was written for matches the host machine's endianness. So for example, since you wrote the test3 program assuming little endian, it runs fine on an x86 host. But if you were to recompile this program on a big endian host machine, it would fail to run test3 properly.

You could fix this by defining your virtual machine to be little endian, and then fixing your conversions appropriately.

Repetitive code

I see a lot of repeated code, such as this code that reads a 4-byte value from the instruction stream:

conv.c[0] = code[++vm->ip];
conv.c[1] = code[++vm->ip];
conv.c[2] = code[++vm->ip];
conv.c[3] = code[++vm->ip];
a = conv.ui;

You could use inline functions to simplify your code:

static inline uint32_t read_imm4(const uint8_t *code,
        struct vm_cpu *const vm)
{
    conv_union conv;
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
    return conv.ui;
}

// At call site:
a = read_imm4(code, vm);

Not only does this reduce the amount of repeated code in your dispatch handlers, it also has the advantage of centralizing the conversion code to functions which you can then modify as necessary. For example, to do endianness correctly, you might modify the above to look like this:

static inline uint32_t read_imm4(const uint8_t *code,
    struct vm_cpu *const vm)
{
    conv_union conv;
#if defined(BIGENDIAN_HOST)
    conv.c[3] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[0] = code[++vm->ip];
#else
    conv.c[0] = code[++vm->ip];
    conv.c[1] = code[++vm->ip];
    conv.c[2] = code[++vm->ip];
    conv.c[3] = code[++vm->ip];
#endif
    return conv.ui;
}

Bounds checking

Right now, your virtual machine has no bounds checking. So for example, if the program does a wrtl instruction with an address larger than MEM_SIZE, your program doesn't catch that as an error, and will proceed to write to some arbitrary memory location. A malicious user could potentially figure out a way to exploit this and cause the program to start executing injected code.

I would advise guarding every array access with a bounds check.

\$\endgroup\$
  • \$\begingroup\$ thx man! I addressed the incorrect call/ret opcodes as you mentioned! About endianess issues, the VM is little endian and I'm not going to port this VM to any architecture that's big endian as this will only be used for a game engine. So mostly x86 and ARM and ARM is bi-endian. \$\endgroup\$ – Nergal Jul 7 '17 at 22:17
  • \$\begingroup\$ Also, I forgot to put this but the reason I have callbp is for recursive functions, do recursive functions not really need a frame pointer? Also, how could a malicious user exploit this and inject code? \$\endgroup\$ – Nergal Jul 7 '17 at 22:18
  • \$\begingroup\$ @Nergal You don't need callbp for recursive functions. Your callsp stack index is all you need to return from any type of function. As far as injecting code, the program could just use multiple wrtl to write code onto your program's stack, and then use anotherwrtl to overwrite the current function's return address on the stack to jump to the place on the stack where the injected code was written. Then the next time you return from a function, it would jump to the malicious code. \$\endgroup\$ – JS1 Jul 7 '17 at 23:13
3
\$\begingroup\$

I see a number of things that may help you improve your program.

Don't take the address of a label

Taking the address of a label is NOT supported by the standard and is, to my knowledge, solely a feature of gcc. Besides the fact that this is a non-standard extension, it's also not a very good way to express what you're trying to do. See the next suggestion for a better way.

Use a switch where appropriate

A much more direct, efficient, and portable method for exec would be to use a switch instead of goto. Another way to organize things is shown in the next suggestion.

Use an appropriate data structure

Each instruction has an opcode (the number assigned), a name (such as "pushl") and associated code that performs the operation. Those could be very nicely be put into a coherent structure like this:

static const struct {
    char *name;
    bool (*evaluate)(struct vm_cpu *);
} Instruction[] = {
    X(halt), X(pushl),   // ...
    X(nop),
};
static const int inst_count = sizeof(Instruction)/sizeof(Instruction[0]);

Labels are not statements

A label is not a statement and does not need a terminating semicolon.

Use portable constants for printing

On your machine, %llu may well be the correct printf format for printing a uint64_t, but on my 64-bit machine, it would be %lu. Rather than having to guess, simply use the constants defined in <intypes.h> and do it like this:

printf("loaded %" PRIu64 " from reg[%" PRIu64 "]\n", stack[sp], a);

Eliminate global variables where practical

Having routines dependent on global variables makes it that much more difficult to understand the logic and introduces many opportunities for error. Eliminating global variables where practical is always a good idea. In this case, if you wrap all of the associated values into a struct as in the previous suggestion, then you can pass a pointer to the struct into exec. This has many benefits, including the ability to have multiple simultaneous virtual machines running at the same time.

Create a reset function

Real CPUs have a reset pin which sets the machine to a defined, known state. You could implement a reset function for the virtual machine as well that would set everything to a known, predictable state.

Allow the user to control verbosity

Almost all of the instructions contain print functions of various types. That may be useful for debugging, but it would be annoying when trying to trace a larger program. I'd suggest creating a "verbose" flag within the virtual machine to control whether such statements are printed or not.

Allow the user to control range checking

I'd suggest that a boolean flag "safe" could be added to the VM. It would turn on checking for ranges (e.g. stack overflow, etc.) when debugging, or it could be turned off if you're looking for maximum VM speed at the expense of safety. I'd probably leave it on by default. Nobody likes fast programs that crash.

Putting it together

Here's a small sample showing how some of these suggestions might look:

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#define WORD_SIZE       4
#define STK_SIZE        1024*WORD_SIZE  // 4096 4Kb
#define CALLSTK_SIZE    256             // 1024 bytes
#define MEM_SIZE        256*WORD_SIZE   // 1024 bytes

// 'b' for Byte, not bool
struct vm_cpu {
    const uint8_t *code;
    size_t codesize;
    uint8_t bStack[STK_SIZE];            // 4096 bytes
    uint32_t bCallstack[CALLSTK_SIZE];    // 1024 bytes
    uint8_t bMemory[MEM_SIZE];            // 1024 bytes
    uint32_t ip, sp, callsp, callbp;        // 16 bytes
    bool verbose;
    bool safe;
};

uint32_t as_uint32_t(const void *ptr) {
    uint32_t val;
    memcpy(&val, ptr, sizeof(val));
    return val;
}

bool exec_halt(struct vm_cpu *vm) {
    if (vm->verbose) {
        printf("halted at %4.4x\n", vm->ip);
    }
    return false;
}
bool exec_nop(struct vm_cpu *vm) {
    ++vm->ip;
    return true;
}

bool exec_pushl(struct vm_cpu *vm) {
    if (vm->safe) {
        // check pointers
    }
    ++vm->ip;
    memcpy(&vm->bStack[vm->sp], &vm->code[vm->ip], sizeof(uint32_t));
    if (vm->verbose) {
        printf("pushl: pushed %" PRIu32 "\n", as_uint32_t(&vm->code[vm->ip]));
    }
    vm->ip += sizeof(uint32_t);
    vm->sp += sizeof(uint32_t);
    return true;
}

#define X(x) { #x, exec_##x }

static const struct {
    char *name;
    bool (*evaluate)(struct vm_cpu *);
} Instruction[] = {
    X(halt), X(pushl), // ... etc.
    X(nop),
};

static const int inst_count = sizeof(Instruction)/sizeof(Instruction[0]);

void vm_init(struct vm_cpu *vm, const void *code, size_t code_len) {
    vm->code = code;
    vm->codesize = code_len;
    vm->ip = vm->sp = vm->callsp = vm->callbp = 0;
    vm->verbose = true;
    vm->safe = true;
}

void vm_print(struct vm_cpu *vm) {
    printf("sp = %4.4x, ip = %4.4x, next inst = %s\n", 
            vm->sp, vm->ip, Instruction[vm->code[vm->ip]].name
    );
}

bool vm_eval(struct vm_cpu *vm) {
    if (vm->verbose) {
        vm_print(vm);
    }
    return Instruction[vm->code[vm->ip]].evaluate(vm);
}

int main() {
    struct vm_cpu vm;
    static uint8_t code[] = { 
        1, 1, 2, 3, 4,   // pushl 0x04030201
        2,               // nop
        0,               // halt
    };
    size_t code_len = sizeof(code) / sizeof(code[0]);
    vm_init(&vm, code, code_len);
    while (vm_eval(&vm)
    {} // just execute instructions
}

Sample output

sp = 0000, ip = 0000, next inst = pushl
pushl: pushed 67305985
sp = 0004, ip = 0005, next inst = nop
sp = 0004, ip = 0006, next inst = halt
halted at 0006
\$\endgroup\$
  • \$\begingroup\$ flagged for copy pasting \$\endgroup\$ – Nergal Jul 7 '17 at 20:06
  • 1
    \$\begingroup\$ @Nergal you can always comment and ask the author of an answer to clarify how copying parts of a previous answer is justified in this iteration, but there's nothing wrong with re-iterating previous advice that wasn't followed. \$\endgroup\$ – Mathieu Guindon Jul 7 '17 at 20:18
  • \$\begingroup\$ ok @Edward why are you telling me to group globals into a struct when there are no globals to group into a struct? \$\endgroup\$ – Nergal Jul 7 '17 at 21:00
  • \$\begingroup\$ also, Use portable constants for printing? I'm not using non-portable printing constants... how is '%u' not portable? \$\endgroup\$ – Nergal Jul 7 '17 at 21:25
  • 1
    \$\begingroup\$ @Nergal: nice to see that you're reading these closely. I couldn't tell from earlier reviews. As for globals, opcode2str is global in the original code. If you'll note in the revised code I posted, the Instruction array is static const which limits it to file scope. Also %u is not strictly portable unless an unsigned and uint32_t happen to be the same size (they're not on my machine). If you're printing particularly sized variables, as in this case, it makes sense to use the matching particularly sized formatting strings, as I have demonstrated in the pushl() code I posted. \$\endgroup\$ – Edward Jul 7 '17 at 23:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.