• Javascript QCode Virtual Machine, an Update

    by  • August 20, 2012 • Articles, HTML5, Programming • 0 Comments

    I’ve been tinkering away and thought it would be about time I give a status update. Over the last month or two I have been porting my PSION OPO/QCode runtime to JavaScript from C#, this has required quite a lot of work and rewriting and refactoring, along with the hope to include additional new functionality such as an improved debugger and the possibility of a JIT’ter

    For constant updates I have made the source available via Google Code and can be found here.

    PSION OPO Memory Debugger

    Memory allocations

    It has now got to the stage where most of the core memory, runtime and maths functions are in place, what is left is now the graphics, dialogs and menu functionality.

    The memory management is now complete, OPO applications allocate 64KB of memory and then split it up and allocate it as functions are called and globals are pushed to the stack (as shown in the image above). There are different types of variables (Int16, Int32, Float, String) and allocations cover globally reserved, function reserved and dynamically reserved memory, but they all share the common memory.

    // Memory Manager
    var Mem = function(size) {
      // Alloc the memory
      this.m = new Array(size);
    
      // saves which cells memory belongs to
      // Layout: A/D, Size, Offset, Type
      this.c = [[0, size, 0, 0]];
    
      //Type
      // 0 = Unused
      // 1 = Global
      // 2 = Procedure
      // 3 = Dynamic
    
      // Peek at the contents of a memory address
      this.peek = function(i) {
        return this.m[i];
      };
    
      // Allocate in memory contents of a certain size
      this.alloc = function(s, t) {
        for(var i = 0; i< this.c.length; i++) {
          var l = this.c[i][1], o = this.c[i][2];
          if (this.c[i][0] == 0 && l >= s) {
            // Cell is deallocated and has more or equal to the required length
            if (l == s) {
              // Equal sizes, reallocate cell
              this.c[i][0] = 1;
              this.c[i][3] = t;
              return o;
            } else {
              // Cell has more space than allocation, reallocate and create new
              this.c[i][0] = 1;
              this.c[i][1] = s;
              this.c[i][3] = t;
    
              // Create new cell
              this.c.push([0, l - s, o + s, 0]);
              return o;
            }
          }
        }
        // Allocation failed due to lack of space.
        return -1;
      };
    
      // Deallocate memory at a certain start point
      this.dealloc = function(s) {
        for (var i = 0; i < this.c.length; i++) {
          if (this.c[i][2] == s) {
            this.c[i][0] = 0;
            return 1;
          }
        }
        return -1;
      }
    
      // Reallocate the size of a memory structure
      this.realloc = function(s, n) {
        for (var i = 0; i < this.c.length; i++) {
          if (this.c[i][2] == s) {
            var l = this.c[i][1], o = this.c[i][2];
            if (n > l) {
              // New Allocation is larger
              var a = this.alloc(n);
    
              // Copy the data from the old location to the new
              this.m.splice(a,l,this.m.slice(s,l));
            } else if (n < l) {
              // New Allocation is smaller
              this.c[i][1] = n;
    
              // Create new empty cell
              this.c.push([0, l - n, o + s, 0]);
              return;
            }
          }
        }
      }
    
      // Reserve a specific space in memory - used only for globals
      // Does not expect the memory to already be allocated.
      this.reserve = function(o, s) {
        for (var i = 0; i< this.c.length; i++) {
          if (o == this.c[i][2]) {
            // The offset of the allocated chunk starts at the same offset as
            // the free piece of memory
            if (s > this.c[i][1] || this.c[i][0] == 1) {
              return;
            }
    
            // Allocate cell
        	if (s != this.c[i][1]) {
        		// Need to split the end of the cell
              	this.c.push([0, this.c[i][1] - s, o + s, 0]);
        	}
        	// Allocate it as used
        	this.c[i][0] = 1;
        	this.c[i][1] = s;
            this.c[i][3] = 1;
    
          } else if (o > this.c[i][2] && o < this.c[i][2] + this.c[i][1]) {
            // The offset of the allocated chunk starts further along the free
            // piece of memories chunk. Need to free the first part
            if (o + s > this.c[i][2] + this.c[i][1] || this.c[i][0] == 1) {
              return;
            }
    
            // Create new empty cell at the beginning of the cell
            this.c.push([0, o - this.c[i][2] , this.c[i][2], 0]);
    
            // Readjust the current cell
            this.c[i][1] = this.c[i][1] - (o - this.c[i][2]);
            this.c[i][2] = o;
    
            // Call this function again, will create from an empty cell with the start
            // offset
            this.reserve(o, s);
          }
        }
      }
    
      // Copy a memory sequence into memory
      this.set = function(o, v) {
        if (o < 0 || o >= this.m.length) {
          // Array offset out of bounds
        } else {
          // Splice did not correctly work
          for (var i = 0; i< v.length; i++) {
            this.m[o + i] = v[i];
          }
        }
      }
    }
    

    The memory manager uses a linked list to store the allocations and debug information, when a new allocation is made the list is searched for a free chunk large enough. I have yet to write a ‘defrag’ style algorithm when memory gets churned.

    Extracting floating point variables from the application byte code was interesting, Javascript has limited handling of byte arrays and bit conversion. It was only with HTML5 and some of the new Javascript functionality (thanks in part to WebGL) that this could be achieved with the inclusion of Typed Arrays

    // Return a 32 bit Float
    // a = Array
    // o = Offset
    function f32(a,o) {
    	var buffer = new ArrayBuffer(4);
    	var uint8s = new Uint8Array(buffer);
    	uint8s[0] = a[o];
    	uint8s[1] = a[o + 1];
    	uint8s[2] = a[o + 2];
    	uint8s[3] = a[o + 3];
    	var fBuffer = new Float32Array(buffer);
    
        return fBuffer[0];
    }
    

    To test the functionality so far I am using the ByteCode for REVTRAN, if anyone used and a PSION and programmed in OPL you most likely will have heard of it as it sent shockwaves through the programming community. It reverse engineered the OPO/QCode byte code back into OPL allowing people to disassemble commercial apps.

    One of the benefits of this was the information on the QCode format was opened up and is now available as part of the PSIONICS files with information about each Op-Code and Stack variables which has proved extremely useful for me.

    PSION OPL REVTRAN

    To test out the runtime so far, click here, remember however that it only displays the console debug output of the runtime, and no graphics yet exist.

    About

    Software engineer. Tea drinker

    http://MrPfister.com

    Leave a Reply

    Your email address will not be published. Required fields are marked *