From bf32e2e468c0177e33813dde915cfb7a54fbb6da Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 6 Jul 2020 19:34:38 -0500 Subject: [PATCH] Back to a yielding compiler --- .../blocks/compiler_scratch3_control.js | 48 +++-- .../blocks/compiler_scratch3_procedures.js | 10 +- src/compiler/compiler.js | 163 ++++++-------- src/compiler/execute.js | 203 ++++++------------ src/engine/thread.js | 3 +- 5 files changed, 170 insertions(+), 257 deletions(-) diff --git a/src/compiler/blocks/compiler_scratch3_control.js b/src/compiler/blocks/compiler_scratch3_control.js index b3c54832..f357924f 100644 --- a/src/compiler/blocks/compiler_scratch3_control.js +++ b/src/compiler/blocks/compiler_scratch3_control.js @@ -32,20 +32,28 @@ const repeat = /** @param {StatementUtil} util */ (util) => { const TIMES = util.input('TIMES'); const SUBSTACK = util.substack('SUBSTACK'); util.enterState(TIMES.asNumber()); - const label = util.putLabel(); - util.writeLn(`if (thread.state >= 0.5) {`); - util.writeLn(` thread.state--;`); + util.writeLn(`while (thread.state >= 0.5) { thread.state--;`) util.write(SUBSTACK); - util.jumpLazy(label); - util.writeLn('}'); + util.yieldLoop(); + util.writeLn(`}`) + // const label = util.putLabel(); + // util.writeLn(`if (thread.state >= 0.5) {`); + // util.writeLn(` thread.state--;`); + // util.write(SUBSTACK); + // util.jumpLazy(label); + // util.writeLn('}'); util.restoreState(); }; const forever = /** @param {StatementUtil} util */ (util) => { const SUBSTACK = util.substack('SUBSTACK'); - const label = util.putLabel(); + util.writeLn('while (true) {'); util.write(SUBSTACK); - util.jumpLazy(label); + util.yieldLoop(); + util.writeLn('}'); + // const label = util.putLabel(); + // util.write(SUBSTACK); + // util.jumpLazy(label); }; const if_ = /** @param {StatementUtil} util */ (util) => { @@ -70,21 +78,29 @@ const ifElse = /** @param {StatementUtil} util */ (util) => { const repeatUntil = /** @param {StatementUtil} util */ (util) => { const CONDITION = util.input('CONDITION'); const SUBSTACK = util.substack('SUBSTACK'); - const label = util.putLabel(); - util.writeLn(`if (!${CONDITION.asBoolean()}) {`); + util.writeLn(`while (!${CONDITION.asBoolean()}) {`); util.write(SUBSTACK); - util.jumpLazy(label); - util.writeLn(`}`); + util.yieldLoop(); + util.writeLn('}'); + // const label = util.putLabel(); + // util.writeLn(`if (!${CONDITION.asBoolean()}) {`); + // util.write(SUBSTACK); + // util.jumpLazy(label); + // util.writeLn(`}`); }; const while_ = /** @param {StatementUtil} util */ (util) => { const CONDITION = util.input('CONDITION'); const SUBSTACK = util.substack('SUBSTACK'); - const label = util.putLabel(); - util.writeLn(`if (${CONDITION.asBoolean()}) {`); + util.writeLn(`while (${CONDITION.asBoolean()}) {`); util.write(SUBSTACK); - util.jumpLazy(label); - util.writeLn(`}`); + util.yieldLoop(); + util.writeLn('}'); + // const label = util.putLabel(); + // util.writeLn(`if (${CONDITION.asBoolean()}) {`); + // util.write(SUBSTACK); + // util.jumpLazy(label); + // util.writeLn(`}`); }; const wait = /** @param {StatementUtil} util */ (util) => { @@ -124,7 +140,7 @@ const stop = /** @param {StatementUtil} util */ (util) => { } else if (STOP_OPTION === 'other scripts in sprite' || STOP_OPTION === 'other scripts in stage') { util.writeLn('target.runtime.stopForTarget(target, thread);'); } else if (STOP_OPTION === 'this script') { - util.writeLn('end(); return;'); + util.writeLn('return;'); } }; diff --git a/src/compiler/blocks/compiler_scratch3_procedures.js b/src/compiler/blocks/compiler_scratch3_procedures.js index 9cbd6e51..4a9626c6 100644 --- a/src/compiler/blocks/compiler_scratch3_procedures.js +++ b/src/compiler/blocks/compiler_scratch3_procedures.js @@ -34,11 +34,10 @@ const call = /** @param {StatementUtil} util */ (util) => { } const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; - const labelId = util.nextLabel(); util.compiler.dependProcedure(procedureCode); - util.write(`call("${util.safe(procedureCode)}", {`); + util.write(`yield* thread.procedures["${util.safe(procedureCode)}"].fn({`); for (let i = 0; i < paramIds.length; i++) { let value; @@ -55,16 +54,15 @@ const call = /** @param {StatementUtil} util */ (util) => { util.write(`"${util.safe(paramNames[i])}": ${value},`); } - util.writeLn(`}, ${labelId}); return;`); - util.putLabel(labelId); + util.writeLn(`});`); }; const getStringArgument = /** @param {InputUtil} util */ (util) => { const VALUE = util.fieldValueUnsafe('VALUE'); - return util.unknown(`thread.call.args["${util.safe(VALUE)}"]`); + return util.unknown(`C["${util.safe(VALUE)}"]`); }; const getBooleanArgument = /** @param {InputUtil} util */ (util) => { const VALUE = util.fieldValueUnsafe('VALUE'); - return util.boolean(`toBoolean(thread.call.args["${util.safe(VALUE)}"])`); + return util.boolean(`toBoolean(C["${util.safe(VALUE)}"])`); }; diff --git a/src/compiler/compiler.js b/src/compiler/compiler.js index 391e32e5..51a38fa3 100644 --- a/src/compiler/compiler.js +++ b/src/compiler/compiler.js @@ -173,53 +173,38 @@ class StatementUtil extends BlockUtil { this.source = ''; } - /** - * @returns {number} - */ - nextLabel() { - return this.compiler.nextLabel(); + yieldLoop() { + this.writeLn('if (thread.warp === 0) { debugger; yield; }'); } - /** - * @param {number} [label] - * @returns {number} - */ - putLabel(label) { - if (label === undefined) { - label = this.nextLabel(); - } - this.write(`{{${label}}}`); - return label; - } + // /** + // * Immediately jump to a label. + // * @param {number} label + // */ + // jump(label) { + // this.writeLn(`jump(${label}); return;`); + // } - /** - * Immediately jump to a label. - * @param {number} label - */ - jump(label) { - this.writeLn(`jump(${label}); return;`); - } + // /** + // * Lazily jump to a label. + // * If running in warp mode, this will be instant. Otherwise, it will run the next tick. + // * @param {number} label + // */ + // jumpLazy(label) { + // this.writeLn(`jumpLazy(${label}); return;`); + // } - /** - * Lazily jump to a label. - * If running in warp mode, this will be instant. Otherwise, it will run the next tick. - * @param {number} label - */ - jumpLazy(label) { - this.writeLn(`jumpLazy(${label}); return;`); - } - - /** - * Writes the necessary JS to yield the current thread until all given threads have finished executing. - * @param {string} threads The threads to wait for (eg. "thread.state") - */ - waitUntilThreadsComplete(threads) { - this.enterState(threads); - const label = this.putLabel(); - this.writeLn(`if (waitThreads(thread.state)) {`); - this.jumpLazy(label); - this.writeLn(`}`); - } + // /** + // * Writes the necessary JS to yield the current thread until all given threads have finished executing. + // * @param {string} threads The threads to wait for (eg. "thread.state") + // */ + // waitUntilThreadsComplete(threads) { + // this.enterState(threads); + // const label = this.putLabel(); + // this.writeLn(`if (waitThreads(thread.state)) {`); + // this.jumpLazy(label); + // this.writeLn(`}`); + // } /** * Write JS to this statement, followed by a newline. @@ -341,13 +326,12 @@ disableToString(CompiledInput.prototype.asBoolean); /** * @typedef {Object} CompiledProcedure * @property {boolean} warp - * @property {number} label + * @property {Function} fn */ /** * @typedef {Object} CompilationResult * @property {Function} startingFunction - * @property {Jump[]} jumps * @property {Object.} procedures */ @@ -371,15 +355,6 @@ class Compiler { /** @type {string} */ this.topBlock = thread.topBlock; - /** @type {number} */ - this.labelCount = 0; - - /** - * Function jump points. - * @type {Jump[]} - */ - this.jumps = []; - /** * Compiled procedures. * @type {Object.} @@ -474,54 +449,36 @@ class Compiler { return source; } - nextLabel() { - return this.labelCount++; - } - - parseContinuations(script) { - const labels = {}; - let index = 0; - let accumulator = 0; - - while (true) { - const labelStart = script.indexOf('{{', index); - if (labelStart === -1) { - break; - } - const labelEnd = script.indexOf('}}', index); - const id = script.substring(labelStart + 2, labelEnd); - const length = labelEnd + 2 - labelStart; - accumulator += length; - - labels[id] = labelEnd + 2 - accumulator; - - index = labelEnd + 2; - } - - const modifiedScript = script.replace(/{{\d+}}/g, ''); - - return { - labels, - script: modifiedScript, - }; - } - compileHat(topBlock) { - let script = ''; - script += '{{' + this.nextLabel() + '}}'; - script += this.compileStack(topBlock); - script += 'end();'; - - const parseResult = this.parseContinuations(script); - const parsedScript = parseResult.script; - - const startingLabelCount = this.jumps.length; - for (const label of Object.keys(parseResult.labels)) { - this.jumps[label] = execute.createContinuation(this, parsedScript.slice(parseResult.labels[label])); + let script = '(function*(C) {\n'; + if (this.isWarp) { + script += 'thread.warp++;\n'; + } else { + script += 'if (thread.warp) thread.warp++;\n'; } + script += this.compileStack(topBlock); + if (this.isWarp) { + script += 'thread.warp--;\n'; + } else { + script += 'debugger;\n'; + script += 'if (thread.warp) thread.warp--; if (thread.warp === 0) yield;\n'; + } + script += '\n});'; + // script += 'end();'; + + // const parseResult = this.parseContinuations(script); + // const parsedScript = parseResult.script; + + // const startingLabelCount = this.jumps.length; + // for (const label of Object.keys(parseResult.labels)) { + // this.jumps[label] = execute.createContinuation(this, parsedScript.slice(parseResult.labels[label])); + // } + + const fn = execute.evalCompiledScript(this, script); log.info(`[${this.target.getName()}] compiled script`, script); - return startingLabelCount; + + return fn; } /** @@ -568,17 +525,19 @@ class Compiler { } } - const procedureLabel = this.compileHat(bodyStart); + this.isWarp = isWarp; + + const procedureFn = this.compileHat(bodyStart); + this.procedures[procedureCode] = { warp: isWarp, - label: procedureLabel, + fn: procedureFn, }; } } return { - jumps: this.jumps, - startingFunction: this.jumps[startingFunction], + startingFunction: startingFunction, procedures: this.procedures, }; } diff --git a/src/compiler/execute.js b/src/compiler/execute.js index 802968fc..7af08273 100644 --- a/src/compiler/execute.js +++ b/src/compiler/execute.js @@ -5,62 +5,62 @@ const Timer = require('../util/timer'); // The JSDoc annotations define the function's contract. // Most of these functions are only used at runtime by generated scripts. Despite what your editor may say, they are not unused. -/** - * Immediately jump to a label. - * @param {number} id The label to jump to. - */ -const jump = (id) => { - immediate = thread.jumps[id]; -}; +// /** +// * Immediately jump to a label. +// * @param {number} id The label to jump to. +// */ +// const jump = (id) => { +// immediate = thread.jumps[id]; +// }; -/** - * Jump to a label. - * If in warp mode, this will be instant (like jump()) - * Otherwise, this jump will occur in the next tick loop. - * @param {number} id The label to jump to. - */ -const jumpLazy = (id) => { - if (thread.warp) { - jump(id); - } else { - thread.fn = thread.jumps[id]; - } -}; +// /** +// * Jump to a label. +// * If in warp mode, this will be instant (like jump()) +// * Otherwise, this jump will occur in the next tick loop. +// * @param {number} id The label to jump to. +// */ +// const jumpLazy = (id) => { +// if (thread.warp) { +// jump(id); +// } else { +// thread.fn = thread.jumps[id]; +// } +// }; -/** - * Call into a procedure. - * @param {string} procedureCode The procedure's name - * @param {*} args The arguments to pass to the procedure. - * @param {number} resume The label to return to when the procedure completes. - */ -const call = (procedureCode, args, resume) => { - thread.callStack.push(thread.call); - thread.call = { - args, - resume, - }; - // TODO: check recursion - const procedure = thread.procedures[procedureCode]; - if (procedure.warp || thread.warp) { - thread.warp++; - } - jump(procedure.label); -}; +// /** +// * Call into a procedure. +// * @param {string} procedureCode The procedure's name +// * @param {*} args The arguments to pass to the procedure. +// * @param {number} resume The label to return to when the procedure completes. +// */ +// const call = (procedureCode, args, resume) => { +// thread.callStack.push(thread.call); +// thread.call = { +// args, +// resume, +// }; +// // TODO: check recursion +// const procedure = thread.procedures[procedureCode]; +// if (procedure.warp || thread.warp) { +// thread.warp++; +// } +// jump(procedure.label); +// }; -/** - * End a script or procedure call. - */ -const end = () => { - if (thread.callStack.length) { - jump(thread.call.resume); - if (thread.warp) { - thread.warp--; - } - thread.call = thread.callStack.pop(); - } else { - retire(); - } -}; +// /** +// * End a script or procedure call. +// */ +// const end = () => { +// if (thread.callStack.length) { +// jump(thread.call.resume); +// if (thread.warp) { +// thread.warp--; +// } +// thread.call = thread.callStack.pop(); +// } else { +// retire(); +// } +// }; /** * Start hats by opcode. @@ -353,11 +353,11 @@ const mod = (n, modulus) => { return result; }; -/** - * If set, the executor will immediately start executing this function when the current function returns. - * @type {Function} - */ -var immediate; +// /** +// * If set, the executor will immediately start executing this function when the current function returns. +// * @type {Function} +// */ +// var immediate; /** * The currently running thread. * @type {Thread} @@ -377,13 +377,19 @@ const execute = (_thread) => { thread = _thread; target = thread.target; - _thread.fn(); + _thread.generator.next(); + + // while (_thread.warp) { + // _thread.generator.next(); + // } - while (immediate) { - var fn = immediate; - immediate = null; - fn(); - } + // _thread.fn(); + + // while (immediate) { + // var fn = immediate; + // immediate = null; + // fn(); + // } }; /** @@ -404,73 +410,6 @@ const evalCompiledScript = (compiler, _source) => { return eval(_source); }; -const createContinuation = (compiler, source) => { - // TODO: optimize, refactor - // TODO: support more than just "} else {" - // TODO: this is something that definitely deserves unit tests - var result = '(function continuation() {\n'; - var brackets = 0; - var delBrackets = 0; - var shouldDelete = false; - var here = 0; - var length = source.length; - while (here < length) { - var i = source.indexOf('{', here); - var j = source.indexOf('}', here); - var k = source.indexOf('return;', here); - if (k === -1) k = length; - if (i === -1 && j === -1) { - if (!shouldDelete) { - result += source.slice(here, k); - } - break; - } - if (i === -1) i = length; - if (j === -1) j = length; - if (shouldDelete) { - if (i < j) { - delBrackets++; - here = i + 1; - } else { - delBrackets--; - if (!delBrackets) { - shouldDelete = false; - } - here = j + 1; - } - } else { - if (brackets === 0 && k < i && k < j) { - result += source.slice(here, k); - break; - } - if (i < j) { - result += source.slice(here, i + 1); - brackets++; - here = i + 1; - } else { - result += source.slice(here, j); - here = j + 1; - if (source.substr(j, 8) === '} else {') { - if (brackets > 0) { - result += '} else {'; - here = j + 8; - } else { - shouldDelete = true; - delBrackets = 0; - } - } else { - if (brackets > 0) { - result += '}'; - brackets--; - } - } - } - } - } - result += '})'; - return evalCompiledScript(compiler, result); -}; - -execute.createContinuation = createContinuation; +execute.evalCompiledScript = evalCompiledScript; module.exports = execute; diff --git a/src/engine/thread.js b/src/engine/thread.js index 81a7d56a..a0bd6d92 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -198,6 +198,7 @@ class Thread { this.warp = 0; this.jumps = []; this.fn = null; + this.generator = null; this.state = null; this.stateStack = []; this.call = null; @@ -452,10 +453,10 @@ class Thread { } } - this.jumps = result.jumps; this.procedures = result.procedures; this.fn = result.startingFunction; this.isCompiled = true; + this.generator = this.fn(); } }