From 07e0d38f9ec603fa8edb033823fcfacec1053099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Fri, 25 May 2018 18:24:27 +0300 Subject: [PATCH] Add transformMultiplyLoops optimization pass and switch to using Maps --- README.md | 5 +- gir.js | 138 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 63888c8..1b443f9 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,8 @@ TODO ---- ### gir.js * Implement `:;#` -* Optimization pass to turn multiply loops into commands that do `x += y * c` -* Make VM use a Proxied object that gives out 0 for nonexistent elements for - its memory +* Make VM and transformMultiplyLoops use a Proxied object that gives out 0 + for nonexistent elements for tape * Keep a cache of compiled programs in `run()` * Support for other types of EOF? diff --git a/gir.js b/gir.js index 8ae5fdc..bff9fcf 100644 --- a/gir.js +++ b/gir.js @@ -26,6 +26,9 @@ const loop = Symbol('loop'); // Can have offset property const clear = Symbol('clear'); +// [>+>++<<-] → {type: move, changes: Map { 0 → -1, 1 → 1, 2 → 2 }} +const multiply = Symbol('multiply'); + // {type: jumpIfZero, target: 5} const jumpIfZero = Symbol('jumpIfZero'); // {type: jumpIfNonZero, target: 2} @@ -205,6 +208,13 @@ function prettifyIR(parsed) { line += ` (${command.offset})`; } lines.push(line); + } else if(command.type == multiply) { + let changes = []; + for(let [offset, value] of command.changes.entries()) { + changes.push(`${offset}: ${value}`); + } + line += `multiply ${changes.join(', ')}`; + lines.push(line); } else if(command.type == jumpIfZero) { line += `jumpIfZero ${command.target}`; lines.push(line); @@ -212,7 +222,7 @@ function prettifyIR(parsed) { line += `jumpIfNonZero ${command.target}`; lines.push(line); } else { - line += `unknown ${command.type}`; + line += `unknown ${command.type.toString()}`; lines.push(line); } } @@ -349,7 +359,8 @@ function addOffsetProperties(parsed) { // our isBalanced, which will be forced to // false if any inner loop is not balanced } else { - throw new UnknownIRError(); + throw new UnknownIRError( + `Unknown command ${command.type.toString()}`); } } @@ -370,7 +381,68 @@ function addOffsetProperties(parsed) { return worker(parsed).offsetted; } -// TODO: Optimization pass to turn copy loops into copy commands +// ([offsetCommandObjects]) → [offsetCommandObjects] +function transformMultiplyLoops(offsetted) { + let optimized = []; + + for(let command of offsetted) { + let isMultiplyLoop = false; + // Not necessarily a multiply loop, since multiply loops, + // in addition to being balanced loops with only adds, + // also decrement / increment the cell under tape head + // by one. However, these are loops we can run through the + // next processing step, and we can drop unfit ones after + // that + let maybeMultiplyLoop = command.type == loop && + command.isBalanced && + command.contents.every(x => x.type == add); + + // TODO: Change this to use a Proxy thingie + let changes = new Map(); + if(maybeMultiplyLoop) { + for(let addition of command.contents) { + // We already know all of these are adds + if(!changes.has(addition.offset)) { + changes.set(addition.offset, 0); + } + + let current = changes.get(addition.offset); + changes.set(addition.offset, + current + addition.value); + } + + // Did we actually have a multiply loop? + isMultiplyLoop = changes.has(0) && + (changes.get(0) == 1 || + changes.get(0) == -1); + } + + if(isMultiplyLoop) { + // If changes[0] is 1, we are dealing with + // a loop of the type [>-<+], which is + // (except for run time) same as [>+<-]. + // Transform former into latter + if(changes.get(0) == 1) { + for(let [offset, value] of changes.entries()) { + changes.set(offset, -value); + } + } + + optimized.push({type: multiply, + changes: changes}); + } else if(command.type == loop) { + // Recurse + optimized.push({type: loop, + contents: transformMultiplyLoops(command.contents), + balanced: command.balanced}); + } else { + // Pass through + optimized.push(command); + } + } + + return optimized; +} // ([offsetCommandObjects]) → [flatCommandObjects] function flattenLoops(offsetted) { @@ -432,6 +504,7 @@ function optimize(parsed) { joinAdjacentOps, transformClearLoops, addOffsetProperties, + transformMultiplyLoops, flattenLoops ] return optimizations.reduce((IR, optimization) => @@ -449,7 +522,7 @@ function newVM(program, input) { program: program, ip: 0, - memory: Object.create(null), + memory: new Map(), tapeHead: 0, input: input, @@ -468,10 +541,7 @@ function runVM(state, maxCycles = null) { // Create a copy of the memory, since we're going to modify it // TODO: Make memory into a Proxied thing that returns 0 if it // doesn't have the requested cell - let memory = Object.create(null); - for(let key in state.memory) { - memory[key] = state.memory[key]; - } + let memory = new Map(state.memory.entries()); let tapeHead = state.tapeHead; // Create copies of input and output, since we might modify them @@ -502,32 +572,32 @@ function runVM(state, maxCycles = null) { // These have an offset property, add it index += command.offset; // Fall through + case multiply: case jumpIfZero: case jumpIfNonZero: // Ensure the cell exists - if(!(index in memory)) { - memory[index] = 0; + if(!memory.has(index)) { + memory.set(index, 0); } } // Run the command switch(command.type) { case add: - if(!(index in memory)) memory[index] = 0; - memory[index] += command.value; - // Implement wraparound - memory[index] = memory[index] & 0xFF; + let old = memory.get(index); + memory.set(index, (command.value + old) & 0xFF); + ip++; break; case moveHead: tapeHead += command.value; + ip++; break; case writeByte: - if(!(index in memory)) memory[index] = 0; - output.push(memory[index]); + output.push(memory.get(index)); ip++; break; @@ -535,22 +605,42 @@ function runVM(state, maxCycles = null) { // Have we reached EOF? if(input.length == 0) { // Yes, return 0 - memory[index] = 0; + memory.set(index, 0); } else { // No, return character - memory[index] = input.shift(); + memory.set(index, input.shift()); } ip++; break; case clear: - memory[index] = 0; + memory.set(index, 0); + ip++; + break; + + case multiply: + if(command.changes.get(0) != -1) { + throw new UnknownIRError( + `multiply where change for 0 is ${command.changes.get(0)}`); + } + + let multiplier = memory.get(index); + + for(let [offset, change] of command.changes.entries()) { + let index = tapeHead + offset; + if(!memory.has(index)) { + memory.set(index, 0); + } + let old = memory.get(index); + memory.set(index, old + + multiplier * change); + } + ip++; break; case jumpIfZero: - if(!(index in memory)) memory[index] = 0; - if(memory[index] == 0) { + if(memory.get(index) == 0) { ip = command.target; } else { ip++; @@ -558,8 +648,7 @@ function runVM(state, maxCycles = null) { break; case jumpIfNonZero: - if(!(index in memory)) memory[index] = 0; - if(memory[index] != 0) { + if(memory.get(index) != 0) { ip = command.target; } else { ip++; @@ -568,7 +657,8 @@ function runVM(state, maxCycles = null) { default: // Unknown command type - throw new UnknownIRError(); + throw new UnknownIRError( + `Unknown command ${command.type.toString()}`); } }