Add transformMultiplyLoops optimization pass and switch to using Maps
This commit is contained in:
parent
eacbc95cea
commit
07e0d38f9e
2 changed files with 116 additions and 27 deletions
|
@ -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
138
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()}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue