From 434b1da69efeff3ed3e5cfac895c94bf497ba231 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Wed, 22 Dec 2021 23:47:28 -0600 Subject: [PATCH] Introduce procedure warp variants We now know at compile time whether a procedure is in warp mode or not This allows more optimizations to be made in non-warp scripts used by warp scripts --- src/compiler/compile.js | 6 +-- src/compiler/intermediate.js | 4 +- src/compiler/irgen.js | 96 ++++++++++++++++++++++++------------ src/compiler/jsgen.js | 5 +- 4 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/compiler/compile.js b/src/compiler/compile.js index a958ffd7..4193a211 100644 --- a/src/compiler/compile.js +++ b/src/compiler/compile.js @@ -37,10 +37,10 @@ const compile = thread => { const entry = compileScript(ir.entry); - for (const procedureCode of Object.keys(ir.procedures)) { - const procedureData = ir.procedures[procedureCode]; + for (const procedureVariant of Object.keys(ir.procedures)) { + const procedureData = ir.procedures[procedureVariant]; const procedureTree = compileScript(procedureData); - procedures[procedureCode] = procedureTree; + procedures[procedureVariant] = procedureTree; } return { diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index f1e0af30..f4da96ac 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -43,7 +43,7 @@ class IntermediateScript { this.isProcedure = false; /** - * The name of this procedure, if any. + * This procedure's code, if any. * @type {string} */ this.procedureCode = ''; @@ -100,7 +100,7 @@ class IntermediateRepresentation { this.entry = null; /** - * Maps procedure IDs to their intermediate script. + * Maps procedure variants to their intermediate script. * @type {Object.} */ this.procedures = {}; diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index fbdc808d..a97c2066 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -47,6 +47,30 @@ const createVariableData = (scope, varObj) => ({ isCloud: varObj.isCloud }); +/** + * @param {string} code + * @param {boolean} warp + * @returns {string} + */ +const generateProcedureVariant = (code, warp) => { + if (warp) { + return `W${code}`; + } + return `Z${code}`; +}; + +/** + * @param {string} variant Variant generated by generateProcedureVariant() + * @returns {string} original procedure code + */ +const parseProcedureCode = variant => variant.substring(1); + +/** + * @param {string} variant Variant generated by generateProcedureVariant() + * @returns {boolean} true if warp enabled + */ +const parseIsWarp = variant => variant.charAt(0) === 'W'; + class ScriptTreeGenerator { constructor (thread) { /** @private */ @@ -74,14 +98,16 @@ class ScriptTreeGenerator { this.variableCache = {}; } - setProcedureCode (procedureCode) { + setProcedureVariant (procedureVariant) { + const procedureCode = parseProcedureCode(procedureVariant); + this.script.procedureCode = procedureCode; this.script.isProcedure = true; this.script.yields = false; const paramNamesIdsAndDefaults = this.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode); if (paramNamesIdsAndDefaults === null) { - throw new Error(`IR: cannot find procedure: ${procedureCode}`); + throw new Error(`IR: cannot find procedure: ${procedureVariant}`); } const [paramNames, _paramIds, _paramDefaults] = paramNamesIdsAndDefaults; @@ -1153,8 +1179,27 @@ class ScriptTreeGenerator { }; } - if (!this.script.dependedProcedures.includes(procedureCode)) { - this.script.dependedProcedures.push(procedureCode); + // These are guaranteed to exist by previous checks + const definitionId = this.blocks.getProcedureDefinition(procedureCode); + const definitionBlock = this.blocks.getBlock(definitionId); + const innerDefinition = this.blocks.getBlock(definitionBlock.inputs.custom_block.block); + + let isWarp = this.script.isWarp; + if (!isWarp) { + if (innerDefinition && innerDefinition.mutation) { + const warp = innerDefinition.mutation.warp; + if (typeof warp === 'boolean') { + isWarp = warp; + } else if (typeof warp === 'string') { + isWarp = JSON.parse(warp); + } + } + } + + const variant = generateProcedureVariant(procedureCode, isWarp); + + if (!this.script.dependedProcedures.includes(variant)) { + this.script.dependedProcedures.push(variant); } // Non-warp direct recursion yields. @@ -1181,6 +1226,7 @@ class ScriptTreeGenerator { return { kind: 'procedures.call', code: procedureCode, + variant, arguments: args }; } @@ -1471,18 +1517,19 @@ class IRGenerator { } addProcedureDependencies (dependencies) { - for (const procedureCode of dependencies) { - if (this.procedures.hasOwnProperty(procedureCode)) { + for (const procedureVariant of dependencies) { + if (this.procedures.hasOwnProperty(procedureVariant)) { continue; } - if (this.compilingProcedures.has(procedureCode)) { + if (this.compilingProcedures.has(procedureVariant)) { continue; } - if (this.proceduresToCompile.has(procedureCode)) { + if (this.proceduresToCompile.has(procedureVariant)) { continue; } + const procedureCode = parseProcedureCode(procedureVariant); const definition = this.blocks.getProcedureDefinition(procedureCode); - this.proceduresToCompile.set(procedureCode, definition); + this.proceduresToCompile.set(procedureVariant, definition); } } @@ -1532,37 +1579,24 @@ class IRGenerator { // Compile any required procedures. // As procedures can depend on other procedures, this process may take several iterations. + const procedureTreeCache = this.blocks._cache.compiledProcedures; while (this.proceduresToCompile.size > 0) { this.compilingProcedures = this.proceduresToCompile; this.proceduresToCompile = new Map(); - for (const [procedureCode, definitionId] of this.compilingProcedures.entries()) { - const definitionBlock = this.blocks.getBlock(definitionId); - const innerDefinition = this.blocks.getBlock(definitionBlock.inputs.custom_block.block); - - // Extract the function's warp mode. - // See Sequencer.stepToProcedure - let isWarp = false; - if (innerDefinition && innerDefinition.mutation) { - const warp = innerDefinition.mutation.warp; - if (typeof warp === 'boolean') { - isWarp = warp; - } else if (typeof warp === 'string') { - isWarp = JSON.parse(warp); - } - } - - if (this.blocks._cache.compiledProcedures[procedureCode]) { - const result = this.blocks._cache.compiledProcedures[procedureCode]; - this.procedures[procedureCode] = result; + for (const [procedureVariant, definitionId] of this.compilingProcedures.entries()) { + if (procedureTreeCache[procedureVariant]) { + const result = procedureTreeCache[procedureVariant]; + this.procedures[procedureVariant] = result; this.addProcedureDependencies(result.dependedProcedures); } else { + const isWarp = parseIsWarp(procedureVariant); const generator = new ScriptTreeGenerator(this.thread); - generator.setProcedureCode(procedureCode); + generator.setProcedureVariant(procedureVariant); if (isWarp) generator.enableWarp(); const compiledProcedure = this.generateScriptTree(generator, definitionId); - this.procedures[procedureCode] = compiledProcedure; - this.blocks._cache.compiledProcedures[procedureCode] = compiledProcedure; + this.procedures[procedureVariant] = compiledProcedure; + procedureTreeCache[procedureVariant] = compiledProcedure; } } } diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index d45d21b3..11b3ccbc 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -927,8 +927,9 @@ class JSGenerator { case 'procedures.call': { const procedureCode = node.code; + const procedureVariant = node.variant; // Do not generate any code for empty procedures. - const procedureData = this.ir.procedures[procedureCode]; + const procedureData = this.ir.procedures[procedureVariant]; if (procedureData.stack === null) { break; } @@ -945,7 +946,7 @@ class JSGenerator { throw new Error('Script uses yielding procedure but is not marked as yielding.'); } } - this.source += `thread.procedures["${sanitize(procedureCode)}"](`; + this.source += `thread.procedures["${sanitize(procedureVariant)}"](`; // Only include arguments if the procedure accepts any. if (procedureData.arguments.length) { const args = [];