From 9f478261bc8fd1502090c701c998e48a4018e7ee Mon Sep 17 00:00:00 2001 From: sanine-a Date: Wed, 24 May 2023 13:06:45 -0500 Subject: fix operand resolution to match spec --- src/vm/instruction.js | 146 +++++++------ src/vm/instruction.test.js | 15 ++ src/vm/instruction.test.js.n | 508 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 605 insertions(+), 64 deletions(-) create mode 100644 src/vm/instruction.test.js.n diff --git a/src/vm/instruction.js b/src/vm/instruction.js index dcf9a51..2b8ce84 100644 --- a/src/vm/instruction.js +++ b/src/vm/instruction.js @@ -3,68 +3,86 @@ const { AddrMode } = require('./enum.js'); -exports.DAT = function(core, pc, ins) { +function getOp(core, pc, op) { + switch(op.mode) { + case AddrMode.Immediate: + return { mode: AddrMode.Immediate, value: op.value }; + default: + return { mode: 'precomp', value: core.getLocation(pc, op) }; + } +} + + +function Instruction(f) { + return function(core, pc, ins) { + const a = getOp(core, pc, ins.a); + const b = getOp(core, pc, ins.b); + return f(core, pc, a, b); + } +} + + +exports.DAT = Instruction((core, pc, a, b) => { // do nothing and die return []; -} +}); -exports.MOV = function(core, pc, ins) { - if (ins.a.mode === AddrMode.Immediate) { - const dst = core.getValue(pc, ins.b); - dst.b.value = ins.a.value; +exports.MOV = Instruction((core, pc, a, b) => { + if (a.mode === AddrMode.Immediate) { + const dst = core.data[b.value]; + dst.b.value = a.value; } else { - const src = core.getValue(pc, ins.a); - const dstLocation = core.getLocation(pc, ins.b); + const src = core.data[a.value]; // hacky deep copy - core.data[dstLocation] = JSON.parse(JSON.stringify(src)); + core.data[b.value] = JSON.parse(JSON.stringify(src)); } return [core.normalize(pc, 1)]; -} +}); -exports.ADD = function(core, pc, ins) { - if (ins.a.mode === AddrMode.Immediate) { - const dst = core.getValue(pc, ins.b); - dst.b.value += ins.a.value; +exports.ADD = Instruction((core, pc, a, b) => { + if (a.mode === AddrMode.Immediate) { + const dst = core.data[b.value]; + dst.b.value += a.value; } else { - const src = core.getValue(pc, ins.a); - const dst = core.getValue(pc, ins.b); + const src = core.data[a.value]; + const dst = core.data[b.value]; dst.a.value += src.a.value; dst.b.value += src.b.value; } return [core.normalize(pc, 1)]; -} +}); -exports.SUB = function(core, pc, ins) { - if (ins.a.mode === AddrMode.Immediate) { - const dst = core.getValue(pc, ins.b); - dst.b.value -= ins.a.value; +exports.SUB = Instruction((core, pc, a, b) => { + if (a.mode === AddrMode.Immediate) { + const dst = core.data[b.value]; + dst.b.value -= a.value; } else { - const src = core.getValue(pc, ins.a); - const dst = core.getValue(pc, ins.b); + const src = core.data[a.value]; + const dst = core.data[b.value]; dst.a.value -= src.a.value; dst.b.value -= src.b.value; } return [core.normalize(pc, 1)]; -} +}); -exports.CMP = function(core, pc, ins) { - if (ins.a.mode === AddrMode.Immediate) { - const test = core.getValue(pc, ins.b); - if (test.b.value === ins.a.value) { +exports.CMP = Instruction((core, pc, a, b) => { + if (a.mode === AddrMode.Immediate) { + const test = core.data[b.value]; + if (test.b.value === a.value) { return [core.normalize(pc, 2)]; } else { return [core.normalize(pc, 1)]; } } else { - const left = core.getValue(pc, ins.a); - const right = core.getValue(pc, ins.b); + const left = core.data[a.value]; + const right = core.data[b.value]; if ( // compare opcode left.opcode === right.opcode && @@ -80,93 +98,93 @@ exports.CMP = function(core, pc, ins) { return [core.normalize(pc, 1)]; } } -} +}); -exports.SLT = function(core, pc, ins) { - if (ins.a.mode === AddrMode.Immediate) { - const test = core.getValue(pc, ins.b); - if (ins.a.value < test.b.value) { +exports.SLT = Instruction((core, pc, a, b) => { + if (a.mode === AddrMode.Immediate) { + const test = core.data[b.value]; + if (a.value < test.b.value) { return [core.normalize(pc, 2)]; } else { return [core.normalize(pc, 1)]; } } else { - const left = core.getValue(pc, ins.a); - const right = core.getValue(pc, ins.b); + const left = core.data[a.value]; + const right = core.data[b.value]; if (left.b.value < right.b.value) { return [core.normalize(pc, 2)]; } else { return [core.normalize(pc, 1)]; } } -} +}); -exports.JMP = function(core, pc, ins) { - const dstLoc = core.getLocation(pc, ins.a); - return [dstLoc]; -} +exports.JMP = Instruction((core, pc, a, b) => { + return [a.value]; +}); -exports.JMZ = function(core, pc, ins) { +exports.JMZ = Instruction((core, pc, a, b) => { let test - if (ins.b.mode === AddrMode.Immediate) { - test = (ins.b.value === 0); + if (b.mode === AddrMode.Immediate) { + test = (b.value === 0); } else { - const src = core.getValue(pc, ins.b); + const src = core.data[b.value]; test = (src.b.value === 0); } if (test) { - return [core.getLocation(pc, ins.a)]; + return [a.value]; } else { return [core.normalize(pc, 1)]; } -} +}); -exports.JMN = function(core, pc, ins) { +exports.JMN = Instruction((core, pc, a, b) => { let test; - if (ins.b.mode === AddrMode.Immediate) { - test = (ins.b.value !== 0); + if (b.mode === AddrMode.Immediate) { + test = (b.value !== 0); } else { - const src = core.getValue(pc, ins.b); + const src = core.data[b.value]; test = (src.b.value !== 0); } if (test) { - return [core.getLocation(pc, ins.a)]; + return [a.value]; } else { return [core.normalize(pc, 1)]; } -} +}); -exports.DJN = function(core, pc, ins) { +exports.DJN = Instruction((core, pc, a, b) => { let test; - if (ins.b.mode === AddrMode.Immediate) { - ins.b.value -= 1; - test = (ins.b.value !== 0); + if (b.mode === AddrMode.Immediate) { + core.data[pc].b.value -= 1; + b.value -= 1; + test = (b.value !== 0); } else { - const src = core.getValue(pc, ins.b); + const src = core.data[b.value]; src.b.value -= 1; test = (src.b.value !== 0); } if (test) { - return [core.getLocation(pc, ins.a)]; + return [a.value]; } else { return [core.normalize(pc, 1)]; } -} +}); -exports.SPL = function(core, pc, ins) { +exports.SPL = Instruction((core, pc, a, b) => { return [ core.normalize(pc, 1), - core.getLocation(pc, ins.a), + a.value, ]; -} +}); diff --git a/src/vm/instruction.test.js b/src/vm/instruction.test.js index cf23404..279b9b8 100644 --- a/src/vm/instruction.test.js +++ b/src/vm/instruction.test.js @@ -393,6 +393,21 @@ test('JMP correctly jumps', () => { }); +test('JMP correctly responds to B-field predecrement', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'JMP', + a: { mode: 'direct', value: -3 }, + b: { mode: 'predecrement', value: 1 }, + }; + const ins = core.data[pc]; + + expect(JMP(core, pc, ins)).toEqual([CORESIZE-1]); + expect(core.data[pc+1].b.value).toBe(-1); +}); + + test('JMZ correctly jumps in non-immediate mode', () => { const core = new Core(CORESIZE); const pc = 2; diff --git a/src/vm/instruction.test.js.n b/src/vm/instruction.test.js.n new file mode 100644 index 0000000..cf23404 --- /dev/null +++ b/src/vm/instruction.test.js.n @@ -0,0 +1,508 @@ +'use strict'; + +const { Core } = require('./core.js'); +const { + DAT, + MOV, ADD, SUB, CMP, SLT, + JMP, JMZ, JMN, DJN, SPL, +} = require('./instruction.js'); + + +const CORESIZE = 8000; + + +test('DAT does nothing and kills the program', () => { + const core = new Core(CORESIZE); + const pc = 0; + const ins = core.data[pc]; + expect(DAT(core, pc, ins)).toEqual([]); +}); + + +test('MOV correctly moves a full instruction', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'MOV', + a: { mode: 'direct', value: 0 }, + b: { mode: 'direct', value: 1 }, + }; + const ins = core.data[pc]; + expect(core.data[pc+1].opcode).toBe('DAT'); + expect(MOV(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc]).toEqual(core.data[pc+1]); + expect(core.data[pc+1].opcode).toBe('MOV'); +}); + + +test('MOV correctly moves a B-field', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'MOV', + a: { mode: 'immediate', value: 100 }, + b: { mode: 'direct', value: 1 }, + }; + const ins = core.data[pc]; + expect(core.data[pc+1].opcode).toBe('DAT'); + expect(MOV(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: 100 }, + }); +}); + + +test('MOV correctly handles indirection', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc-1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: 2 }, + }; + core.data[pc] = { + opcode: 'MOV', + a: { mode: 'immediate', value: 100 }, + b: { mode: 'indirect', value: -1 }, + }; + const ins = core.data[pc]; + expect(core.data[pc+1].opcode).toBe('DAT'); + expect(MOV(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc-1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: 2 }, + }); + + expect(core.data[pc+2]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: 100 }, + }); + +}); + + + + +test('MOV correctly handles predecrement indirection', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'MOV', + a: { mode: 'immediate', value: 100 }, + b: { mode: 'predecrement', value: 1 }, + }; + const ins = core.data[pc]; + expect(core.data[pc+1].opcode).toBe('DAT'); + expect(MOV(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: -1 }, + }); + + expect(core.data[pc-1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: 100 }, + }); + +}); + + +test('MOV correctly handles core boundaries', () => { + const core = new Core(CORESIZE); + const pc = CORESIZE-1; + core.data[pc] = { + opcode: 'MOV', + a: { mode: 'immediate', value: 100 }, + b: { mode: 'predecrement', value: 2 }, + }; + const ins = core.data[pc]; + expect(core.data[0].opcode).toBe('DAT'); + expect(MOV(core, pc, ins)).toEqual([0]); + expect(core.data[1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: -1 }, + }); + + expect(core.data[CORESIZE-2]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 0 }, + b: { mode: 'immediate', value: 100 }, + }); + +}); + + + +test('ADD correctly adds two full instructions', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'ADD', + a: { mode: 'direct', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(ADD(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }); + expect(core.data[pc+2]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 13 }, + b: { mode: 'immediate', value: 24 }, + }); +}); + + +test('ADD correctly adds B-field', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'ADD', + a: { mode: 'immediate', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(ADD(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }); + expect(core.data[pc+2]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 5 }, + }); +}); + + +test('SUB correctly subtracts two full instructions', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'SUB', + a: { mode: 'direct', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(SUB(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }); + expect(core.data[pc+2]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: -7 }, + b: { mode: 'immediate', value: -16 }, + }); +}); + + +test('SUB correctly subtracts B-field', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'SUB', + a: { mode: 'immediate', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(SUB(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }); + expect(core.data[pc+2]).toEqual({ + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 3 }, + }); +}); + + +test('CMP properly compares full instructions', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'CMP', + a: { mode: 'direct', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 10 }, + b: { mode: 'immediate', value: 20 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(CMP(core, pc, ins)).toEqual([pc+1]); + + core.data[pc+2] = JSON.parse(JSON.stringify(core.data[pc+1])); + + expect(CMP(core, pc, ins)).toEqual([pc+2]); +}); + + +test('CMP properly compares B-field', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'CMP', + a: { mode: 'immediate', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(CMP(core, pc, ins)).toEqual([pc+1]); + + core.data[pc+2].b.value = 1; + + expect(CMP(core, pc, ins)).toEqual([pc+2]); +}); + + +test('SLT properly compares full instructions', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'SLT', + a: { mode: 'direct', value: 1 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+1] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(SLT(core, pc, ins)).toEqual([pc+1]); + + core.data[pc+2].b.value = 5; + + expect(SLT(core, pc, ins)).toEqual([pc+2]); +}); + + + +test('SLT properly compares B-fields', () => { + const core = new Core(CORESIZE); + const pc = 20; + core.data[pc] = { + opcode: 'SLT', + a: { mode: 'immediate', value: 7 }, + b: { mode: 'direct', value: 2 }, + }; + core.data[pc+2] = { + opcode: 'DAT', + a: { mode: 'immediate', value: 3 }, + b: { mode: 'immediate', value: 4 }, + }; + const ins = core.data[pc]; + + expect(SLT(core, pc, ins)).toEqual([pc+1]); + + core.data[pc+2].b.value = 8; + + expect(SLT(core, pc, ins)).toEqual([pc+2]); +}); + + +test('JMP correctly jumps', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'JMP', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 0 }, + }; + const ins = core.data[pc]; + + expect(JMP(core, pc, ins)).toEqual([CORESIZE-1]); +}); + + +test('JMZ correctly jumps in non-immediate mode', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'JMZ', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 1 }, + }; + core.data[pc+1] = { + opcode: 'JMZ', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 1 }, + }; + const ins = core.data[pc]; + + expect(JMZ(core, pc, ins)).toEqual([pc+1]); + core.data[pc+1].b.value = 0; + expect(JMZ(core, pc, ins)).toEqual([CORESIZE-1]); +}); + + +test('JMZ correctly jumps in immediate mode', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'JMZ', + a: { mode: 'direct', value: -3 }, + b: { mode: 'immediate', value: 1 }, + }; + const ins = core.data[pc]; + + expect(JMZ(core, pc, ins)).toEqual([pc+1]); + core.data[pc].b.value = 0; + expect(JMZ(core, pc, ins)).toEqual([CORESIZE-1]); +}); + + +test('JMN correctly jumps in non-immediate mode', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'JMN', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 1 }, + }; + core.data[pc+1] = { + opcode: 'JMN', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 0 }, + }; + const ins = core.data[pc]; + + expect(JMN(core, pc, ins)).toEqual([pc+1]); + core.data[pc+1].b.value = 1; + expect(JMN(core, pc, ins)).toEqual([CORESIZE-1]); +}); + + +test('JMN correctly jumps in immediate mode', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'JMN', + a: { mode: 'direct', value: -3 }, + b: { mode: 'immediate', value: 0 }, + }; + const ins = core.data[pc]; + + expect(JMN(core, pc, ins)).toEqual([pc+1]); + core.data[pc].b.value = 1; + expect(JMN(core, pc, ins)).toEqual([CORESIZE-1]); +}); + + +test('DJN correctly jumps in non-immediate mode', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'DJN', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 1 }, + }; + core.data[pc+1] = { + opcode: 'DJN', + a: { mode: 'direct', value: -3 }, + b: { mode: 'direct', value: 1 }, + }; + const ins = core.data[pc]; + + expect(DJN(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc+1].b.value).toBe(0); + expect(DJN(core, pc, ins)).toEqual([CORESIZE-1]); + expect(core.data[pc+1].b.value).toBe(-1); +}); + + +test('DJN correctly jumps in immediate mode', () => { + const core = new Core(CORESIZE); + const pc = 2; + core.data[pc] = { + opcode: 'DJN', + a: { mode: 'direct', value: -3 }, + b: { mode: 'immediate', value: 1 }, + }; + const ins = core.data[pc]; + + expect(DJN(core, pc, ins)).toEqual([pc+1]); + expect(core.data[pc].b.value).toBe(0); + expect(DJN(core, pc, ins)).toEqual([CORESIZE-1]); + expect(core.data[pc].b.value).toBe(-1); +}); + + -- cgit v1.2.1