• Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    Using the code above, you can develop a 4k intro using c language, but there are some cases where writing some intro parts using a bytecode language is useful and it takes less space.

    I have implemented a stack virtual machine, but really is a mixed solution, because it uses registers as variables. There are basic instructions push, pop instructions, arithmetic, branching and jumps, calling c functions, and some custom instructions to perform graphical effects.

    This vm can have technically, 127 registers, memory recommended upto 64kb*, 17 basic instructions, and 5 graphical instructions.

    *Memory array should not be bigger than 64kb, because jump instructions are short values.

    Basic instructions

    HALT - 1 byte - Finish execution
    PUSH - 2 bytes - Push one byte in stack
    RPUSH - 2 bytes - Push register in stack
    RPOP - 2 bytes - Pop into register
    ADD, SUB, MUL, DIV - 1 byte - Pop two bytes from stack and push the operation result
    CALL - 2 bytes - Call C function
    JMP - 3 bytes - Jump to address (Short)
    RJMP - 3 bytes - Relative jump (Short)
    CMP - 1 byte - Pop two bytes and compare, set the less significant bits:
    flags register: xxxx xGEL
    G greather than - E equal - L less than

    BREQ, BRLE, BRGR - 2 bytes - branch if last CMP result is great, equal or less, jump size is 1 signed byte.
    LDSP, STSP - 1 byte - Push and pop sp current address

    Graphical instructions

    The drawing system uses some internal variables to store a drawing position x and y, although the vm only support byte values, internally the drawing system have short values.
    There are two basic instructions, MOVE and CLPA.
    MOVE translate the next drawing point adding a signed byte to internal drawing position x and y. So you can access to screen positions beyond 128 pixels, but I have used glScale to resize my content.
    CLPA means CLose PAth, and perform the drawing operation created with MOVE. Start drawing point is 0, 0 which is the center of screen.
    SSHA means Set SHApe, and define which render mode uses, values accepted are GL_POINTS, GL_LINES, and GL_LINE_LOOP


    SCOL - 1 byte - Get from stack two bytes for color 4 bits per channel
    LWID - 1 byte - Set drawing line width from stack
    SSHA - 1 byte - Set drawing shape from stack
    MOVE - 1 byte - Get two bytes from stack and add these values to the drawing coordinate
    CLPA - 1 byte - Perform the graphical operation

    The memory array contain a simple effect, and as you can see is similar to write an assembler program:

    Code:

    static byte mem[VM_MEMORY] =
    {
    PUSH, 8,
    LWID,

    PUSH, GL_LINE_LOOP,
    SSHA,

    PUSH, 0xff,
    PUSH, 0x1f,
    SCOL,

    CALL, 0,
    CALL, 0,
    MOVE,

    CALL, 0,
    CALL, 0,
    MOVE,

    CLPA,

    RPUSH, 0,
    PUSH, 1,
    ADD,
    RPOP, 0,

    RPUSH, 0,
    PUSH, 127,
    CMP,

    BREQ, 3,
    JMP, 0,11,

    HALT,
    };


    You can use C functions using CALL instruction from VM, technically you can define 127 C functions.
    VM can pass parameters to these functions with stack, and the C functions can return any value pushing them on to the stack.

    This is the code:

    Code:


    #define VM_REGISTERS 16
    #define VM_MEMORY 4096

    #define HALT 0x00
    #define PUSH 0x01
    #define RPUSH 0x02
    #define RPOP 0x03
    #define ADD 0x04
    #define SUB 0x05
    #define MUL 0x06
    #define DIV 0x07
    #define CALL 0x08
    #define JMP 0x09
    #define RJMP 0x0A
    #define CMP 0x0B
    #define BRLE 0x0C
    #define BREQ 0x0D
    #define BRGR 0x0E
    #define LDSP 0x0F
    #define STSP 0x10

    #define SCOL 0x60
    #define LWID 0x61
    #define SSHA 0x62
    #define MOVE 0x63
    #define CLPA 0x65

    static void vm_exec(void);

    static short pc;
    static short sp;
    static ubyte flags;

    static short vertex[1024], shape=0, x = 0, y = 0, m=0;

    static byte reg[VM_REGISTERS];

    static byte mem[VM_MEMORY] =
    {
    PUSH, 8,
    LWID,

    PUSH, GL_LINE_LOOP,
    SSHA,

    PUSH, 0xff,
    PUSH, 0x1f,
    SCOL,

    CALL, 0,
    CALL, 0,
    MOVE,

    CALL, 0,
    CALL, 0,
    MOVE,

    CLPA,

    RPUSH, 0,
    PUSH, 1,
    ADD,
    RPOP, 0,

    RPUSH, 0,
    PUSH, 127,
    CMP,

    BREQ, 3,
    JMP, 0,11,

    HALT,
    };


    static void push(byte value)
    {
    mem[sp--] = value;
    }

    static byte pop(void)
    {
    return mem[++sp];
    }

    static short dpop(void)
    {
    return (pop()<<8)+(pop()&0xff);
    }

    static byte pop_pc(void)
    {
    return mem[pc++];
    }

    static byte dpop_pc(void)
    {
    return (pop_pc()<<8)+(pop_pc()&0xff);
    }

    static void func1(void)
    {
    }

    static void func2(void)
    {
    }

    static void func3(void)
    {
    }

    static void (*func_ptr[3])(void) = {func1, func2, func3};

    static void vm_exec(void)
    {
    ubyte opCode;
    byte a,b;
    short c;

    pc = 0;
    sp = VM_MEMORY-1;
    flags = 0;
    do {
    opCode = pop_pc();
    switch (opCode)
    {
    case PUSH:
    push(pop_pc());
    break;
    case RPUSH:
    push(reg[pop_pc()]);
    break;
    case RPOP:
    reg[pop_pc()]=pop();
    break;
    case ADD:
    push(pop()+pop());
    break;
    case SUB:
    push(pop()-pop());
    break;
    case MUL:
    push(pop()*pop());
    break;
    case DIV:
    push(pop()/pop());
    break;
    case CALL:
    (*func_ptr[pop_pc()])();
    break;
    case JMP:
    pc = dpop_pc();
    break;
    case RJMP:
    pc += pop_pc();
    break;
    case CMP:
    a = pop();
    b = pop();
    flags = 0;
    flags |= ((a<b) << 0);
    flags |= ((a==b)<< 1);
    flags |= ((a>b) << 2);
    break;
    case BRLE:
    a = pop_pc();
    if (flags & (1 << 0))
    pc += a;
    break;
    case BREQ:
    a = pop_pc();
    if (flags & (1 << 1))
    pc += a;
    break;
    case BRGR:
    a = pop_pc();
    if (flags & (1 << 2))
    pc += a;
    break;
    case LDSP:
    sp = dpop();
    break;
    case STSP:
    push(sp);
    push(sp>>8);
    break;

    case SCOL:
    c = dpop();
    glColor4ub((c>>4)&0xf0,c&0xf0,(c<<4)&0xf0,(c>>8)&0xf0);
    break;
    case LWID:
    glLineWidth(pop());
    break;
    case SSHA:
    shape = pop();
    break;
    case MOVE:
    x += pop();
    y += pop();
    vertex[m++] = x;
    vertex[m++] = y;
    break;
    case CLPA:
    glVertexPointer(2, GL_SHORT, 0, vertex);
    glDrawArrays(shape, 0, m);
    m = x = y = 0;
    break;
    }
    } while (opCode != 0x00); // HALT
    }


    And for lazy people like me, here are the files to see this example:

    http://www.amigaskool.net/download/4k_intro_3.lha

    Here is the executable of this example, for those who wanna see result first... :)

    http://www.amigaskool.net/download/4k_intro_exe.lha


    [ Edited by BalrogSoft 19.05.2015 - 11:37 ]
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »18.05.15 - 20:53
    Profile Visit Website