From 126a7b9aee87a142ad3f85688944dd8bddeca355 Mon Sep 17 00:00:00 2001 From: Alex Cui Date: Wed, 17 Jul 2024 23:00:55 +0800 Subject: [PATCH 1/3] :tada: feat(vm): basic compiler infrastructure Signed-off-by: Alex Cui --- packages/vm/src/compiler/compile.js | 16 ++++ packages/vm/src/compiler/constants.js | 27 ++++++ packages/vm/src/compiler/ir-generator.js | 106 ++++++++++++++++++++ packages/vm/src/compiler/ir-scratch.js | 112 ++++++++++++++++++++++ packages/vm/src/compiler/js-generator.js | 117 +++++++++++++++++++++++ packages/vm/src/compiler/type.d.ts | 47 +++++++++ 6 files changed, 425 insertions(+) create mode 100644 packages/vm/src/compiler/compile.js create mode 100644 packages/vm/src/compiler/constants.js create mode 100644 packages/vm/src/compiler/ir-generator.js create mode 100644 packages/vm/src/compiler/ir-scratch.js create mode 100644 packages/vm/src/compiler/js-generator.js create mode 100644 packages/vm/src/compiler/type.d.ts diff --git a/packages/vm/src/compiler/compile.js b/packages/vm/src/compiler/compile.js new file mode 100644 index 000000000..6c3a12167 --- /dev/null +++ b/packages/vm/src/compiler/compile.js @@ -0,0 +1,16 @@ +const IRGenerator = require('./ir-generator'); +const JSGenerator = require('./js-generator'); + +const compile = function (thread) { + const irGenerator = new IRGenerator(thread.blockContainer); + const ir = irGenerator.generateScript(thread.topBlock); + console.log(ir); + + const jsGenerator = new JSGenerator(); + const js = jsGenerator.generateInstructionList(ir); + console.log(js.code); + + return js; +}; + +module.exports = compile; diff --git a/packages/vm/src/compiler/constants.js b/packages/vm/src/compiler/constants.js new file mode 100644 index 000000000..56eedea5c --- /dev/null +++ b/packages/vm/src/compiler/constants.js @@ -0,0 +1,27 @@ +const TYPE_NUMBER = 1; +const TYPE_STRING = 2; +const TYPE_STATEMENT = 8; +const TYPE_UNKNOWN = 99; + +const IR_CONSTANT = 'constant'; +const IR_WHILE = 'control.while'; +const IR_REPEAT = 'control.repeat'; +const IR_IFELSE = 'control.ifelse'; +const IR_WAIT = 'control.wait'; +const IR_ADD = 'op.add'; +const IR_SUB = 'op.sub'; + +module.exports = { + TYPE_NUMBER, + TYPE_STRING, + TYPE_STATEMENT, + TYPE_UNKNOWN, + + IR_CONSTANT, + IR_WHILE, + IR_REPEAT, + IR_IFELSE, + IR_WAIT, + IR_ADD, + IR_SUB +}; diff --git a/packages/vm/src/compiler/ir-generator.js b/packages/vm/src/compiler/ir-generator.js new file mode 100644 index 000000000..50b54ea4d --- /dev/null +++ b/packages/vm/src/compiler/ir-generator.js @@ -0,0 +1,106 @@ +const ScratchIRGenerator = require('./ir-scratch'); + +class IRGenerator { + /** + * @param {Blocks} blockContainer + */ + constructor (blockContainer) { + this.blockContainer = blockContainer; + this.generator = ScratchIRGenerator; + } + + /** + * Generate IR for a list of blocks. + * @param {?string} blockId top block id + * @return {IRBaseInst[]} list of ir instructions + */ + generateScript (blockId) { + const result = []; + + while (blockId) { + const block = this.blockContainer.getBlock(blockId); + if (!block) { + break; + } + + result.push(this.generateBlock(blockId)); + blockId = block.next; // next block + } + + return result; + } + + /** + * Generate IR for a block. + * @param {?string} blockId block id + * @return {IRBaseInst} ir instruction + */ + generateBlock (blockId) { + const block = blockId && this.blockContainer.getBlock(blockId); + if (!block) { + return null; + } + + const opcode = block.opcode; + + const generate = this.generator[opcode]; + if (generate) { + return generate(block, this); + } else { + console.warn(`no ir generator for opcode ${opcode}`); + return IRGenerator.defaultGenerator(block, this); + } + } + + static defaultGenerator (block, generator) { + // TODO: compatible mode + return null; + } + + /** + * Generate IR from field. + * @param {Object} block block to generate ir + * @param {string} name field name + * @return {IRBaseInst} + */ + fromField (block, name) { + if (block.fields.hasOwnProperty(name)) { + return block.fields[name].value; + } else { + throw `no field ${name} in block ${block.opcode}`; + } + } + + /** + * Generate IR from field. + * @param {Object} block block to generate ir + * @param {string} name value name + * @return {IRBaseInst} + */ + fromValue (block, name) { + if (block.inputs.hasOwnProperty(name)) { + const blockId = block.inputs[name].block; + return this.generateBlock(blockId); + } else { + throw `no input ${name} in block ${block.opcode}`; + } + } + + /** + * Generate IR from statement. + * @param {Object} block block to generate ir + * @param {string} name statement name + * @return {IRBaseInst[]} + */ + fromStatement (block, name) { + if (block.inputs.hasOwnProperty(name)) { + const blockId = block.inputs[name].block; + return this.generateScript(blockId); + } else { + // input undefined when no block is connected to substack + return []; + } + } +} + +module.exports = IRGenerator; diff --git a/packages/vm/src/compiler/ir-scratch.js b/packages/vm/src/compiler/ir-scratch.js new file mode 100644 index 000000000..bbdbf4527 --- /dev/null +++ b/packages/vm/src/compiler/ir-scratch.js @@ -0,0 +1,112 @@ +const constants = require('./constants'); + +/** @import IRGenerator from './ir-generator.js' */ + +// common blocks +const colour_picker = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_CONSTANT, + value: generator.fromField(block, 'COLOUR') + }; +}; + +const math_number = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_CONSTANT, + value: generator.fromField(block, 'NUM') + }; +}; + +const math_integer = math_number; + +const math_whole_number = math_number; + +const math_positive_number = math_number; + +const math_angle = math_number; + +const text = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_CONSTANT, + value: generator.fromField(block, 'TEXT') + }; +}; + +// other test blocks +const control_forever = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_WHILE, + test: { + opcode: constants.IR_CONSTANT, + value: true + }, + body: generator.fromStatement(block, 'SUBSTACK') + }; +}; + +const control_repeat = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_REPEAT, + times: generator.fromValue(block, 'TIMES'), + body: generator.fromStatement(block, 'SUBSTACK') + }; +}; + +const control_if = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_IFELSE, + test: generator.fromValue(block, 'CONDITION'), + consequent: generator.fromStatement(block, 'SUBSTACK'), + alternate: [] + }; +}; + +const control_if_else = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_IFELSE, + test: generator.fromValue(block, 'CONDITION'), + consequent: generator.fromStatement(block, 'SUBSTACK'), + alternate: generator.fromStatement(block, 'SUBSTACK2') + }; +}; + +const control_wait = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_WAIT, + duration: generator.fromValue(block, 'DURATION') + }; +}; + +const operator_add = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_ADD, + left: generator.fromValue(block, 'NUM1'), + right: generator.fromValue(block, 'NUM2') + }; +}; + +const operator_sub = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_SUB, + left: generator.fromValue(block, 'NUM1'), + right: generator.fromValue(block, 'NUM2') + }; +}; + +module.exports = { + colour_picker, + math_number, + math_integer, + math_whole_number, + math_positive_number, + math_angle, + text, + + control_forever, + control_repeat, + control_if, + control_if_else, + control_wait, + operator_add, + operator_sub +}; diff --git a/packages/vm/src/compiler/js-generator.js b/packages/vm/src/compiler/js-generator.js new file mode 100644 index 000000000..5459a8293 --- /dev/null +++ b/packages/vm/src/compiler/js-generator.js @@ -0,0 +1,117 @@ +const constants = require('./constants'); + +class TypedExpression { + /** + * @param {string} code + * @param {number} type + */ + constructor (code, type) { + this.code = code; + this.type = type; + } + + asNumber () { + if (this.type === constants.TYPE_NUMBER) { + return this.code; + } else { + //return `+(${this.code}) || 0` + return `Cast.toNumber(${this.code})` + } + } + + asBoolean () { + if (this.type === constants.TYPE_NUMBER) { + return this.code; + } else { + return `Cast.toBoolean(${this.code})` + } + } +} + +class JSGenerator { + /** + * @param {Blocks} blockContainer + */ + constructor () {} + + /** + * Generate JavaScript code for IR instructions. + * @param {IRInst[]} irList list of IR instruction + * @return {TypedExpression} generated code + */ + generateInstructionList (irList) { + const codes = []; + + for (const ir of irList) { + const code = this.generateInstruction(ir); + codes.push(code.code); + } + + return new TypedExpression(codes.join(';'), constants.TYPE_STATEMENT); + } + + /** + * Generate JavaScript code for IR instruction. + * @param {IRInst} ir IR instruction + * @return {TypedExpression} generated code + */ + generateInstruction (ir) { + switch (ir.opcode) { + case constants.IR_CONSTANT: { + return new TypedExpression(ir.value, constants.TYPE_UNKNOWN); + } + case constants.IR_WHILE: { + const test = this.generateInstruction(ir.test); + const body = this.generateInstructionList(ir.body); + return new TypedExpression( + `while (${test.asNumber()}) {${body.code}}`, + constants.TYPE_STATEMENT + ); + } + case constants.IR_REPEAT: { + const times = this.generateInstruction(ir.times); + const body = this.generateInstructionList(ir.body); + return new TypedExpression( + `for (let i = Math.round(${times.asNumber()}); i >= 0; --i) {${body.code}}`, + constants.TYPE_STATEMENT + ); + } + case constants.IR_IFELSE: { + const test = this.generateInstruction(ir.test); + const consequent = this.generateInstructionList(ir.consequent); + const alternate = this.generateInstructionList(ir.alternate); + return new TypedExpression( + `if (${test}) {${consequent.code}} else {${alternate.code}}`, + constants.TYPE_STATEMENT + ); + } + case constants.IR_WAIT: { + return new TypedExpression( + 'console.log(wait)', + constants.TYPE_STATEMENT + ); + } + case constants.IR_ADD: { + const left = this.generateInstruction(ir.left); + const right = this.generateInstruction(ir.right); + return new TypedExpression( + `(${left.asNumber()}) + (${right.asNumber()})`, + constants.TYPE_NUMBER + ); + } + case constants.IR_SUB: { + const left = this.generateInstruction(ir.left); + const right = this.generateInstruction(ir.right); + return new TypedExpression( + `(${left.asNumber()}) - (${right.asNumber()})`, + constants.TYPE_NUMBER + ); + } + default: { + throw 'unknown ir instruction'; + } + } + } +} + +module.exports = JSGenerator; diff --git a/packages/vm/src/compiler/type.d.ts b/packages/vm/src/compiler/type.d.ts new file mode 100644 index 000000000..0f4f7a730 --- /dev/null +++ b/packages/vm/src/compiler/type.d.ts @@ -0,0 +1,47 @@ +interface IRBaseInst { + opcode: string; +} + +interface IRBinaryInst extends IRBaseInst { + left: IRBaseInst; + right: IRBaseInst; +} + +interface IRConstant extends IRBaseInst { + opcode: 'constant'; + value: any; +} + +interface IRWhileInst extends IRBaseInst { + opcode: 'control.while'; + test: IRBaseInst; + body: IRBaseInst[]; +} + +interface IRRepeatInst extends IRBaseInst { + opcode: 'control.repeat'; + times: IRBaseInst, + body: IRBaseInst[] +} + +interface IRIfElseInst extends IRBaseInst { + opcode: 'control.ifelse'; + test: IRBaseInst, + consequent: IRBaseInst[], + alternate: IRBaseInst[] +} + +interface IRWaitInst extends IRBaseInst { + opcode: 'control.wait'; + duration: IRBaseInst +} + +interface IRAddInst extends IRBinaryInst { + opcode: 'op.add' +} + +interface IRSubInst extends IRBinaryInst { + opcode: 'op.sub' +} + +type IRInst = IRConstant | IRWhileInst | IRRepeatInst | IRIfElseInst | IRWaitInst | IRAddInst | IRSubInst; From 64c5f5df758090d273df82fb083a5bb35ca07208 Mon Sep 17 00:00:00 2001 From: Alex Cui Date: Thu, 18 Jul 2024 16:34:43 +0800 Subject: [PATCH 2/3] :sparkles: feat(vm): basic compilation for variables Signed-off-by: Alex Cui --- packages/vm/src/compiler/compile.js | 2 +- packages/vm/src/compiler/constants.js | 8 +- packages/vm/src/compiler/ir-generator.js | 105 ++++++++++++++++++++++- packages/vm/src/compiler/ir-scratch.js | 19 +++- packages/vm/src/compiler/js-generator.js | 27 ++++++ packages/vm/src/compiler/type.d.ts | 38 ++++++-- 6 files changed, 184 insertions(+), 15 deletions(-) diff --git a/packages/vm/src/compiler/compile.js b/packages/vm/src/compiler/compile.js index 6c3a12167..ac88d720c 100644 --- a/packages/vm/src/compiler/compile.js +++ b/packages/vm/src/compiler/compile.js @@ -2,7 +2,7 @@ const IRGenerator = require('./ir-generator'); const JSGenerator = require('./js-generator'); const compile = function (thread) { - const irGenerator = new IRGenerator(thread.blockContainer); + const irGenerator = new IRGenerator(thread); const ir = irGenerator.generateScript(thread.topBlock); console.log(ir); diff --git a/packages/vm/src/compiler/constants.js b/packages/vm/src/compiler/constants.js index 56eedea5c..8477522b1 100644 --- a/packages/vm/src/compiler/constants.js +++ b/packages/vm/src/compiler/constants.js @@ -4,12 +4,15 @@ const TYPE_STATEMENT = 8; const TYPE_UNKNOWN = 99; const IR_CONSTANT = 'constant'; +const IR_IDENTIFIER = 'identifier'; const IR_WHILE = 'control.while'; const IR_REPEAT = 'control.repeat'; const IR_IFELSE = 'control.ifelse'; const IR_WAIT = 'control.wait'; const IR_ADD = 'op.add'; const IR_SUB = 'op.sub'; +const IR_LOAD = 'var.load'; +const IR_STORE = 'var.store'; module.exports = { TYPE_NUMBER, @@ -18,10 +21,13 @@ module.exports = { TYPE_UNKNOWN, IR_CONSTANT, + IR_IDENTIFIER, IR_WHILE, IR_REPEAT, IR_IFELSE, IR_WAIT, IR_ADD, - IR_SUB + IR_SUB, + IR_LOAD, + IR_STORE }; diff --git a/packages/vm/src/compiler/ir-generator.js b/packages/vm/src/compiler/ir-generator.js index 50b54ea4d..4bdb96f82 100644 --- a/packages/vm/src/compiler/ir-generator.js +++ b/packages/vm/src/compiler/ir-generator.js @@ -1,12 +1,27 @@ +const { IR_IDENTIFIER } = require('./constants'); const ScratchIRGenerator = require('./ir-scratch'); +/** @import Blocks from '../engine/blocks' */ +/** @import Target from '../engine/target' */ +/** @import Thread from '../engine/thread' */ + class IRGenerator { /** - * @param {Blocks} blockContainer + * @param {Thread} thread */ - constructor (blockContainer) { - this.blockContainer = blockContainer; + constructor (thread) { + this.thread = thread; + this.blockContainer = /** @type {Blocks} */ (thread.blockContainer); this.generator = ScratchIRGenerator; + + this.clear(); + } + + /** + * Clear compiler context. + */ + clear () { + this.variables = {}; } /** @@ -101,6 +116,90 @@ class IRGenerator { return []; } } + + /** + * Generate IR from variable field. + * @param {Object} block block to generate ir + * @param {string} name field name + * @return {IRBaseInst[]} + */ + fromVariable (block, name) { + if (block.fields.hasOwnProperty(name)) { + const field = block.fields[name]; + return this.lookupVariable(field.name, field.id); + } else { + throw `no variable field ${name} in block ${block.opcode}`; + } + } + + /** + * Generate IR from variable. + * @param {string} name name of data + * @param {string} id id of data + * @return {IRBaseInst} + */ + lookupVariable (name, id) { + const target = /** @type {Target} */ (this.thread.target); + const stage = /** @type {?Target} */ (target.runtime.getTargetForStage()); + + // lookup by id + if (target.variables.hasOwnProperty(id)) { + return { + opcode: IR_IDENTIFIER, + name: name, + id: id, + scope: 'target' + }; + } + + // lookup in stage by id + if (!target.isStage && stage) { + if (stage.variables.hasOwnProperty(id)) { + return { + opcode: IR_IDENTIFIER, + name: name, + id: id, + scope: 'stage' + }; + } + } + + // lookup by name + for (const varId in this.variables) { + const currVar = this.variables[varId]; + if (currVar.name === name && currVar.type === '') { + return { + opcode: IR_IDENTIFIER, + name: name, + id: varId, + scope: 'target' + }; + } + } + + // lookup in stage by name + if (!target.isStage && stage) { + for (const varId in stage.variables) { + const currVar = stage.variables[varId]; + if (currVar.name === name && currVar.type === '') { + return { + opcode: IR_IDENTIFIER, + name: name, + id: varId, + scope: 'stage' + }; + } + } + } + + // create a new variable in current target + return { + opcode: IR_IDENTIFIER, + name: name, + id: varId, + scope: 'target' + }; + } } module.exports = IRGenerator; diff --git a/packages/vm/src/compiler/ir-scratch.js b/packages/vm/src/compiler/ir-scratch.js index bbdbf4527..d8af631ad 100644 --- a/packages/vm/src/compiler/ir-scratch.js +++ b/packages/vm/src/compiler/ir-scratch.js @@ -93,6 +93,21 @@ const operator_sub = function (block, /** @type {IRGenerator} */ generator) { }; }; +const data_variable = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_LOAD, + variable: generator.fromVariable(block, 'VARIABLE') + }; +}; + +const data_setvariableto = function (block, /** @type {IRGenerator} */ generator) { + return { + opcode: constants.IR_STORE, + variable: generator.fromVariable(block, 'VARIABLE'), + value: generator.fromValue(block, 'VALUE') + }; +}; + module.exports = { colour_picker, math_number, @@ -108,5 +123,7 @@ module.exports = { control_if_else, control_wait, operator_add, - operator_sub + operator_sub, + data_variable, + data_setvariableto }; diff --git a/packages/vm/src/compiler/js-generator.js b/packages/vm/src/compiler/js-generator.js index 5459a8293..4224ef9bd 100644 --- a/packages/vm/src/compiler/js-generator.js +++ b/packages/vm/src/compiler/js-generator.js @@ -1,5 +1,14 @@ const constants = require('./constants'); +/** + * Get JS string literal. + * @param {string} str string + * @return {string} quoted string + */ +const quote = function (str) { + return JSON.stringify(str); +} + class TypedExpression { /** * @param {string} code @@ -60,6 +69,13 @@ class JSGenerator { case constants.IR_CONSTANT: { return new TypedExpression(ir.value, constants.TYPE_UNKNOWN); } + case constants.IR_IDENTIFIER: { + if (ir.scope === 'stage') { + return new TypedExpression(`stage.variables[${quote(ir.id)}].value`, constants.TYPE_UNKNOWN); + } else { + return new TypedExpression(`target.variables[${quote(ir.id)}].value`, constants.TYPE_UNKNOWN); + } + } case constants.IR_WHILE: { const test = this.generateInstruction(ir.test); const body = this.generateInstructionList(ir.body); @@ -107,6 +123,17 @@ class JSGenerator { constants.TYPE_NUMBER ); } + case constants.IR_LOAD: { + return this.generateInstruction(ir.variable); + } + case constants.IR_STORE: { + const variable = this.generateInstruction(ir.variable); + const value = this.generateInstruction(ir.value); + return new TypedExpression( + `(${variable.code}) = (${value.code})`, + constants.TYPE_STATEMENT + ); + } default: { throw 'unknown ir instruction'; } diff --git a/packages/vm/src/compiler/type.d.ts b/packages/vm/src/compiler/type.d.ts index 0f4f7a730..29d982ca7 100644 --- a/packages/vm/src/compiler/type.d.ts +++ b/packages/vm/src/compiler/type.d.ts @@ -12,6 +12,13 @@ interface IRConstant extends IRBaseInst { value: any; } +interface IRIdentifier extends IRBaseInst { + opcode: 'identifier'; + name: string, + id: string; + scope: 'target' | 'stage'; +} + interface IRWhileInst extends IRBaseInst { opcode: 'control.while'; test: IRBaseInst; @@ -20,28 +27,41 @@ interface IRWhileInst extends IRBaseInst { interface IRRepeatInst extends IRBaseInst { opcode: 'control.repeat'; - times: IRBaseInst, - body: IRBaseInst[] + times: IRBaseInst; + body: IRBaseInst[]; } interface IRIfElseInst extends IRBaseInst { opcode: 'control.ifelse'; - test: IRBaseInst, - consequent: IRBaseInst[], - alternate: IRBaseInst[] + test: IRBaseInst; + consequent: IRBaseInst[]; + alternate: IRBaseInst[]; } interface IRWaitInst extends IRBaseInst { opcode: 'control.wait'; - duration: IRBaseInst + duration: IRBaseInst; } interface IRAddInst extends IRBinaryInst { - opcode: 'op.add' + opcode: 'op.add'; } interface IRSubInst extends IRBinaryInst { - opcode: 'op.sub' + opcode: 'op.sub'; +} + +interface IRLoadInst extends IRBaseInst { + opcode: 'var.load'; + variable: IRIdentifier; +} + +interface IRStoreInst extends IRBaseInst { + opcode: 'var.store'; + variable: IRIdentifier; + value: IRBaseInst; } -type IRInst = IRConstant | IRWhileInst | IRRepeatInst | IRIfElseInst | IRWaitInst | IRAddInst | IRSubInst; +type IRInst = IRConstant | IRIdentifier | + IRWhileInst | IRRepeatInst | IRIfElseInst | IRWaitInst | + IRAddInst | IRSubInst | IRLoadInst | IRStoreInst; From 24ac9dbe6cde6b9bcc69b699a79c3709d4db105b Mon Sep 17 00:00:00 2001 From: Alex Cui Date: Thu, 18 Jul 2024 22:00:08 +0800 Subject: [PATCH 3/3] :sparkles: feat(compiler): simple execution Signed-off-by: Alex Cui --- packages/vm/src/compiler/compile.js | 29 ++++++++++++++++++------ packages/vm/src/compiler/js-generator.js | 17 ++++++++++++++ packages/vm/src/engine/runtime.js | 5 ++++ packages/vm/src/engine/sequencer.js | 9 ++++++++ packages/vm/src/engine/thread.js | 6 +++++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/packages/vm/src/compiler/compile.js b/packages/vm/src/compiler/compile.js index ac88d720c..dcdb06d80 100644 --- a/packages/vm/src/compiler/compile.js +++ b/packages/vm/src/compiler/compile.js @@ -1,16 +1,31 @@ const IRGenerator = require('./ir-generator'); const JSGenerator = require('./js-generator'); +const Cast = require('../util/cast'); +/** @import Thread from '../engine/thread.js' */ + +/** + * Compile an existing thread to executable object. + * @param {Thread} thread thread to compile + * @return {?Generator} compiled result, null if failed + */ const compile = function (thread) { - const irGenerator = new IRGenerator(thread); - const ir = irGenerator.generateScript(thread.topBlock); - console.log(ir); + try { + const irGenerator = new IRGenerator(thread); + const ir = irGenerator.generateScript(thread.topBlock); + console.log(ir); + + const jsGenerator = new JSGenerator(); + const fn = jsGenerator.compileForThread(ir).call(globalThis); - const jsGenerator = new JSGenerator(); - const js = jsGenerator.generateInstructionList(ir); - console.log(js.code); + console.log(fn.toString()); - return js; + thread.compileResult = fn.call({Cast}, thread)(); + } catch (e) { + console.error(e); + thread.compileResult = null; + } + return thread.compileResult; }; module.exports = compile; diff --git a/packages/vm/src/compiler/js-generator.js b/packages/vm/src/compiler/js-generator.js index 4224ef9bd..bce981aea 100644 --- a/packages/vm/src/compiler/js-generator.js +++ b/packages/vm/src/compiler/js-generator.js @@ -43,6 +43,23 @@ class JSGenerator { */ constructor () {} + /** + * Generate JavaScript for single thread. + * @param {IRInst[]} irList list of IR instruction + * @return {Function} generated function + */ + compileForThread (irList) { + return new Function([ + 'return (function (thread) {', + 'const target = thread.target;', + 'const stage = target.runtime.getTargetForStage();', + 'const {Cast} = this;', + 'return (function* () {', + this.generateInstructionList(irList).code + ';', + '});})' + ].join('')); + } + /** * Generate JavaScript code for IR instructions. * @param {IRInst[]} irList list of IR instruction diff --git a/packages/vm/src/engine/runtime.js b/packages/vm/src/engine/runtime.js index 3d5fedd57..01cfde18d 100644 --- a/packages/vm/src/engine/runtime.js +++ b/packages/vm/src/engine/runtime.js @@ -17,6 +17,7 @@ const StageLayering = require('./stage-layering'); const Variable = require('./variable'); const xmlEscape = require('../util/xml-escape'); const ScratchLinkWebSocket = require('../util/scratch-link-websocket'); +const compile = require('../compiler/compile'); // Virtual I/O devices. const Clock = require('../io/clock'); @@ -1717,6 +1718,10 @@ class Runtime extends EventEmitter { thread.blockContainer = thread.updateMonitor ? this.monitorBlocks : target.blocks; + + if (!thread.updateMonitor) { + compile(thread); + } thread.pushStack(id); this.threads.push(thread); diff --git a/packages/vm/src/engine/sequencer.js b/packages/vm/src/engine/sequencer.js index f76fa8316..b0ca08fec 100644 --- a/packages/vm/src/engine/sequencer.js +++ b/packages/vm/src/engine/sequencer.js @@ -177,6 +177,15 @@ class Sequencer { * @param {!Thread} thread Thread object to step. */ stepThread (thread) { + // check if thread is compiled + if (thread.compileResult) { + const result = thread.compileResult.next(); + if (result.done) { + thread.status = Thread.STATUS_DONE; + } + return; + } + let currentBlockId = thread.peekStack(); if (!currentBlockId) { // A "null block" - empty branch. diff --git a/packages/vm/src/engine/thread.js b/packages/vm/src/engine/thread.js index 96b1829e6..d15b2a3ab 100644 --- a/packages/vm/src/engine/thread.js +++ b/packages/vm/src/engine/thread.js @@ -199,6 +199,12 @@ class Thread { * @type {boolean} */ this.controlFlowed = false; + + /** + * The compiled JavaScript executable object. + * @type {?Generator} + */ + this.compileResult = null; } /**