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
* 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?

138
gir.js
View File

@ -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()}`);
}
}