Skip to content

Commit

Permalink
Merge "Optimize counter blocks in compiler"
Browse files Browse the repository at this point in the history
  • Loading branch information
Tacodiva committed Jan 27, 2024
1 parent a94785a commit 9054d11
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 197 deletions.
35 changes: 5 additions & 30 deletions src/compiler/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
const InputType = {
/** The value Infinity */
NUMBER_POS_INF: 0x001,
/** Any natrual number */
/** Any natural number */
NUMBER_POS_INT: 0x002,
/** Any positive fractional number, excluding integers. */
NUMBER_POS_FRACT: 0x004,
Expand Down Expand Up @@ -65,41 +65,13 @@ const InputType = {
/** Any number, excluding NaN. Equal to NUMBER_REAL | NUMBER_INF */
NUMBER: 0x0FF,
/** Any number, including NaN. Equal to NUMBER | NUMBER_NAN */
<<<<<<< HEAD
NUMBER_OR_NAN: 0x03F,
/** Anything that can be interperated as a number. Equal to NUMBER | STRING_NUM | BOOLEAN */
NUMBER_INTERPRETABLE: 0x15F,

=======
NUMBER_OR_NAN: 0x1FF,
/** Anything that can be interperated as a number. Equal to NUMBER | STRING_NUM | BOOLEAN */
NUMBER_INTERPRETABLE: 0x12FF,
<<<<<<< HEAD

>>>>>>> 2087b0e2 (Teach the compiler what an integer is)
=======

>>>>>>> a620ae88 (Merge "Fix compiler's hat implementation")
/** Any string which as a non-NaN neumeric interpretation, excluding ''. */
STRING_NUM: 0x200,
/** Any string which has no non-NaN neumeric interpretation, including ''. */
<<<<<<< HEAD
STRING_NAN: 0x080,
/** One of the strings 'true' or 'false'. */
STRING_BOOLEAN: 0x200,

/** Any string. Equal to STRING_NUM | STRING_NAN */
STRING: 0x2C0,

/** Any boolean. */
BOOLEAN: 0x100,
/** Any input that can be interperated as a boolean. Equal to BOOLEAN | STRING_BOOLEAN */
BOOLEAN_INTERPRETABLE: 0x300,


/** Any type. Equal to NUMBER_OR_NAN | STRING | BOOLEAN */
ANY: 0x3FF
=======
STRING_NAN: 0x400,
/** Either of the strings 'true' or 'false'. */
STRING_BOOLEAN: 0x800,
Expand All @@ -114,7 +86,6 @@ const InputType = {

/** Any type. Equal to NUMBER_OR_NAN | STRING | BOOLEAN */
ANY: 0x1FFF
>>>>>>> 2087b0e2 (Teach the compiler what an integer is)
};

/**
Expand Down Expand Up @@ -144,6 +115,8 @@ const StackOpcode = {
CONTROL_STOP_SCRIPT: "control.stopScript",
CONTROL_WAIT: "control.wait",
CONTROL_WAIT_UNTIL: "control.waitUntil",
CONTROL_CLEAR_COUNTER: "control.counterClear",
CONTORL_INCR_COUNTER: "control.counterIncr",

LIST_ADD: "list.add",
LIST_INSERT: "list.instert",
Expand Down Expand Up @@ -310,6 +283,8 @@ const InputOpcode = {
PROCEDURE_ARG_STRING_NUMBER: "args.stringNumber",
PROCEDURE_ARG_BOOLEAN: "args.boolean",

CONTROL_COUNTER: "control.counter",

TW_KEY_LAST_PRESSED: "tw.lastKeyPressed"
};

Expand Down
180 changes: 15 additions & 165 deletions src/compiler/irgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,9 @@ class ScriptTreeGenerator {
// This menu is special compared to other menus -- it actually has an opcode function.
return this.createConstantInput(block.fields.SOUND_MENU.value);

case 'control_get_counter':
return new IntermediateInput(InputOpcode.CONTROL_COUNTER, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO);

case 'tw_getLastKeyPressed':
return new IntermediateInput(InputOpcode.TW_KEY_LAST_PRESSED, InputType.STRING);

Expand Down Expand Up @@ -666,6 +669,10 @@ class ScriptTreeGenerator {
// We should consider analyzing this like we do for control_repeat_until
warpTimer: false
}, this.analyzeLoop());
case 'control_clear_counter':
return new IntermediateStackBlock(StackOpcode.CONTROL_CLEAR_COUNTER);
case 'control_incr_counter':
return new IntermediateStackBlock(StackOpcode.CONTORL_INCR_COUNTER);

case 'data_addtolist':
return new IntermediateStackBlock(StackOpcode.LIST_ADD, {
Expand Down Expand Up @@ -1102,7 +1109,7 @@ class ScriptTreeGenerator {
const variable = block.fields[fieldName];
const id = variable.id;

if (Object.prototype.hasOwnProperty.call(this.variableCache, id)) {
if (this.variableCache.hasOwnProperty(id)) {
return this.variableCache[id];
}

Expand All @@ -1123,20 +1130,20 @@ class ScriptTreeGenerator {
const stage = this.stage;

// Look for by ID in target...
if (Object.prototype.hasOwnProperty.call(target.variables, id)) {
if (target.variables.hasOwnProperty(id)) {
return createVariableData('target', target.variables[id]);
}

// Look for by ID in stage...
if (!target.isStage) {
if (stage && Object.prototype.hasOwnProperty.call(stage.variables, id)) {
if (stage && stage.variables.hasOwnProperty(id)) {
return createVariableData('stage', stage.variables[id]);
}
}

// Look for by name and type in target...
for (const varId in target.variables) {
if (Object.prototype.hasOwnProperty.call(target.variables, varId)) {
if (target.variables.hasOwnProperty(varId)) {
const currVar = target.variables[varId];
if (currVar.name === name && currVar.type === type) {
return createVariableData('target', currVar);
Expand All @@ -1147,7 +1154,7 @@ class ScriptTreeGenerator {
// Look for by name and type in stage...
if (!target.isStage && stage) {
for (const varId in stage.variables) {
if (Object.prototype.hasOwnProperty.call(stage.variables, varId)) {
if (stage.variables.hasOwnProperty(varId)) {
const currVar = stage.variables[varId];
if (currVar.name === name && currVar.type === type) {
return createVariableData('stage', currVar);
Expand All @@ -1165,7 +1172,7 @@ class ScriptTreeGenerator {
// This is necessary because the script cache is shared between clones.
// sprite.clones has all instances of this sprite including the original and all clones
for (const clone of target.sprite.clones) {
if (!Object.prototype.hasOwnProperty.call(clone.variables, id)) {
if (!clone.variables.hasOwnProperty(id)) {
clone.variables[id] = new Variable(id, name, type, false);
}
}
Expand All @@ -1174,97 +1181,6 @@ class ScriptTreeGenerator {
return createVariableData('target', newVariable);
}

descendProcedure (block) {
const procedureCode = block.mutation.proccode;
const paramNamesIdsAndDefaults = this.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode);
if (paramNamesIdsAndDefaults === null) {
return {
kind: 'noop'
};
}

const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults;

const addonBlock = this.runtime.getAddonBlock(procedureCode);
if (addonBlock) {
this.script.yields = true;
const args = {};
for (let i = 0; i < paramIds.length; i++) {
let value;
if (block.inputs[paramIds[i]] && block.inputs[paramIds[i]].block) {
value = this.descendInputOfBlock(block, paramIds[i]);
} else {
value = {
kind: 'constant',
value: paramDefaults[i]
};
}
args[paramNames[i]] = value;
}
return {
kind: 'addons.call',
code: procedureCode,
arguments: args,
blockId: block.id
};
}

const definitionId = this.blocks.getProcedureDefinition(procedureCode);
const definitionBlock = this.blocks.getBlock(definitionId);
if (!definitionBlock) {
return {
kind: 'noop'
};
}
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.
if (!this.script.isWarp) {
if (procedureCode === this.script.procedureCode) {
this.script.yields = true;
}
}

const args = [];
for (let i = 0; i < paramIds.length; i++) {
let value;
if (block.inputs[paramIds[i]] && block.inputs[paramIds[i]].block) {
value = this.descendInputOfBlock(block, paramIds[i]);
} else {
value = {
kind: 'constant',
value: paramDefaults[i]
};
}
args.push(value);
}

return {
kind: 'procedures.call',
code: procedureCode,
variant,
arguments: args
};
}

/**
* Descend into an input block that uses the compatibility layer.
* @param {*} block The block to use the compatibility layer for.
Expand All @@ -1273,10 +1189,10 @@ class ScriptTreeGenerator {
*/
descendCompatLayerInput(block) {
const inputs = {};
const fields = {};
for (const name of Object.keys(block.inputs)) {
inputs[name] = this.descendInputOfBlock(block, name, true);
}
const fields = {};
for (const name of Object.keys(block.fields)) {
fields[name] = block.fields[name].value;
}
Expand Down Expand Up @@ -1363,72 +1279,6 @@ class ScriptTreeGenerator {
}
}

descendVisualReport (block) {
if (!this.thread.stackClick || block.next) {
return null;
}
try {
return {
kind: 'visualReport',
input: this.descendInput(block)
};
} catch (e) {
return null;
}
}

/**
* @param {Block} hatBlock
*/
walkHat (hatBlock) {
const nextBlock = hatBlock.next;
const opcode = hatBlock.opcode;
const hatInfo = this.runtime._hats[opcode];

if (this.thread.stackClick) {
// We still need to treat the hat as a normal block (so executableHat should be false) for
// interpreter parity, but the reuslt is ignored.
const opcodeFunction = this.runtime.getOpcodeFunction(opcode);
if (opcodeFunction) {
return [
this.descendCompatLayer(hatBlock),
...this.walkStack(nextBlock)
];
}
return this.walkStack(nextBlock);
}

if (hatInfo.edgeActivated) {
// Edge-activated HAT
this.script.yields = true;
this.script.executableHat = true;
return [
{
kind: 'hat.edge',
id: hatBlock.id,
condition: this.descendCompatLayer(hatBlock)
},
...this.walkStack(nextBlock)
];
}

const opcodeFunction = this.runtime.getOpcodeFunction(opcode);
if (opcodeFunction) {
// Predicate-based HAT
this.script.yields = true;
this.script.executableHat = true;
return [
{
kind: 'hat.predicate',
condition: this.descendCompatLayer(hatBlock)
},
...this.walkStack(nextBlock)
];
}

return this.walkStack(nextBlock);
}

/**
* @param {*} hatBlock
* @returns {IntermediateStack}
Expand Down Expand Up @@ -1541,7 +1391,7 @@ class IRGenerator {

addProcedureDependencies(dependencies) {
for (const procedureVariant of dependencies) {
if (Object.prototype.hasOwnProperty.call(this.procedures, procedureVariant)) {
if (this.procedures.hasOwnProperty(procedureVariant)) {
continue;
}
if (this.compilingProcedures.has(procedureVariant)) {
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/jsgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@ class JSGenerator {
case InputOpcode.SENSING_TIMER_GET:
return 'runtime.ioDevices.clock.projectTimer()';

case InputOpcode.CONTROL_COUNTER:
return 'runtime.ext_scratch3_control._counter';

case InputOpcode.TW_KEY_LAST_PRESSED:
return 'runtime.ioDevices.keyboard.getLastKeyPressed()';

Expand Down Expand Up @@ -622,6 +625,12 @@ class JSGenerator {
}
this.source += `}\n`;
break;
case StackOpcode.CONTROL_CLEAR_COUNTER:
this.source += 'runtime.ext_scratch3_control._counter = 0;\n';
break;
case StackOpcode.CONTORL_INCR_COUNTER:
this.source += 'runtime.ext_scratch3_control._counter++;\n';
break;

case StackOpcode.EVENT_BROADCAST:
this.source += `startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: ${this.descendInput(node.broadcast)} });\n`;
Expand Down
8 changes: 6 additions & 2 deletions test/integration/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ fs.readdirSync(executeDir)
t.fail(reason);
},
plan (count) {
didPlan = true;
t.plan(Number(count));
if (didPlan) {
t.fail("Must plan exactly once.");
} else {
didPlan = true;
t.plan(Number(count));
}
},
end () {
didEnd = true;
Expand Down

0 comments on commit 9054d11

Please sign in to comment.