Add transformMultiplyLoops optimization pass and switch to using Maps

This commit is contained in:
Juhani Krekelä 2018-05-25 18:24:27 +03:00
parent eacbc95cea
commit 07e0d38f9e
2 changed files with 116 additions and 27 deletions

View File

@ -21,9 +21,8 @@ TODO
---- ----
### gir.js ### gir.js
* Implement `:;#` * Implement `:;#`
* Optimization pass to turn multiply loops into commands that do `x += y * c` * Make VM and transformMultiplyLoops use a Proxied object that gives out 0
* Make VM use a Proxied object that gives out 0 for nonexistent elements for for nonexistent elements for tape
its memory
* Keep a cache of compiled programs in `run()` * Keep a cache of compiled programs in `run()`
* Support for other types of EOF? * Support for other types of EOF?

138
gir.js
View File

@ -26,6 +26,9 @@ const loop = Symbol('loop');
// Can have offset property // Can have offset property
const clear = Symbol('clear'); const clear = Symbol('clear');
// [>+>++<<-] → {type: move, changes: Map { 0 → -1, 1 → 1, 2 → 2 }}
const multiply = Symbol('multiply');
// {type: jumpIfZero, target: 5} // {type: jumpIfZero, target: 5}
const jumpIfZero = Symbol('jumpIfZero'); const jumpIfZero = Symbol('jumpIfZero');
// {type: jumpIfNonZero, target: 2} // {type: jumpIfNonZero, target: 2}
@ -205,6 +208,13 @@ function prettifyIR(parsed) {
line += ` (${command.offset})`; line += ` (${command.offset})`;
} }
lines.push(line); 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) { } else if(command.type == jumpIfZero) {
line += `jumpIfZero ${command.target}`; line += `jumpIfZero ${command.target}`;
lines.push(line); lines.push(line);
@ -212,7 +222,7 @@ function prettifyIR(parsed) {
line += `jumpIfNonZero ${command.target}`; line += `jumpIfNonZero ${command.target}`;
lines.push(line); lines.push(line);
} else { } else {
line += `unknown ${command.type}`; line += `unknown ${command.type.toString()}`;
lines.push(line); lines.push(line);
} }
} }
@ -349,7 +359,8 @@ function addOffsetProperties(parsed) {
// our isBalanced, which will be forced to // our isBalanced, which will be forced to
// false if any inner loop is not balanced // false if any inner loop is not balanced
} else { } else {
throw new UnknownIRError(); throw new UnknownIRError(
`Unknown command ${command.type.toString()}`);
} }
} }
@ -370,7 +381,68 @@ function addOffsetProperties(parsed) {
return worker(parsed).offsetted; 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] // ([offsetCommandObjects]) → [flatCommandObjects]
function flattenLoops(offsetted) { function flattenLoops(offsetted) {
@ -432,6 +504,7 @@ function optimize(parsed) {
joinAdjacentOps, joinAdjacentOps,
transformClearLoops, transformClearLoops,
addOffsetProperties, addOffsetProperties,
transformMultiplyLoops,
flattenLoops flattenLoops
] ]
return optimizations.reduce((IR, optimization) => return optimizations.reduce((IR, optimization) =>
@ -449,7 +522,7 @@ function newVM(program, input) {
program: program, program: program,
ip: 0, ip: 0,
memory: Object.create(null), memory: new Map(),
tapeHead: 0, tapeHead: 0,
input: input, input: input,
@ -468,10 +541,7 @@ function runVM(state, maxCycles = null) {
// Create a copy of the memory, since we're going to modify it // 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 // TODO: Make memory into a Proxied thing that returns 0 if it
// doesn't have the requested cell // doesn't have the requested cell
let memory = Object.create(null); let memory = new Map(state.memory.entries());
for(let key in state.memory) {
memory[key] = state.memory[key];
}
let tapeHead = state.tapeHead; let tapeHead = state.tapeHead;
// Create copies of input and output, since we might modify them // 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 // These have an offset property, add it
index += command.offset; index += command.offset;
// Fall through // Fall through
case multiply:
case jumpIfZero: case jumpIfZero:
case jumpIfNonZero: case jumpIfNonZero:
// Ensure the cell exists // Ensure the cell exists
if(!(index in memory)) { if(!memory.has(index)) {
memory[index] = 0; memory.set(index, 0);
} }
} }
// Run the command // Run the command
switch(command.type) { switch(command.type) {
case add: case add:
if(!(index in memory)) memory[index] = 0; let old = memory.get(index);
memory[index] += command.value; memory.set(index, (command.value + old) & 0xFF);
// Implement wraparound
memory[index] = memory[index] & 0xFF;
ip++; ip++;
break; break;
case moveHead: case moveHead:
tapeHead += command.value; tapeHead += command.value;
ip++; ip++;
break; break;
case writeByte: case writeByte:
if(!(index in memory)) memory[index] = 0; output.push(memory.get(index));
output.push(memory[index]);
ip++; ip++;
break; break;
@ -535,22 +605,42 @@ function runVM(state, maxCycles = null) {
// Have we reached EOF? // Have we reached EOF?
if(input.length == 0) { if(input.length == 0) {
// Yes, return 0 // Yes, return 0
memory[index] = 0; memory.set(index, 0);
} else { } else {
// No, return character // No, return character
memory[index] = input.shift(); memory.set(index, input.shift());
} }
ip++; ip++;
break; break;
case clear: 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++; ip++;
break; break;
case jumpIfZero: case jumpIfZero:
if(!(index in memory)) memory[index] = 0; if(memory.get(index) == 0) {
if(memory[index] == 0) {
ip = command.target; ip = command.target;
} else { } else {
ip++; ip++;
@ -558,8 +648,7 @@ function runVM(state, maxCycles = null) {
break; break;
case jumpIfNonZero: case jumpIfNonZero:
if(!(index in memory)) memory[index] = 0; if(memory.get(index) != 0) {
if(memory[index] != 0) {
ip = command.target; ip = command.target;
} else { } else {
ip++; ip++;
@ -568,7 +657,8 @@ function runVM(state, maxCycles = null) {
default: default:
// Unknown command type // Unknown command type
throw new UnknownIRError(); throw new UnknownIRError(
`Unknown command ${command.type.toString()}`);
} }
} }