Full TCO (tail-call-elimination)

now Snap! really *is* Scheme :-)
pull/3/merge
jmoenig 2014-11-25 17:51:04 +01:00
rodzic 75849a59a2
commit 723c232f3d
2 zmienionych plików z 49 dodań i 54 usunięć

Wyświetl plik

@ -2357,3 +2357,4 @@ ______
1411225 1411225
------- -------
* Threads: Evaluator optimizations (reducing the stack size for reporters) * Threads: Evaluator optimizations (reducing the stack size for reporters)
* Threads: Full TCO (tail-call-elimination), now Snap! *is* Scheme :-)

Wyświetl plik

@ -564,12 +564,14 @@ Process.prototype.doReport = function (block) {
this.popContext(); this.popContext();
} }
} }
if (this.context.expression === 'expectReport') { if (this.context) {
// pop off inserted top-level exit context if (this.context.expression === 'expectReport') {
this.popContext(); // pop off inserted top-level exit context
} else { this.popContext();
// un-tag and preserve original caller } else {
this.context.tag = null; // un-tag and preserve original caller
this.context.tag = null;
}
} }
this.pushContext(block.inputs()[0], outer); this.pushContext(block.inputs()[0], outer);
}; };
@ -831,7 +833,6 @@ Process.prototype.evaluate = function (
caller = this.context.parentContext, caller = this.context.parentContext,
exit, exit,
runnable, runnable,
extra,
parms = args.asArray(), parms = args.asArray(),
i, i,
value; value;
@ -845,17 +846,11 @@ Process.prototype.evaluate = function (
outer, outer,
context.receiver context.receiver
); );
extra = new Context(runnable, 'doYield'); this.context.parentContext = runnable;
// Note: if the context's expression is a ReporterBlockMorph,
// the extra context gets popped off immediately without taking
// effect (i.e. it doesn't yield within evaluating a stack of
// nested reporters)
if (context.expression instanceof ReporterBlockMorph) { if (context.expression instanceof ReporterBlockMorph) {
this.context.parentContext = extra; // auto-"warp" nested reporters
} else { this.readyToYield = (Date.now() - this.lastYield > this.timeout);
this.context.parentContext = runnable;
} }
// assign parameters if any were passed // assign parameters if any were passed
@ -1062,7 +1057,6 @@ Process.prototype.evaluateCustomBlock = function () {
parms = args.asArray(), parms = args.asArray(),
runnable, runnable,
exit, exit,
extra,
i, i,
value, value,
outer; outer;
@ -1081,8 +1075,7 @@ Process.prototype.evaluateCustomBlock = function () {
outer.receiver outer.receiver
); );
runnable.isCustomBlock = true; runnable.isCustomBlock = true;
extra = new Context(runnable, 'doYield'); this.context.parentContext = runnable;
this.context.parentContext = extra;
// passing parameters if any were passed // passing parameters if any were passed
if (parms.length > 0) { if (parms.length > 0) {
@ -1104,40 +1097,43 @@ Process.prototype.evaluateCustomBlock = function () {
} }
} }
if (runnable.expression instanceof CommandBlockMorph) { // tag return target
// insert a reporter exit tag for the if (this.context.expression.definition.type !== 'command') {
// CALL SCRIPT primitive variant if (caller) {
if (this.context.expression.definition.type !== 'command') { // tag caller, so "report" can catch it later
if (caller) { caller.tag = 'exit';
// tag caller, so "report" can catch it later
caller.tag = 'exit';
} else {
// top-level context, insert a tagged exit context
// which "report" can catch later
exit = new Context(
runnable.parentContext,
'expectReport',
outer,
outer.receiver
);
exit.tag = 'exit';
runnable.parentContext = exit;
}
} else { } else {
// tag all "stop this block" blocks with the current // top-level context, insert a tagged exit context
// procedureCount as exitTag, and mark all "report" blocks // which "report" can catch later
// as being inside a custom command definition exit = new Context(
runnable.expression.tagExitBlocks(this.procedureCount, true); runnable.parentContext,
'expectReport',
// tag the caller with the current procedure count, so outer,
// "stop this block" blocks can catch it, but only outer.receiver
// if the caller hasn't been tagged already );
if (caller && !caller.tag) { exit.tag = 'exit';
caller.tag = this.procedureCount; runnable.parentContext = exit;
} }
// auto-"warp" nested reporters
this.readyToYield = (Date.now() - this.lastYield > this.timeout);
} else {
// tag all "stop this block" blocks with the current
// procedureCount as exitTag, and mark all "report" blocks
// as being inside a custom command definition
runnable.expression.tagExitBlocks(this.procedureCount, true);
// tag the caller with the current procedure count, so
// "stop this block" blocks can catch it, but only
// if the caller hasn't been tagged already
if (caller && !caller.tag) {
caller.tag = this.procedureCount;
}
// yield commands unless explicitly "warped"
if (!this.isAtomic) {
this.readyToYield = true;
} }
runnable.expression = runnable.expression.blockSequence();
} }
runnable.expression = runnable.expression.blockSequence();
}; };
// Process variables primitives // Process variables primitives
@ -2920,12 +2916,10 @@ Context.prototype.continuation = function () {
} else if (this.parentContext) { } else if (this.parentContext) {
cont = this.parentContext; cont = this.parentContext;
} else { } else {
return new Context(null, 'doStop'); return new Context(null, 'doYield');
}
if (cont.expression === 'expectReport') {
return cont.continuation();
} }
cont = cont.copyForContinuation(); cont = cont.copyForContinuation();
cont.tag = null;
cont.isContinuation = true; cont.isContinuation = true;
return cont; return cont;
}; };