Add transformMultiplyLoops optimization pass and switch to using Maps
This commit is contained in:
parent
eacbc95cea
commit
07e0d38f9e
|
@ -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
138
gir.js
|
@ -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()}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue