project: Simple JS stack machine

Details

Simulation of a minimalistic stack machine, written in javascript.

  • last updated: July, 2018
Download source

Source explorer

js_vm.js

View raw
//integer instructions
IADD = 1; //number add
ISUB = 2; //number subtract
IMUL = 3; //number multiply
ILT = 4; //number less than
ILET = 5; //number less than
IEQ = 6; //number equal
BR = 7; //branch
BRT = 8; //branch if true
BRF = 9; //branch if false
CONST = 10; //number const
LOAD = 11; //load local
GLOAD = 12; //load from global
GSTORE = 13; //store in global
PRINT = 14;//output
POP = 15;//pop value from stack
CALL = 16;//call procedure at address
RET = 17;//return result from local computation
HALT = 18;//end execution

//BOOLEAN VALUES
B_TRUE = 1;
B_FALSE = 0;

var instructions={};// = {
instructions[IADD]= {name: "IADD", nop: 0};
instructions[ISUB]= {name: "ISUB", nop: 0};
instructions[IMUL]= {name: "IMUL", nop: 0};
instructions[ILT]= {name: "ILT", nop: 0};  
instructions[ILET]= {name: "ILET", nop: 0};
instructions[IEQ]= {name: "IEQ", nop: 0};
instructions[CONST]= {name: "CONST", nop: 1};
instructions[BR]= {name: "BR", nop: 1};
instructions[BRT]= {name: "BRT", nop: 1};
instructions[BRF]= {name: "BRF", nop: 1};
instructions[LOAD]= {name: "LOAD", nop: 1};
instructions[GSTORE]= {name: "GSTORE", nop: 1};
instructions[GLOAD]= {name: "GLOAD", nop: 1};
instructions[PRINT]= {name: "PRINT", nop: 0};
instructions[POP]= {name: "POP", nop: 0};
instructions[CALL]= {name: "CALL", nop: 2};
instructions[RET]= {name: "RET", nop: 0};
instructions[HALT]= {name: "HALT", nop: 0};

var vm = function(program, startat, configs) {
    
    //reset method
    this.reset= function(program, startat, configs)
    {
        //REGISTERS
        this.sp = -1;//stack pointer
        this.fp = 0;//frame pointer
        //read more from here: https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers    
        this.ip = startat || 0;//instruction pointer (program counter)
        this.lastip= -1;
        configs= configs || {};
        var datasize= configs.datasize || 100;
        var stacksize= configs.stacksize || 30;    
        this.trace= configs.trace || false;

        configs.IO= configs.IO || {};
        this.IO= {};
        this.IO.print= (typeof configs.IO.print == "function")? configs.IO.print: (function(text, endl, color){
            console.log(text);
        });
        this.IO.clear= (typeof configs.IO.clear == "function")? configs.IO.clear : (function(){});

        //memory
        this.data = new Array(datasize);
        this.stack = new Array(stacksize);

        //get program in memory
        this.code = program || [];

        this.isIdle= true;
    }

    this.reset(program, startat, configs);

    //program executor
    this.run = function() {
                    
        var stepped= false;
        if(this.isIdle) this.isIdle= false;

        while ((!this.trace && this.ip < this.code.length) || (!stepped && this.trace))
        {
            //fetch
            var opcode = this.code[this.ip];

            //update program counter
            this.lastip= this.ip;
            this.ip++;

            //decode
            switch (opcode) 
            {
                case CONST:
                    var v = this.code[this.ip];
                    this.ip++; //jump over argument
                    this.sp++;                    
                    this.stack[this.sp] = v;
                    break;
                case POP:
                    var a= this.stack[this.sp--];
                case IADD:
                    var a= this.stack[this.sp--];
                    var b= this.stack[this.sp--];
                    this.stack[++this.sp]= a+b;
                    break;
                case ISUB:
                    var b= this.stack[this.sp--];
                    var a= this.stack[this.sp--];
                    this.stack[++this.sp]= a-b;
                    break;
                case IMUL:
                    var a= this.stack[this.sp--];
                    var b= this.stack[this.sp--];
                    this.stack[++this.sp]= a*b;
                    break;
                case ILT:
                    var b= this.stack[this.sp--];
                    var a= this.stack[this.sp--];
                    this.stack[++this.sp]= (a>b)? B_TRUE: B_FALSE;
                    break;
                case ILET:
                    var b= this.stack[this.sp--];
                    var a= this.stack[this.sp--];
                    this.stack[++this.sp]= (a>=b)? B_TRUE: B_FALSE;
                    break;
                case IEQ:
                    var b= this.stack[this.sp--];
                    var a= this.stack[this.sp--];
                    this.stack[++this.sp]= (a==b)? B_TRUE: B_FALSE;
                    break;
                case BR:
                    this.ip= this.code[this.ip++];
                break;
                case BRT:
                    var addr= this.code[this.ip++];
                    if(this.stack[this.sp--]== B_TRUE) this.ip= addr;
                break;
                case BRF:
                    var addr= this.code[this.ip++];
                    if(this.stack[this.sp--]== B_FALSE) this.ip= addr;
                break;
                case LOAD:
                    var offset= this.code[this.ip++];
                    this.stack[++this.sp]= this.stack[this.fp+offset];
                break;
                case GSTORE:
                    var v= this.stack[this.sp];
                    this.sp--;
                    var addr= this.code[this.ip];
                    this.ip++;
                    this.data[addr]= v;
                    break;
                case GLOAD:
                    var addr= this.code[this.ip];
                    this.ip++;
                    var v= this.data[addr];
                    this.sp++;
                    this.stack[this.sp]=v;
                    break;
                case PRINT:
                    var v = this.stack[this.sp];
                    this.sp--;
                    this.IO.print("OUT: " + v, 1);
                    break;
                case CALL:
                    var addr= this.code[this.ip++];
                    var nargs= this.code[this.ip++];
                    this.stack[++this.sp]= nargs;//add the number of args
                    this.stack[++this.sp]= this.fp;//add the current frame pointer value
                    this.stack[++this.sp]= this.ip;//add the return address
                    this.fp= this.sp;//update the frame pointer value
                    this.ip= addr;//jump to the address of the procedure
                    break;
                case RET:
                    var rvalue= this.stack[this.sp--];
                    this.sp= this.fp;//send the stak pointer to the beginning of the local frame
                    this.ip= this.stack[this.sp--];//get the program counter to its prev pos
                    this.fp= this.stack[this.sp--];//assign the frame pointer to its prev value, before the call
                    var nargs= this.stack[this.sp--];//get the number of args
                    this.sp-= nargs;//reduce the stack pointer to destroy the call args later on
                    this.stack[++this.sp]= rvalue;//push the return value on the stack
                    break;
                case HALT:
                    this.isIdle= true;
            }

            stepped= true;
            if(this.isIdle) return;
        }
    }
}