Skip to content

Commit 9acb696

Browse files
committed
Implement LOOP blocks in compiler
1 parent e9f0a36 commit 9acb696

File tree

4 files changed

+32
-12
lines changed

4 files changed

+32
-12
lines changed

src/compiler/compat-block-utility.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
const BlockUtility = require('../engine/block-utility');
22

33
class CompatibilityLayerBlockUtility extends BlockUtility {
4+
constructor () {
5+
super();
6+
this._stackFrame = {};
7+
}
8+
9+
get stackFrame () {
10+
return this._stackFrame;
11+
}
12+
413
// Branching operations are not supported.
514
startBranch () {
615
throw new Error('startBranch is not supported by this BlockUtility');
@@ -20,9 +29,10 @@ class CompatibilityLayerBlockUtility extends BlockUtility {
2029
throw new Error('getParam is not supported by this BlockUtility');
2130
}
2231

23-
init (thread, fakeBlockId) {
32+
init (thread, fakeBlockId, stackFrame) {
2433
this.thread = thread;
2534
this.sequencer = thread.target.runtime.sequencer;
35+
this._stackFrame = stackFrame;
2636
thread.stack[0] = fakeBlockId;
2737
}
2838
}

src/compiler/irgen.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,7 +1145,7 @@ class ScriptTreeGenerator {
11451145
const blockInfo = this.getBlockInfo(block.opcode);
11461146
if (blockInfo) {
11471147
const type = blockInfo.info.blockType;
1148-
if (type === BlockType.COMMAND || type === BlockType.CONDITIONAL) {
1148+
if (type === BlockType.COMMAND || type === BlockType.CONDITIONAL || type === BlockType.LOOP) {
11491149
return this.descendCompatLayer(block);
11501150
}
11511151
}
@@ -1402,7 +1402,7 @@ class ScriptTreeGenerator {
14021402
const blockInfo = this.getBlockInfo(block.opcode);
14031403
const blockType = (blockInfo && blockInfo.info && blockInfo.info.blockType) || BlockType.COMMAND;
14041404
const substacks = [];
1405-
if (blockType === BlockType.LOOP || blockType === BlockType.CONDITIONAL) {
1405+
if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP) {
14061406
const branchCount = blockInfo.info.branchCount;
14071407
for (let i = 0; i < branchCount; i++) {
14081408
const inputName = i === 0 ? 'SUBSTACK' : `SUBSTACK${i + 1}`;

src/compiler/jsexecute.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ runtimeFunctions.waitThreads = `const waitThreads = function*(threads) {
101101
* @param {function} blockFunction The primitive's function.
102102
* @param {boolean} useFlags Whether to set flags (hasResumedFromPromise)
103103
* @param {string} blockId Block ID to set on the emulated block utility.
104+
* @param {*|null} stackFrame Object to use as stack frame.
104105
* @returns {*} the value returned by the block, if any.
105106
*/
106107
runtimeFunctions.executeInCompatibilityLayer = `let hasResumedFromPromise = false;
@@ -131,16 +132,13 @@ const isPromise = value => (
131132
typeof value === 'object' &&
132133
typeof value.then === 'function'
133134
);
134-
const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, useFlags, blockId) {
135+
const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, useFlags, blockId, stackFrame) {
135136
const thread = globalState.thread;
136-
137-
// reset the stackframe
138-
// we only ever use one stackframe at a time, so this shouldn't cause issues
139-
thread.stackFrames[thread.stackFrames.length - 1].reuse(isWarp);
137+
const blockUtility = globalState.blockUtility;
138+
if (!stackFrame) stackFrame = {};
140139
141140
const executeBlock = () => {
142-
const blockUtility = globalState.blockUtility;
143-
blockUtility.init(thread, blockId);
141+
blockUtility.init(thread, blockId, stackFrame);
144142
return blockFunction(inputs, blockUtility);
145143
};
146144
@@ -195,6 +193,11 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use
195193
return returnValue;
196194
}`;
197195

196+
/**
197+
* @returns {unknown} An object to use as a stack frame.
198+
*/
199+
runtimeFunctions.persistentStackFrame = `const persistentStackFrame = () => ({});`;
200+
198201
/**
199202
* End the current script.
200203
*/

src/compiler/jsgen.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,12 @@ class JSGenerator {
774774
this.source += `}\n`;
775775
}
776776
this.source += `}\n`;
777+
} else if (node.blockType === BlockType.LOOP) {
778+
const stackFrameName = this.localVariables.next();
779+
this.source += `const ${stackFrameName} = persistentStackFrame();\n`;
780+
this.source += `while (toBoolean(${this.generateCompatibilityLayerCall(node, isLastInLoop, stackFrameName)})) {\n`;
781+
this.descendStack(node.substacks[0], new Frame(true));
782+
this.source += '}\n';
777783
}
778784

779785
if (isLastInLoop) {
@@ -1307,9 +1313,10 @@ class JSGenerator {
13071313
* Generate a call into the compatibility layer.
13081314
* @param {*} node The "compat" kind node to generate from.
13091315
* @param {boolean} setFlags Whether flags should be set describing how this function was processed.
1316+
* @param {string|null} [frameName] Name of the stack frame variable, if any
13101317
* @returns {string} The JS of the call.
13111318
*/
1312-
generateCompatibilityLayerCall (node, setFlags) {
1319+
generateCompatibilityLayerCall (node, setFlags, frameName = null) {
13131320
const opcode = node.opcode;
13141321

13151322
let result = 'yield* executeInCompatibilityLayer({';
@@ -1324,7 +1331,7 @@ class JSGenerator {
13241331
result += `"${sanitize(fieldName)}":"${sanitize(field)}",`;
13251332
}
13261333
const opcodeFunction = this.evaluateOnce(`runtime.getOpcodeFunction("${sanitize(opcode)}")`);
1327-
result += `}, ${opcodeFunction}, ${this.isWarp}, ${setFlags}, null)`;
1334+
result += `}, ${opcodeFunction}, ${this.isWarp}, ${setFlags}, null, ${frameName})`;
13281335

13291336
return result;
13301337
}

0 commit comments

Comments
 (0)