UNPKG

microvium

Version:

A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.

117 lines 5.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeIL = void 0; const lodash_1 = __importDefault(require("lodash")); const il_opcodes_1 = require("./il-opcodes"); const utils_1 = require("./utils"); /** * Normalize IL for the purposes of comparison during testing */ function normalizeIL(unit, opts = {}) { opts = { cullUnreachableBlocks: true, cullUnreachableInstructions: true, inlineUnnecessaryJumps: true, ...opts }; return { ...unit, functions: new Map((0, utils_1.entriesInOrder)(unit.functions).map(([funcID, func]) => [funcID, normalizeFunction(func)])) }; function normalizeFunction(func) { let blocks = func.blocks; if (opts.cullUnreachableBlocks) { blocks = cullUnreachableBlocks(blocks, func.entryBlockID); } if (opts.cullUnreachableInstructions) { blocks = cullUnreachableInstructions(blocks); } if (opts.inlineUnnecessaryJumps) { blocks = inlineUnnecessaryJumps(blocks); } return { ...func, blocks }; } } exports.normalizeIL = normalizeIL; function cullUnreachableBlocks(blocks, entryBlockID) { const blockIsReachableSet = new Set(); blockIsReachable(entryBlockID); return lodash_1.default.pickBy(blocks, b => blockIsReachableSet.has(b.id)); function blockIsReachable(blockID) { if (blockIsReachableSet.has(blockID)) { return; } blockIsReachableSet.add(blockID); const block = (0, utils_1.notUndefined)(blocks[blockID]); for (const op of block.operations) { for (const label of (0, il_opcodes_1.labelOperandsOfOperation)(op)) { blockIsReachable(label.targetBlockId); } } } } function cullUnreachableInstructions(blocks) { // The purpose of this function is to remove extra terminating instructions // from the code. This can happen if the user has provided code that // explicitly terminates a block before the end of the block (e.g. using // `break`). This culling is not for performance optimization. The reason it's // needed is for testing purposes, to get the code into a canonical form for // comparison. return lodash_1.default.mapValues(blocks, block => ({ ...block, operations: cullOperations(block.operations) })); function cullOperations(operations) { // Find the first instruction that terminates the block const index = operations.findIndex(op => il_opcodes_1.blockTerminatingOpcodes.has(op.opcode)); // Blocks in IL do not have a defined order, so there is no such thing as // "falling through" to the next block. Every block must terminate with a // terminating instruction. if (index === -1) return (0, utils_1.unexpected)(); if (index === operations.length - 1) return operations; return operations.slice(0, index + 1); } } function inlineUnnecessaryJumps(blocks) { blocks = { ...blocks }; // In the case where you jump to a block, and the block is only jumped to from // one source location, the jump can be removed and the blocks can just be // merged. const reachabilityCount = new Map(); const blockIsJumpedTo = new Map(); for (const [blockID, block] of Object.entries(blocks)) { for (const operation of block.operations) { for (const { targetBlockId } of (0, il_opcodes_1.labelOperandsOfOperation)(operation)) { reachabilityCount.set(targetBlockId, (reachabilityCount.get(targetBlockId) ?? 0) + 1); if (operation.opcode === 'Jump') { blockIsJumpedTo.set(targetBlockId, { fromBlockId: blockID, fromOperation: operation }); } } } } // "to" and "from" here refer to jumping to and jumping from for (const toBlockId of Object.keys(blocks)) { const toBlock = blocks[toBlockId]; const shouldInlineBlock = reachabilityCount.get(toBlockId) === 1 && blockIsJumpedTo.has(toBlockId); if (shouldInlineBlock) { const { fromBlockId, fromOperation } = blockIsJumpedTo.get(toBlockId); const fromBlock = blocks[fromBlockId]; let operations = [...fromBlock.operations]; // We're inlining at the end, so it's expected the last operation is at the end (0, utils_1.hardAssert)(operations[operations.length - 1] === fromOperation); operations.splice(operations.length - 1, 1, ...toBlock.operations); delete blocks[toBlockId]; blocks[fromBlockId] = { ...blocks[fromBlockId], operations }; } } return blocks; } //# sourceMappingURL=normalize-il.js.map