├── .gitignore ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── README.md ├── design.md ├── lib ├── pipeline.js └── pipeline │ ├── api.js │ ├── base │ └── index.js │ ├── cfg │ ├── block.js │ ├── index.js │ └── node.js │ ├── dominance │ ├── block.js │ └── index.js │ ├── pipeline │ ├── format │ │ ├── graphviz.js │ │ ├── json.js │ │ └── printable.js │ ├── index.js │ └── node.js │ └── register │ ├── format │ ├── json.js │ └── printable.js │ ├── index.js │ ├── instruction.js │ ├── operand.js │ └── spill-type-range.js ├── package.json ├── register-allocation.md └── test ├── cfg-test.js ├── dominance-test.js ├── fixtures ├── index.js ├── p0.json ├── p1.json ├── p1cfg.json ├── p2dom.json └── r0.json ├── pipeline-test.js ├── printable-test.js └── register-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywordsOnNewLine": [ "else" ], 3 | "disallowMixedSpacesAndTabs": true, 4 | "disallowMultipleLineStrings": true, 5 | "disallowMultipleVarDecl": true, 6 | "disallowNewlineBeforeBlockStatements": true, 7 | "disallowQuotedKeysInObjects": true, 8 | "disallowSpaceAfterObjectKeys": true, 9 | "disallowSpaceAfterPrefixUnaryOperators": true, 10 | "disallowSpaceBeforePostfixUnaryOperators": true, 11 | "disallowSpacesInCallExpression": true, 12 | "disallowTrailingComma": true, 13 | "disallowTrailingWhitespace": true, 14 | "disallowYodaConditions": true, 15 | 16 | "requireCommaBeforeLineBreak": true, 17 | "requireOperatorBeforeLineBreak": true, 18 | "requireSpaceAfterBinaryOperators": true, 19 | "requireSpaceAfterKeywords": [ "if", "for", "while", "else", "try", "catch" ], 20 | "requireSpaceAfterLineComment": true, 21 | "requireSpaceBeforeBinaryOperators": true, 22 | "requireSpaceBeforeBlockStatements": true, 23 | "requireSpaceBeforeKeywords": [ "else", "catch" ], 24 | "requireSpaceBeforeObjectValues": true, 25 | "requireSpaceBetweenArguments": true, 26 | "requireSpacesInAnonymousFunctionExpression": { 27 | "beforeOpeningCurlyBrace": true 28 | }, 29 | "requireSpacesInFunctionDeclaration": { 30 | "beforeOpeningCurlyBrace": true 31 | }, 32 | "requireSpacesInFunctionExpression": { 33 | "beforeOpeningCurlyBrace": true 34 | }, 35 | "requireSpacesInConditionalExpression": true, 36 | "requireSpacesInForStatement": true, 37 | "requireSpacesInsideArrayBrackets": "all", 38 | "requireSpacesInsideObjectBrackets": "all", 39 | "requireDotNotation": true, 40 | 41 | "maximumLineLength": 80, 42 | "validateIndentation": 2, 43 | "validateLineBreaks": "LF", 44 | "validateParameterSeparator": ", ", 45 | "validateQuoteMarks": "'" 46 | } 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : false, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 2, // {int} Number of spaces to use for indentation 16 | "latedef" : true, // true: Require variables/functions to be defined before being used 17 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : false, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | "quotmark" : "single", // Quotation mark consistency: 24 | // false : do nothing (default) 25 | // true : ensure whatever is used is consistent 26 | // "single" : require single quotes 27 | // "double" : require double quotes 28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 29 | "unused" : true, // true: Require all defined variables be used 30 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 31 | "maxparams" : false, // {int} Max number of formal params allowed per function 32 | "maxdepth" : 3, // {int} Max depth of nested blocks (within functions) 33 | "maxstatements" : false, // {int} Max number statements per function 34 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 35 | "maxlen" : false, // {int} Max number of characters per line 36 | 37 | // Relaxing 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 40 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 41 | "eqnull" : false, // true: Tolerate use of `== null` 42 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 43 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 44 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 45 | // (ex: `for each`, multiple try/catch, function expression…) 46 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 47 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 48 | "funcscope" : false, // true: Tolerate defining variables inside control statements 49 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 50 | "iterator" : false, // true: Tolerate using the `__iterator__` property 51 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 52 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 53 | "laxcomma" : false, // true: Tolerate comma-first style coding 54 | "loopfunc" : false, // true: Tolerate functions being defined in loops 55 | "multistr" : false, // true: Tolerate multi-line strings 56 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 57 | "notypeof" : false, // true: Tolerate invalid typeof operator values 58 | "proto" : false, // true: Tolerate using the `__proto__` property 59 | "scripturl" : false, // true: Tolerate script-targeted URLs 60 | "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 61 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 62 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 63 | "validthis" : false, // true: Tolerate using this in a non-constructor function 64 | 65 | // Environments 66 | "browser" : true, // Web Browser (window, document, etc) 67 | "browserify" : true, // Browserify (node.js code in the browser) 68 | "couch" : false, // CouchDB 69 | "devel" : true, // Development/debugging (alert, confirm, etc) 70 | "dojo" : false, // Dojo Toolkit 71 | "jasmine" : false, // Jasmine 72 | "jquery" : false, // jQuery 73 | "mocha" : true, // Mocha 74 | "mootools" : false, // MooTools 75 | "node" : true, // Node.js 76 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 77 | "prototypejs" : false, // Prototype and Scriptaculous 78 | "qunit" : false, // QUnit 79 | "rhino" : false, // Rhino 80 | "shelljs" : false, // ShellJS 81 | "worker" : false, // Web Workers 82 | "wsh" : false, // Windows Scripting Host 83 | "yui" : false, // Yahoo User Interface 84 | 85 | // Custom Globals 86 | "globals" : { 87 | "module": true 88 | } // additional predefined global variables 89 | } 90 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "iojs" 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON pipeline 2 | [![Build Status](https://secure.travis-ci.org/indutny/json-pipeline.png)](http://travis-ci.org/indutny/json-pipeline) 3 | [![NPM version](https://badge.fury.io/js/json-pipeline.svg)](http://badge.fury.io/js/json-pipeline) 4 | 5 | A structure specification for the flexible compiler design, with support to the 6 | 3rd-party optimization phases. 7 | 8 | ## Design document 9 | 10 | Work on the design document is happening [here][0]. 11 | 12 | ## Useful reading 13 | 14 | - [Sea-of-nodes][1] and [this][2] 15 | - [SSA][3] 16 | - [CFG][4] 17 | - [Dominator tree][5], [Lengauer Tarjan Algorithm][6] 18 | - [GCM][7] 19 | 20 | ## LICENSE 21 | 22 | This software is licensed under the MIT License. 23 | 24 | Copyright Fedor Indutny, 2015. 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a 27 | copy of this software and associated documentation files (the 28 | "Software"), to deal in the Software without restriction, including 29 | without limitation the rights to use, copy, modify, merge, publish, 30 | distribute, sublicense, and/or sell copies of the Software, and to permit 31 | persons to whom the Software is furnished to do so, subject to the 32 | following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included 35 | in all copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 38 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 39 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 40 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 41 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 42 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 43 | USE OR OTHER DEALINGS IN THE SOFTWARE. 44 | 45 | [0]: design.md 46 | [1]: http://www.researchgate.net/profile/Cliff_Click/publication/2394127_Combining_Analyses_Combining_Optimizations/links/0a85e537233956f6dd000000.pdf 47 | [2]: http://static.squarespace.com/static/50030e0ac4aaab8fd03f41b7/50030ec0e4b0c0ebbd07b0e0/50030ec0e4b0c0ebbd07b268/1281379125883/ 48 | [3]: https://en.wikipedia.org/wiki/Static_single_assignment_form 49 | [4]: https://en.wikipedia.org/wiki/Control_flow_graph 50 | [5]: https://en.wikipedia.org/wiki/Dominator_(graph_theory) 51 | [6]: https://www.cs.princeton.edu/courses/archive/fall03/cs528/handouts/a%20fast%20algorithm%20for%20finding.pdf 52 | [7]: https://courses.cs.washington.edu/courses/cse501/04wi/papers/click-pldi95.pdf 53 | -------------------------------------------------------------------------------- /design.md: -------------------------------------------------------------------------------- 1 | # Data format for the compiler 2 | 3 | ## Conventions 4 | 5 | Opcode names MUST not contain `:` unless: 6 | 7 | * They are going to be consumed and renamed by one of the next stages 8 | * They represent platform specific low-level instructions. In this case the 9 | `:prefix` should be used. `platform-name` should not contain 10 | any characters except ones defined by the set `[a-zA-Z0-9_\-\.]` 11 | 12 | Every node may take several literal inputs, several regular inputs, and 13 | possibly a control input. 14 | 15 | ## Process 16 | 17 | Stage receive and produce data with indexes instead of pointers between 18 | structures, thus ensuring that the data might be serialized between all stages. 19 | 20 | Stage might transform input data to some internal representation with pointers 21 | instead of the indexes before performing any kind of operations. 22 | 23 | The compilation goes in a following steps. 24 | 25 | ### AST to SSA-less CFG 26 | 27 | AST is naively transformed into CFG graph with: 28 | 29 | * `ssa:load(index)` 30 | * `ssa:store(index, value)` 31 | 32 | for all local variable lookups. Context and global variable lookups should be 33 | generally done using language-specific opcodes, and are not subject to the SSA 34 | phase defined below. `index` is just a number literal (see literal inputs to 35 | nodes), and represents local variable stack cell to put/load the value from. 36 | 37 | SSA may replace some `ssa:load()`s with `ssa:undefined` if the value was not 38 | defined at that point. It is up to next phases to figure out what to do with 39 | such nodes. 40 | 41 | Every node (except region nodes) MUST be in the `nodes` list of one block. First 42 | block in `cfg` section is considered a `start` node. 43 | 44 | SSA-less CFG information is propagated to the next stage. 45 | 46 | ### SSA-less CFG to CFG+SSA 47 | 48 | Using dominator tree information, `ssa:load`s are replaced with proper 49 | `ssa:store`'s values or `ssa:phi` nodes. `ssa:phi` takes two inputs and 50 | has a control dependency on the CFG block that holds it. 51 | 52 | `start` and `region`'s `control` field points to the last control nodes 53 | in the predecessor blocks. 54 | 55 | `ssa:phi`, `jump`, and `if` nodes have the parent `region` or `start` node in 56 | the `control` field (or should have `region` or `start` node reachable through 57 | the `control` field chain). 58 | 59 | `ssa:phi` MUST be at the start of the block, or right after other `ssa:phi`. 60 | `if`, `jump` MUST be the last intruction in the block. 61 | 62 | CFG and dominance information is propagated to the next stage. 63 | 64 | ### CFG to Sea-of-Nodes 65 | 66 | Some optimizations may happen here. 67 | 68 | Nothing is propagated to the next stage, except the `nodes` DAG. 69 | 70 | ### Optimizations on Sea-of-Nodes 71 | 72 | Some optimizations may happen here. 73 | 74 | Propagate all inputs. 75 | 76 | ### Sea-of-Nodes to Low-level Sea-of-Nodes 77 | 78 | **Platform specific stage** 79 | 80 | Opcodes are replaced with their low-level counterparts, possibly generating more 81 | nodes that was previously in the graph. 82 | 83 | Propagate all inputs. 84 | 85 | ### Sea-of-Nodes to CFG 86 | 87 | Scheduling algorithm generates blocks and puts the nodes at proper 88 | positions into them, thus creating CFG out of DAG representation. Blocks are 89 | ordered to optimize amount of jumps. 90 | 91 | This stage propagates CFG and dominance information. 92 | 93 | ### Optional optimizations on CFG 94 | 95 | Some optimizations may happen here. 96 | 97 | Propagate all inputs. 98 | 99 | ### Register Allocation 100 | 101 | **Platform specific stage** 102 | 103 | Allocate register for each input/output of the node, and possibly additional 104 | temporary regiters. 105 | 106 | Propagate data in register allocator format (TBD). 107 | 108 | ### Generate machine code 109 | 110 | **Platform specific stage** 111 | 112 | Generate machine code using register allocator data. 113 | 114 | ## Representation 115 | 116 | ### JSON 117 | 118 | ```js 119 | { 120 | // Lookup by `node id` 121 | "nodes": [ 122 | { 123 | "opcode": "opcode id", 124 | "control": [ ...node ids... ], 125 | 126 | "literals": [ ...literal values... ], 127 | "inputs": [ ...node ids... ] 128 | } 129 | ], 130 | 131 | // Optional CFG information 132 | "cfg": { 133 | // Number of blocks MUST match number of `region` and `start` nodes 134 | "blocks": [ 135 | { 136 | "node": ...node id... // Points to region node 137 | "successors": [ ...node ids... ], 138 | "nodes": [ ...node ids... ] 139 | } 140 | ] 141 | }, 142 | 143 | // Optional dominance information 144 | "dominance": { 145 | // Number of blocks MUST match number of blocks in `cfg` section 146 | "blocks": [ 147 | { 148 | "node": ...node id... // Point to region node 149 | "parent": ...node id... or `null`, 150 | "frontier": [ 151 | [ ... ], 152 | ... 153 | [ ... ] 154 | ] 155 | } 156 | ] 157 | } 158 | } 159 | ``` 160 | 161 | ### Printable 162 | 163 | `iN` - where `N >= 0`, for each node 164 | `bN` - where `N >= 0`, for each block 165 | 166 | `iX = opcode , ` - for every node. 167 | 168 | Optionally nodes, might be placed into `block bN { ... }` section. 169 | 170 | `bX -> bY, ..., bZ` at the end of the block section to specify block 171 | successors. 172 | 173 | `bX => bY, ..., bZ` to specify children in dominance tree. It is in reverse in 174 | JSON only for the compactness of the representation. 175 | 176 | `bX ~> bY, ..., bZ` for dominance frontier 177 | 178 | ``` 179 | pipeline { 180 | # First block 181 | b0 { 182 | # NOTE: `^i1`/`^b0` specifies optional control dependency 183 | # (possibly multiple) 184 | i1 = opcode ^b0, 1, 2, ...literals, i2, i3, ... nodes 185 | } 186 | b0 -> b1, b2 187 | b0 => b1, b2, b3 188 | b0 ~> b1, b2, b3 189 | 190 | b1 { 191 | ... 192 | } 193 | b1 -> b3 194 | 195 | b2 { 196 | ... 197 | } 198 | b2 -> b3 199 | 200 | b3 { 201 | ... 202 | } 203 | } 204 | ``` 205 | 206 | ### Binary 207 | 208 | To be defined 209 | -------------------------------------------------------------------------------- /lib/pipeline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.Base = require('./pipeline/base'); 4 | 5 | exports.Pipeline = require('./pipeline/pipeline'); 6 | 7 | var formats = exports.Pipeline.formats; 8 | formats.json = require('./pipeline/pipeline/format/json'); 9 | formats.printable = require('./pipeline/pipeline/format/printable'); 10 | formats.graphviz = require('./pipeline/pipeline/format/graphviz'); 11 | 12 | exports.CFGBuilder = require('./pipeline/cfg'); 13 | exports.Dominance = require('./pipeline/dominance'); 14 | 15 | exports.Register = require('./pipeline/register'); 16 | 17 | var formats = exports.Register.formats; 18 | formats.json = require('./pipeline/register/format/json'); 19 | formats.printable = require('./pipeline/register/format/printable'); 20 | 21 | // A public API 22 | exports.create = require('./pipeline/api').create; 23 | -------------------------------------------------------------------------------- /lib/pipeline/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pipeline = require('../pipeline'); 4 | 5 | exports.create = function create(type) { 6 | if (type === 'cfg') 7 | return pipeline.CFGBuilder.create(); 8 | else if (type === 'dominance') 9 | return pipeline.Dominance.create(); 10 | else if (type === 'register') 11 | return pipeline.Register.create(); 12 | else 13 | return pipeline.Pipeline.create(); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/pipeline/base/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Base() { 4 | } 5 | module.exports = Base; 6 | 7 | Base.create = function create() { 8 | return new Base(); 9 | }; 10 | 11 | // To be defined in ancestors 12 | Base.prototype.formats = null; 13 | 14 | Base.prototype._selectFormat = function _selectFormat(format) { 15 | if (!format || format === 'json') 16 | return this.formats.json; 17 | else if (format === 'printable') 18 | return this.formats.printable; 19 | else if (format === 'graphviz') 20 | return this.formats.graphviz; 21 | else 22 | throw new Error('Unknown format: ' + format); 23 | }; 24 | 25 | Base.prototype.parse = function parse(data, sections, format) { 26 | if (typeof sections === 'string') { 27 | // .parse(data, format) 28 | format = sections; 29 | sections = {}; 30 | } 31 | 32 | var Parser = this._selectFormat(format); 33 | 34 | return new Parser(sections || {}, this).parse(data); 35 | }; 36 | 37 | Base.prototype.render = function render(sections, format) { 38 | if (typeof sections === 'string') { 39 | // .render(format) 40 | format = sections; 41 | sections = {}; 42 | } 43 | 44 | var Renderer = this._selectFormat(format); 45 | 46 | return new Renderer(sections || {}, this).render(); 47 | }; 48 | -------------------------------------------------------------------------------- /lib/pipeline/cfg/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var pipeline = require('../../pipeline'); 7 | 8 | function Block(opcode) { 9 | pipeline.Pipeline.Node.call(this, opcode || 'region'); 10 | this.blockIndex = 0; 11 | 12 | this.predecessors = []; 13 | this.successors = []; 14 | this.nodes = []; 15 | this.lastControl = this; 16 | } 17 | util.inherits(Block, pipeline.Pipeline.Node); 18 | module.exports = Block; 19 | 20 | Block.create = function create(opcode) { 21 | return new Block(opcode); 22 | }; 23 | 24 | Block.prototype.prepend = function prepend(node) { 25 | this.nodes.unshift(node); 26 | node.block = this; 27 | }; 28 | 29 | Block.prototype.insert = function insert(index, node) { 30 | this.nodes.splice(index, 0, node); 31 | node.block = this; 32 | }; 33 | 34 | Block.prototype.add = function add(node) { 35 | this.nodes.push(node); 36 | node.block = this; 37 | }; 38 | 39 | Block.prototype.addControl = function addControl(node) { 40 | node.setControl(this.lastControl); 41 | this.lastControl = node; 42 | this.add(node); 43 | }; 44 | 45 | Block.prototype.remove = function remove(index) { 46 | var node = this.nodes[index]; 47 | this.nodes.splice(index, 1); 48 | 49 | node.block = null; 50 | }; 51 | 52 | Block.prototype.jump = function jump(other) { 53 | this.successors.push(other); 54 | other.predecessors.push(this); 55 | }; 56 | 57 | Block.prototype.getLastControl = function getLastControl() { 58 | return this.lastControl; 59 | }; 60 | 61 | Block.prototype.setLastControl = function setLastControl(node) { 62 | this.lastControl = node; 63 | }; 64 | 65 | Block.prototype.computeLastControl = function computeLastControl() { 66 | if (this.nodes.length === 0) 67 | return; 68 | 69 | this.lastControl = this.nodes[this.nodes.length - 1]; 70 | assert(this.lastControl.isControl(), 'Last block node is not control'); 71 | }; 72 | 73 | Block.prototype.link = function link() { 74 | if (this.control.length !== 0) 75 | return; 76 | 77 | if (this.predecessors.length === 0) 78 | return; 79 | 80 | if (this.predecessors.length === 1) { 81 | var a = this.predecessors[0]; 82 | this.setControl(a.getLastControl()); 83 | return; 84 | } 85 | 86 | var a = this.predecessors[0]; 87 | var b = this.predecessors[1]; 88 | this.setControl(a.getLastControl(), b.getLastControl()); 89 | }; 90 | 91 | Block.prototype._shortId = function _shortId() { 92 | return 'b' + this.blockIndex + '[' + this.opcode + ']'; 93 | }; 94 | 95 | Block.prototype.verify = function verify() { 96 | pipeline.Pipeline.Node.prototype.verify.call(this); 97 | 98 | var id = this._shortId(); 99 | for (var i = 0; i < this.successors.length; i++) { 100 | var succ = this.successors[i]; 101 | assert(succ.predecessors.indexOf(this) !== -1, 102 | 'Not found ' + id + ' in ' + succ._shortId() + ' predecessors'); 103 | } 104 | 105 | for (var i = 0; i < this.predecessors.length; i++) { 106 | var pred = this.predecessors[i]; 107 | assert(pred.successors.indexOf(this) !== -1, 108 | 'Not found ' + id + ' in ' + pred._shortId() + ' successors'); 109 | } 110 | 111 | if (this.successors.length !== 2) 112 | return; 113 | 114 | assert.notEqual(this.predecessors.length, 2, 'X intersection at ' + id); 115 | for (var i = 0; i < this.successors.length; i++) { 116 | var succ = this.successors[i]; 117 | assert.notEqual(succ.predecessors.length, 2, 'Z intersection at ' + id); 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /lib/pipeline/cfg/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var BitField = require('bitfield.js'); 5 | 6 | var pipeline = require('../../pipeline'); 7 | var Pipeline = pipeline.Pipeline; 8 | 9 | function CFGBuilder() { 10 | Pipeline.call(this); 11 | 12 | this.blocks = []; 13 | this.currentBlock = null; 14 | } 15 | util.inherits(CFGBuilder, Pipeline); 16 | module.exports = CFGBuilder; 17 | 18 | CFGBuilder.Block = require('./block'); 19 | CFGBuilder.prototype.Block = CFGBuilder.Block; 20 | 21 | CFGBuilder.Node = require('./node'); 22 | CFGBuilder.prototype.Node = CFGBuilder.Node; 23 | 24 | CFGBuilder.create = function create() { 25 | return new CFGBuilder(); 26 | }; 27 | 28 | CFGBuilder.prototype.createBlock = function createBlock(opcode) { 29 | var res = this.Block.create(opcode || 30 | (this.blocks.length === 0 ? 'start' : null)); 31 | res.index = this.nodes.length; 32 | res.blockIndex = this.blocks.length; 33 | this.nodes.push(res); 34 | this.blocks.push(res); 35 | return res; 36 | }; 37 | 38 | CFGBuilder.prototype.block = function block(opcode) { 39 | var res = this.createBlock(opcode); 40 | this.setCurrentBlock(res); 41 | return res; 42 | }; 43 | 44 | CFGBuilder.prototype.jumpFrom = function jumpFrom(other) { 45 | var res = this.block(); 46 | other.jump(res); 47 | return res; 48 | }; 49 | 50 | CFGBuilder.prototype.merge = function merge(left, right) { 51 | var res = this.block(); 52 | left.jump(res); 53 | right.jump(res); 54 | return res; 55 | }; 56 | 57 | CFGBuilder.prototype.setCurrentBlock = function setCurrentBlock(block) { 58 | this.currentBlock = block; 59 | }; 60 | 61 | CFGBuilder.prototype.add = function add() { 62 | var node = this.create.apply(this, arguments); 63 | this.currentBlock.add(node); 64 | return node; 65 | }; 66 | 67 | CFGBuilder.prototype.addControl = function addControl() { 68 | var node = this.create.apply(this, arguments); 69 | this.currentBlock.addControl(node); 70 | return node; 71 | }; 72 | 73 | CFGBuilder.prototype.reindex = function reindex() { 74 | var blocks = this._reindexBlocks(); 75 | 76 | var nodes = []; 77 | for (var i = 0; i < blocks.length; i++) { 78 | var block = blocks[i]; 79 | block.index = nodes.length; 80 | block.blockIndex = i; 81 | nodes.push(block); 82 | } 83 | 84 | for (var i = 0; i < blocks.length; i++) { 85 | var block = blocks[i]; 86 | for (var j = 0; j < block.nodes.length; j++) { 87 | var node = block.nodes[j]; 88 | 89 | node.index = nodes.length; 90 | nodes.push(node); 91 | } 92 | } 93 | 94 | this.blocks = blocks; 95 | this.nodes = nodes; 96 | }; 97 | 98 | CFGBuilder.prototype._reindexBlocks = function _reindexBlocks() { 99 | var out = []; 100 | 101 | var visited = new BitField(this.blocks.length); 102 | var queue = [ this.blocks[0] ]; 103 | while (queue.length !== 0) { 104 | var block = queue.pop(); 105 | 106 | var ready = true; 107 | for (var i = 0; i < block.predecessors.length; i++) 108 | if (!visited.check(block.predecessors[i].blockIndex)) 109 | ready = false; 110 | 111 | // Back edge 112 | if (queue.length === 0) 113 | ready = true; 114 | if (!ready) 115 | continue; 116 | 117 | if (visited.set(block.blockIndex)) 118 | out.push(block); 119 | 120 | for (var i = block.successors.length - 1; i >= 0; i--) 121 | if (!visited.check(block.successors[i].blockIndex)) 122 | queue.push(block.successors[i]); 123 | } 124 | 125 | return out; 126 | }; 127 | 128 | CFGBuilder.prototype.link = function link() { 129 | for (var i = 0; i < this.blocks.length; i++) 130 | this.blocks[i].link(); 131 | }; 132 | 133 | CFGBuilder.prototype.computeLastControl = function computeLastControl() { 134 | for (var i = 0; i < this.blocks.length; i++) 135 | this.blocks[i].computeLastControl(); 136 | }; 137 | 138 | CFGBuilder.prototype.remove = function remove(node) { 139 | var res = Pipeline.prototype.remove.apply(this, arguments); 140 | if (node.block === null) 141 | return; 142 | 143 | var index = node.block.nodes.indexOf(node); 144 | if (index < 0) 145 | return; 146 | 147 | node.block.remove(index); 148 | }; 149 | -------------------------------------------------------------------------------- /lib/pipeline/cfg/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var pipeline = require('../../pipeline'); 7 | 8 | function Node(opcode) { 9 | pipeline.Pipeline.Node.call(this, opcode); 10 | this.block = null; 11 | } 12 | util.inherits(Node, pipeline.Pipeline.Node); 13 | module.exports = Node; 14 | 15 | Node.create = function create(opcode) { 16 | return new Node(opcode); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/pipeline/dominance/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var pipeline = require('../../pipeline'); 7 | var CFGBuilder = pipeline.CFGBuilder; 8 | 9 | function Block(opcode) { 10 | CFGBuilder.Block.call(this, opcode); 11 | 12 | // Fast checks for dominance 13 | this.dominanceStart = null; 14 | this.dominanceEnd = null; 15 | this.dominanceDepth = null; 16 | this.loopDepth = null; 17 | 18 | this.parent = null; 19 | this.children = []; 20 | this.frontier = []; 21 | } 22 | module.exports = Block; 23 | util.inherits(Block, CFGBuilder.Block); 24 | 25 | Block.create = function create(opcode) { 26 | return new Block(opcode); 27 | }; 28 | 29 | Block.prototype.addChild = function addChild(other) { 30 | assert(other.parent === null, 'Block is already in Dominator tree'); 31 | this.children.push(other); 32 | other.parent = this; 33 | }; 34 | 35 | Block.prototype.addFrontier = function addFrontier(other) { 36 | this.frontier.push(other); 37 | }; 38 | 39 | Block.prototype.dominates = function dominates(other) { 40 | var index = other.dominanceStart; 41 | var start = this.dominanceStart; 42 | var end = this.dominanceEnd; 43 | 44 | return start <= index && index <= end; 45 | }; 46 | -------------------------------------------------------------------------------- /lib/pipeline/dominance/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | var BitField = require('bitfield.js'); 6 | 7 | var pipeline = require('../../pipeline'); 8 | var CFGBuilder = pipeline.CFGBuilder; 9 | 10 | function Dominance() { 11 | CFGBuilder.call(this); 12 | } 13 | module.exports = Dominance; 14 | util.inherits(Dominance, CFGBuilder); 15 | 16 | Dominance.Block = require('./block'); 17 | Dominance.prototype.Block = Dominance.Block; 18 | 19 | Dominance.create = function create() { 20 | return new Dominance(); 21 | }; 22 | 23 | Dominance.prototype.enumerate = function enumerate() { 24 | // Simple DFS with range tracking 25 | var queue = [ this.blocks[0] ]; 26 | var visited = new BitField(this.blocks.length); 27 | 28 | this.blocks[0].dominanceDepth = 0; 29 | 30 | var index = 0; 31 | while (queue.length !== 0) { 32 | var block = queue[queue.length - 1]; 33 | if (!visited.set(block.blockIndex)) 34 | continue; 35 | 36 | for (var i = block.children.length - 1; i >= 0; i--) 37 | queue.push(block.children[i]); 38 | 39 | block.dominanceStart = index++; 40 | if (block.parent !== null) 41 | block.dominanceDepth = block.parent.dominanceDepth + 1; 42 | 43 | if (block.children.length !== 0) 44 | continue; 45 | 46 | block.dominanceEnd = block.dominanceStart; 47 | 48 | // Pop to the top 49 | var last = queue.pop(); 50 | while (queue.length !== 0) { 51 | // Process only right-most children 52 | if (last.parent.children[last.parent.children.length - 1] !== last) 53 | break; 54 | 55 | // Update parent's range 56 | last = queue.pop(); 57 | last.dominanceEnd = block.dominanceStart; 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /lib/pipeline/pipeline/format/graphviz.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var pipeline = require('../../../pipeline'); 7 | 8 | function Graphviz(sections, pipeline) { 9 | this.sections = sections; 10 | this.pipeline = pipeline; 11 | } 12 | module.exports = Graphviz; 13 | 14 | Graphviz.prototype.parse = function parse(data) { 15 | throw new Error('Not implemented'); 16 | }; 17 | 18 | Graphviz.prototype.escape = function escape(value) { 19 | return value.replace(/([^a-z0-9])/ig, '\\$1'); 20 | }; 21 | 22 | Graphviz.prototype.render = function render() { 23 | var pipeline = this.pipeline; 24 | 25 | var out = 'digraph {\n'; 26 | 27 | out += 'node [fontsize=8,height=0.25]\n'; 28 | out += 'rankdir="TB"\n'; 29 | out += 'ranksep="1.2 equally"\n'; 30 | out += 'overlap="false"\n'; 31 | out += 'splines="true"\n'; 32 | out += 'concentrate="true"\n'; 33 | 34 | if (this.sections.cfg) { 35 | out += this._renderCFG(); 36 | } else { 37 | out += this._renderPlain(); 38 | } 39 | 40 | out += '}'; 41 | return out; 42 | }; 43 | 44 | Graphviz.prototype._renderCFG = function _renderCFG() { 45 | var out = ''; 46 | for (var i = 0; i < this.pipeline.blocks.length; i++) { 47 | var block = this.pipeline.blocks[i]; 48 | 49 | out += 'subgraph cluster' + i + ' {\n'; 50 | out += ' label = "b' + block.blockIndex + '"\n'; 51 | if (block.predecessors.length === 0) 52 | out += ' color = "red"\n'; 53 | else if (block.successors.length === 0) 54 | out += ' color = "blue"\n'; 55 | out += this._renderNode(' ', block); 56 | for (var j = 0; j < block.nodes.length; j++) { 57 | var node = block.nodes[j]; 58 | out += this._renderNode(' ', node); 59 | } 60 | out += '}\n'; 61 | } 62 | for (var i = 0; i < this.pipeline.nodes.length; i++) { 63 | var node = this.pipeline.nodes[i]; 64 | out += this._renderEdges(node); 65 | } 66 | return out; 67 | }; 68 | 69 | Graphviz.prototype._renderPlain = function _renderPlain() { 70 | var out = ''; 71 | for (var i = 0; i < this.pipeline.nodes.length; i++) { 72 | var node = this.pipeline.nodes[i]; 73 | out += this._renderNode('', node); 74 | } 75 | for (var i = 0; i < this.pipeline.nodes.length; i++) { 76 | var node = this.pipeline.nodes[i]; 77 | out += this._renderEdges(node); 78 | } 79 | return out; 80 | }; 81 | 82 | Graphviz.prototype._renderNode = function _renderNode(prefix, node) { 83 | var out = ''; 84 | 85 | var label = ' i' + node.index + ' = ' + this.escape(node.opcode); 86 | if (node.literals.length > 0) 87 | label += this.escape('(' + node.literals.join(', ') + ')'); 88 | 89 | var records = [ '' ]; 90 | for (var i = 0; i < node.control.length; i++) 91 | records.push(' ^i' + node.control[i].index); 92 | records.push(label); 93 | for (var i = 0; i < node.inputs.length; i++) 94 | records.push(' i' + node.inputs[i].index); 95 | 96 | out += prefix + 'N' + node.index + ' [\n'; 97 | out += prefix + ' shape="record"\n'; 98 | out += prefix + ' label="{{' + records.join('|') + '}}"\n'; 99 | out += prefix + ']\n'; 100 | 101 | return out; 102 | }; 103 | 104 | Graphviz.prototype._renderEdges = function _renderEdges(node) { 105 | var out = ''; 106 | 107 | var record = 'N' + node.index; 108 | for (var i = 0; i < node.control.length; i++) { 109 | out += 'N' + node.control[i].index + ':CS -> ' + record + ':C' + i; 110 | out += ' [dir=back, style=dashed]\n'; 111 | } 112 | 113 | for (var i = 0; i < node.inputs.length; i++) { 114 | out += 'N' + node.inputs[i].index + ':S -> ' + record + ':I' + i; 115 | out += ' []\n'; 116 | } 117 | 118 | return out; 119 | }; 120 | -------------------------------------------------------------------------------- /lib/pipeline/pipeline/format/json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function JSONFormat(sections, pipeline) { 4 | this.sections = sections; 5 | this.pipeline = pipeline; 6 | } 7 | module.exports = JSONFormat; 8 | 9 | JSONFormat.prototype.parse = function parse(json) { 10 | var pipeline = this.pipeline; 11 | var nodes = new Array(json.nodes.length); 12 | 13 | var blocks; 14 | if (this.sections.cfg) { 15 | // Create all blocks 16 | blocks = json.cfg.blocks; 17 | for (var i = 0; i < blocks.length; i++) { 18 | var block = blocks[i]; 19 | nodes[block.node] = pipeline.block(); 20 | 21 | // Debug information 22 | if (json.nodes[block.node].loc) 23 | nodes[block.node].loc = json.nodes[block.node].loc; 24 | 25 | // Habitate them with nodes 26 | for (var j = 0; j < block.nodes.length; j++) { 27 | var index = block.nodes[j]; 28 | var node = json.nodes[index]; 29 | nodes[index] = pipeline.add(node.opcode); 30 | 31 | // Debug information 32 | if (node.loc) 33 | nodes[index].loc = node.loc; 34 | } 35 | } 36 | 37 | if (this.sections.dominance) 38 | this._parseDominance(json, blocks); 39 | } else { 40 | // Create all nodes 41 | for (var i = 0; i < json.nodes.length; i++) { 42 | var node = json.nodes[i]; 43 | nodes[i] = pipeline.add(node.opcode); 44 | 45 | // Debug information 46 | if (node.loc) 47 | nodes[i].loc = node.loc; 48 | } 49 | } 50 | 51 | // Link nodes together 52 | for (var i = 0; i < nodes.length; i++) 53 | this._parseNode(json, i, nodes); 54 | 55 | if (this.sections.cfg) { 56 | // Link blocks together 57 | for (var i = 0; i < blocks.length; i++) { 58 | var block = blocks[i]; 59 | 60 | var pred = nodes[block.node]; 61 | pred.computeLastControl(); 62 | for (var j = 0; j < block.successors.length; j++) { 63 | var succ = nodes[block.successors[j]]; 64 | pred.jump(succ); 65 | } 66 | } 67 | 68 | this.pipeline.link(); 69 | } 70 | }; 71 | 72 | JSONFormat.prototype._parseNode = function _parseNode(json, index, nodes) { 73 | var node = json.nodes[index]; 74 | var current = nodes[index]; 75 | 76 | for (var i = 0; i < node.literals.length; i++) 77 | current.addLiteral(node.literals[i]); 78 | 79 | for (var i = 0; i < node.inputs.length; i++) 80 | current.addInput(nodes[node.inputs[i]]); 81 | 82 | if (node.control.length === 2) 83 | current.setControl(nodes[node.control[0]], nodes[node.control[1]]); 84 | else if (node.control.length === 1) 85 | current.setControl(nodes[node.control[0]]); 86 | }; 87 | 88 | JSONFormat.prototype._parseDominance = function _parseDominance(json, blocks) { 89 | var pipeline = this.pipeline; 90 | var blocks = json.dominance.blocks; 91 | for (var i = 0; i < blocks.length; i++) { 92 | var block = blocks[i]; 93 | var current = pipeline.blocks[i]; 94 | 95 | // Blocks are created in the same order 96 | if (block.parent !== null) 97 | pipeline.nodes[block.parent].addChild(current); 98 | 99 | for (var j = 0; j < block.frontier.length; j++) 100 | current.addFrontier(pipeline.nodes[block.frontier[j]]); 101 | } 102 | }; 103 | 104 | JSONFormat.prototype.render = function render() { 105 | var pipeline = this.pipeline; 106 | 107 | var output = { 108 | nodes: new Array(pipeline.nodes.length) 109 | }; 110 | 111 | // Create index 112 | for (var i = 0; i < pipeline.nodes.length; i++) { 113 | var node = pipeline.nodes[i]; 114 | 115 | output.nodes[i] = { 116 | opcode: node.opcode, 117 | control: new Array(node.control.length), 118 | literals: new Array(node.literals.length), 119 | inputs: new Array(node.inputs.length) 120 | }; 121 | } 122 | 123 | // Fill it 124 | for (var i = 0; i < pipeline.nodes.length; i++) { 125 | var node = pipeline.nodes[i]; 126 | var current = output.nodes[i]; 127 | 128 | for (var j = 0; j < node.control.length; j++) 129 | current.control[j] = node.control[j].index; 130 | 131 | for (var j = 0; j < node.literals.length; j++) 132 | current.literals[j] = node.literals[j]; 133 | 134 | for (var j = 0; j < node.inputs.length; j++) 135 | current.inputs[j] = node.inputs[j].index; 136 | } 137 | 138 | if (this.sections.cfg) { 139 | output.cfg = this._renderCFG(); 140 | if (this.sections.dominance) 141 | output.dominance = this._renderDominance(); 142 | } 143 | 144 | return output; 145 | }; 146 | 147 | JSONFormat.prototype._renderCFG = function _renderCFG() { 148 | var pipeline = this.pipeline; 149 | var cfg = { 150 | blocks: new Array(pipeline.blocks.length) 151 | }; 152 | 153 | for (var i = 0; i < pipeline.blocks.length; i++) { 154 | var block = pipeline.blocks[i]; 155 | var current = { 156 | node: block.index, 157 | successors: new Array(block.successors.length), 158 | nodes: new Array(block.nodes.length) 159 | }; 160 | 161 | for (var j = 0; j < block.successors.length; j++) 162 | current.successors[j] = block.successors[j].index; 163 | 164 | for (var j = 0; j < block.nodes.length; j++) 165 | current.nodes[j] = block.nodes[j].index; 166 | 167 | cfg.blocks[i] = current; 168 | } 169 | 170 | return cfg; 171 | }; 172 | 173 | JSONFormat.prototype._renderDominance = function _renderDominance() { 174 | var pipeline = this.pipeline; 175 | var dom = { 176 | blocks: new Array(pipeline.blocks.length) 177 | }; 178 | 179 | for (var i = 0; i < pipeline.blocks.length; i++) { 180 | var block = pipeline.blocks[i]; 181 | var current = { 182 | node: block.index, 183 | parent: block.parent === null ? null : block.parent.index, 184 | frontier: new Array(block.frontier.length) 185 | }; 186 | dom.blocks[i] = current; 187 | 188 | for (var j = 0; j < block.frontier.length; j++) 189 | current.frontier[j] = block.frontier[j].index; 190 | } 191 | 192 | return dom; 193 | }; 194 | -------------------------------------------------------------------------------- /lib/pipeline/pipeline/format/printable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | function Printable(sections, pipeline) { 7 | this.sections = sections; 8 | this.pipeline = pipeline; 9 | 10 | this.ast = null; 11 | this.stack = []; 12 | this.current = null; 13 | this.currentLine = null; 14 | 15 | this.map = null; 16 | } 17 | module.exports = Printable; 18 | 19 | Printable.prototype.parse = function parse(data) { 20 | var lines = (data + '').split(/\r\n|\r|\n/g); 21 | 22 | for (var i = 0; i < lines.length; i++) 23 | if (this._parseLine(lines[i].trim(), i) === false) 24 | break; 25 | 26 | assert(this.ast !== null, '`pipeline {}` section not found'); 27 | 28 | this._parseAST(this.ast); 29 | this.pipeline.computeLastControl(); 30 | this.pipeline.link(); 31 | this.ast = null; 32 | }; 33 | 34 | Printable.prototype._push = function _push(node) { 35 | this.stack.push(this.current); 36 | this.current = node; 37 | }; 38 | 39 | Printable.prototype._pop = function _pop() { 40 | if (this.current.loc) 41 | this.current.loc.end = this.currentLine; 42 | this.current = this.stack.pop(); 43 | return this.current; 44 | }; 45 | 46 | var re = new RegExp('^\s*(?:' + [ 47 | // `pipeline {` 48 | '(pipeline)\\s*{', 49 | 50 | // `b1 {` or `b1 => b2, b3, b4` 51 | '(b\\d+)\\s*({|([\\-=~]>)\\s*((?:b\\d+[\\s,]+)*b\\d+))', 52 | 53 | // `i0 = opcode 1, 2, i1, i2` 54 | '(i\\d+)\\s*=\\s*([^\\s]+)(?:\\s+((?:[^\\s]+[\\s,]+)*[^\\s]+))?', 55 | 56 | // `}` 57 | '(})' 58 | ].join('|') + ')\\s*(?:#\\s*(.*)\\s*)?$'); 59 | Printable.re = re; 60 | 61 | Printable.prototype._parseLine = function _parseLine(line, index) { 62 | if (!line) 63 | return; 64 | 65 | var match = line.match(re); 66 | this.currentLine = index; 67 | 68 | if (!match) 69 | throw new Error(util.format('Failed to parse: %j at line %d', line, index)); 70 | 71 | // `pipeline {` 72 | if (match[1]) { 73 | assert(this.ast === null, 'Nested `pipeline {`'); 74 | this.ast = { 75 | type: 'Pipeline', 76 | body: [] 77 | }; 78 | this._push(this.ast); 79 | return; 80 | } 81 | 82 | // `}` 83 | if (match[9]) { 84 | this._pop(); 85 | 86 | // End of `pipeline {}` 87 | if (this.stack.length === 0) 88 | return false; 89 | return; 90 | } 91 | 92 | assert(this.ast !== null, 'Missing `pipeline {}` section'); 93 | var pipeline = this.pipeline; 94 | 95 | // `b1 {` 96 | if (match[2] && !match[4]) 97 | return this._parseBlock(match[2]); 98 | 99 | // `b1 -> ...` 100 | if (match[2]) { 101 | this._parseLink(match[2], match[4], match[5]); 102 | return; 103 | } 104 | 105 | // node 106 | assert(match[6]); 107 | this._parseNode(match[6], match[7], match[8]); 108 | }; 109 | 110 | Printable.prototype._parseBlock = function _parseBlock(id) { 111 | var block = { 112 | type: 'Block', 113 | id: id, 114 | body: [], 115 | loc: { 116 | line: this.currentLine, 117 | end: null 118 | } 119 | }; 120 | this.current.body.push(block); 121 | this._push(block); 122 | }; 123 | 124 | Printable.prototype._parseLink = function _parseLink(id, arrow, list) { 125 | var pipeline = this.pipeline; 126 | 127 | assert.equal(this.current.type, 128 | 'Pipeline', 129 | 'Arrow should be outside of the block'); 130 | 131 | var items = list.split(/[\s,]+/g); 132 | for (var i = 0; i < items.length; i++) { 133 | this.current.body.push({ 134 | type: 'Link', 135 | arrow: arrow, 136 | from: id, 137 | to: items[i], 138 | loc: { 139 | line: this.currentLine 140 | } 141 | }); 142 | } 143 | }; 144 | 145 | Printable.prototype._parseNode = function _parseNode(id, opcode, list) { 146 | var node = { 147 | type: 'Node', 148 | id: id, 149 | opcode: opcode, 150 | control: [], 151 | literals: [], 152 | inputs: [], 153 | loc: { 154 | line: this.currentLine 155 | } 156 | }; 157 | this.current.body.push(node); 158 | 159 | // No arguments 160 | if (!list) 161 | return; 162 | 163 | var items = list.split(/[\s,]+/g); 164 | 165 | // Parse control 166 | for (var i = 0; i < items.length; i++) { 167 | if (!/^\^/.test(items[i])) 168 | break; 169 | 170 | var prefix = items[i][1]; 171 | assert(prefix === 'i' || prefix === 'b', 172 | 'Unexpected control prefix: ' + prefix); 173 | 174 | node.control.push(items[i].slice(1)); 175 | } 176 | 177 | // Parse literals 178 | for (; i < items.length; i++) { 179 | if (/^i/.test(items[i])) 180 | break; 181 | 182 | node.literals.push(JSON.parse(items[i])); 183 | } 184 | 185 | // Parse inputs 186 | for (; i < items.length; i++) 187 | node.inputs.push(items[i]); 188 | }; 189 | 190 | Printable.prototype._parseAST = function _parseAST(ast) { 191 | this.map = {}; 192 | 193 | // Reserve blocks 194 | for (var i = 0; i < ast.body.length; i++) { 195 | var node = ast.body[i]; 196 | 197 | if (node.type === 'Block') 198 | this._reserveBlock(i === 0, node); 199 | else if (node.type === 'Node') 200 | this._reserveNode(node); 201 | } 202 | 203 | // Fill blocks and link them 204 | for (var i = 0; i < ast.body.length; i++) { 205 | var node = ast.body[i]; 206 | 207 | if (node.type === 'Block') { 208 | for (var j = 0; j < node.body.length; j++) 209 | this._fillNode(node.body[j]); 210 | } else if (node.type === 'Node') { 211 | this._fillNode(node); 212 | } else if (node.type === 'Link') { 213 | this._fillLink(node); 214 | } 215 | } 216 | 217 | this.map = null; 218 | }; 219 | 220 | Printable.prototype._lookup = function _lookup(id) { 221 | var out = this.map[id]; 222 | assert(out, 'Id: ' + id + ' not found'); 223 | return out; 224 | }; 225 | 226 | Printable.prototype._reserveBlock = function _reserveBlock(isFirst, block) { 227 | var out = this.pipeline.block(isFirst ? 'start' : 'region'); 228 | out.loc = block.loc; 229 | this.map[block.id] = out; 230 | 231 | for (var i = 0; i < block.body.length; i++) { 232 | var node = block.body[i]; 233 | this._reserveNode(node); 234 | } 235 | }; 236 | 237 | Printable.prototype._reserveNode = function _reserveNode(node) { 238 | var out = this.pipeline.create(node.opcode); 239 | if (this.pipeline.currentBlock !== null) 240 | this.pipeline.currentBlock.add(out); 241 | out.loc = node.loc; 242 | this.map[node.id] = out; 243 | }; 244 | 245 | Printable.prototype._fillNode = function _fillNode(node) { 246 | var current = this._lookup(node.id); 247 | 248 | if (node.control.length === 1) 249 | current.setControl(this._lookup(node.control[0])); 250 | else if (node.control.length === 2) 251 | current.setControl(this._lookup(node.control[0]), 252 | this._lookup(node.control[1])); 253 | 254 | for (var i = 0; i < node.inputs.length; i++) 255 | current.addInput(this._lookup(node.inputs[i])); 256 | 257 | for (var i = 0; i < node.literals.length; i++) 258 | current.addLiteral(node.literals[i]); 259 | }; 260 | 261 | Printable.prototype._fillLink = function _fillLink(link) { 262 | var from = this._lookup(link.from); 263 | var to = this._lookup(link.to); 264 | 265 | if (link.arrow == '->') 266 | from.jump(to); 267 | else if (link.arrow === '=>') 268 | from.addChild(to); 269 | else if (link.arrow === '~>') 270 | from.addFrontier(to); 271 | else 272 | throw new Error('Unexpected arrow type: ' + link.arrow); 273 | }; 274 | 275 | Printable.prototype.render = function render() { 276 | var out = 'pipeline {\n'; 277 | 278 | if (this.sections.cfg) 279 | out += this._renderCFG(); 280 | else 281 | out += this._renderPlain(); 282 | 283 | out += '}'; 284 | 285 | return out; 286 | }; 287 | 288 | Printable.prototype._renderPlain = function _renderPlain() { 289 | var out = ''; 290 | var pipeline = this.pipeline; 291 | 292 | for (var i = 0; i < pipeline.nodes.length; i++) 293 | out += this._renderNode(pipeline.nodes[i], null, ' '); 294 | 295 | return out; 296 | }; 297 | 298 | function lookupId(ids, node) { 299 | return ids === null ? 'i' + node.index : ids[node.index]; 300 | } 301 | 302 | Printable.prototype._renderNode = function _renderNode(node, ids, pad) { 303 | var out = lookupId(ids, node) + ' = ' + node.opcode; 304 | 305 | var args = []; 306 | for (var i = 0; i < node.control.length; i++) 307 | args.push('^' + lookupId(ids, node.control[i])); 308 | for (var i = 0; i < node.literals.length; i++) 309 | args.push(JSON.stringify(node.literals[i])); 310 | for (var i = 0; i < node.inputs.length; i++) 311 | args.push(lookupId(ids, node.inputs[i])); 312 | 313 | if (args.length !== 0) 314 | out += ' ' + args.join(', '); 315 | 316 | return pad + out + '\n'; 317 | }; 318 | 319 | Printable.prototype._renderCFG = function _renderCFG() { 320 | var pipeline = this.pipeline; 321 | var out = ''; 322 | 323 | // We need node ids skipping the blocks 324 | var ids = new Array(pipeline.nodes.length); 325 | for (var i = 0, j = 0, k = 0; i < ids.length; i++) { 326 | // Block node 327 | if (j < pipeline.blocks.length && pipeline.blocks[j].index === i) { 328 | ids[i] = 'b' + j; 329 | j++; 330 | continue; 331 | } 332 | 333 | // Other node 334 | ids[i] = 'i' + k; 335 | k++; 336 | } 337 | 338 | for (var i = 0; i < pipeline.blocks.length; i++) 339 | out += this._renderBlock(pipeline.blocks[i], ids); 340 | 341 | return out; 342 | }; 343 | 344 | function renderArrow(id, arrow, list) { 345 | var ids = new Array(list.length); 346 | for (var i = 0; i < ids.length; i++) 347 | ids[i] = 'b' + list[i].blockIndex; 348 | return id + ' ' + arrow + ' ' + ids.join(', ') + '\n'; 349 | } 350 | 351 | Printable.prototype._renderBlock = function _renderBlock(block, ids) { 352 | var id = 'b' + block.blockIndex; 353 | var out = ' ' + id + ' {\n'; 354 | 355 | for (var i = 0; i < block.nodes.length; i++) 356 | out += this._renderNode(block.nodes[i], ids, ' '); 357 | 358 | out += ' }\n'; 359 | 360 | if (block.successors.length !== 0) 361 | out += ' ' + renderArrow(id, '->', block.successors); 362 | 363 | if (this.sections.dominance) { 364 | if (block.children.length !== 0) 365 | out += ' ' + renderArrow(id, '=>', block.children); 366 | 367 | if (block.frontier.length !== 0) 368 | out += ' ' + renderArrow(id, '~>', block.frontier); 369 | } 370 | 371 | return out; 372 | }; 373 | -------------------------------------------------------------------------------- /lib/pipeline/pipeline/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var pipeline = require('../../pipeline'); 7 | var Base = pipeline.Base; 8 | 9 | function Pipeline() { 10 | Base.call(this); 11 | 12 | this.nodes = []; 13 | } 14 | util.inherits(Pipeline, Base); 15 | module.exports = Pipeline; 16 | 17 | Pipeline.Node = require('./node'); 18 | Pipeline.prototype.Node = Pipeline.Node; 19 | 20 | Pipeline.create = function create() { 21 | return new Pipeline(); 22 | }; 23 | 24 | Pipeline.formats = {}; 25 | Pipeline.prototype.formats = Pipeline.formats; 26 | 27 | Pipeline.prototype.create = function create(opcode, inputs) { 28 | var node = this.Node.create(opcode); 29 | if (inputs) { 30 | // Single input 31 | if (!Array.isArray(inputs)) { 32 | node.addInput(inputs); 33 | 34 | // Multiple inputs 35 | } else { 36 | for (var i = 0; i < inputs.length; i++) 37 | node.addInput(inputs[i]); 38 | } 39 | } 40 | 41 | node.index = this.nodes.length; 42 | this.nodes.push(node); 43 | 44 | return node; 45 | }; 46 | 47 | // Mostly compatibility 48 | Pipeline.prototype.add = function add(opcode, inputs) { 49 | return this.create(opcode, inputs); 50 | }; 51 | 52 | Pipeline.prototype._maybeRemove = function _maybeRemove(node) { 53 | // Node is still used - can't be removed from the graph 54 | if (node.uses.length !== 0 || node.controlUses.length !== 0) 55 | return; 56 | 57 | // Already removed 58 | if (node.index === -1) 59 | return; 60 | 61 | var last = this.nodes.pop(); 62 | 63 | // Lucky one - it was already the last node 64 | if (last === node) 65 | return; 66 | 67 | // Let the last node take the place of `node` 68 | this.nodes[node.index] = last; 69 | 70 | // Do it in observable order 71 | var index = node.index; 72 | node.index = -1; 73 | last.index = index; 74 | }; 75 | 76 | Pipeline.prototype.remove = function remove(node) { 77 | node.remove(); 78 | 79 | this._maybeRemove(node); 80 | }; 81 | 82 | Pipeline.prototype.cut = function cut(node) { 83 | node.cut(); 84 | 85 | this._maybeRemove(node); 86 | }; 87 | 88 | Pipeline.prototype.verify = function verify() { 89 | for (var i = 0; i < this.nodes.length; i++) 90 | this.nodes[i].verify(); 91 | }; 92 | -------------------------------------------------------------------------------- /lib/pipeline/pipeline/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | function Node(opcode) { 6 | this.index = null; 7 | this.control = []; 8 | this.opcode = opcode; 9 | this.inputs = []; 10 | this.literals = []; 11 | this.uses = []; 12 | this.controlUses = []; 13 | 14 | // Only for tooling, filled by `printable` format 15 | this.loc = null; 16 | 17 | // Only for storing user-data 18 | this.data = null; 19 | } 20 | module.exports = Node; 21 | 22 | Node.create = function create(opcode) { 23 | return new Node(opcode); 24 | }; 25 | 26 | Node.prototype._use = function _use(other, index, control) { 27 | if (control) 28 | this.controlUses.push(other, index); 29 | else 30 | this.uses.push(other, index); 31 | }; 32 | 33 | Node.prototype._unuse = function _unuse(node, index, control) { 34 | var uses = control ? this.controlUses : this.uses; 35 | 36 | for (var i = uses.length - 2; i >= 0; i -= 2) 37 | if (uses[i] === node && uses[i + 1] === index) 38 | break; 39 | 40 | // No such use found 41 | if (i < 0) 42 | return; 43 | 44 | var lastIndex = uses.pop(); 45 | var lastNode = uses.pop(); 46 | 47 | // Removed last use 48 | if (i === uses.length) 49 | return; 50 | 51 | // Replace with the last one 52 | uses[i] = lastNode; 53 | uses[i + 1] = lastIndex; 54 | }; 55 | 56 | Node.prototype.clearInputs = function clearInputs() { 57 | for (var i = 0; i < this.inputs.length; i++) { 58 | var input = this.inputs[i]; 59 | input._unuse(this, i, false); 60 | } 61 | this.inputs.length = 0; 62 | }; 63 | 64 | Node.prototype.removeControl = function removeControl() { 65 | if (this.controlUses.length === 0) { 66 | // No children - just cut link to the parent 67 | this.cutControl(); 68 | return; 69 | } 70 | 71 | assert(this.controlUses.length === 2, 'Can\'t remove branch node'); 72 | 73 | // Replace control uses in parent with the control child 74 | var child = this.controlUses[0]; 75 | var index = this.controlUses[1]; 76 | for (var i = 0; i < this.control.length; i++) { 77 | var parent = this.control[i]; 78 | 79 | child.control[index] = parent; 80 | for (var j = 0; j < parent.controlUses.length; j += 2) { 81 | var parentUse = parent.controlUses[j]; 82 | if (parentUse !== this) 83 | continue; 84 | 85 | parent.controlUses[j] = child; 86 | parent.controlUses[j + 1] = index; 87 | } 88 | } 89 | this.control.length = 0; 90 | this.controlUses.length = 0; 91 | }; 92 | 93 | Node.prototype.cutControl = function cutControl() { 94 | for (var i = 0; i < this.control.length; i++) { 95 | var parent = this.control[i]; 96 | parent._unuse(this, i, true); 97 | } 98 | this.control.length = 0; 99 | }; 100 | 101 | Node.prototype.remove = function remove() { 102 | this.clearInputs(); 103 | this.removeControl(); 104 | }; 105 | 106 | Node.prototype.cut = function cut() { 107 | this.clearInputs(); 108 | this.cutControl(); 109 | }; 110 | 111 | Node.prototype.addInput = function addInput(other) { 112 | other._use(this, this.inputs.length, false); 113 | this.inputs.push(other); 114 | return this; 115 | }; 116 | 117 | Node.prototype.replaceInput = function replaceInput(index, other) { 118 | var old = this.inputs[index]; 119 | old._unuse(this, index); 120 | this.inputs[index] = other; 121 | other._use(this, index, false); 122 | }; 123 | 124 | Node.prototype.removeInput = function removeInput(index) { 125 | var old = this.inputs[index]; 126 | old._unuse(this, index); 127 | 128 | // Shift inputs to the left, and move their uses 129 | for (var i = index + 1; i < this.inputs.length; i++) { 130 | var input = this.inputs[i]; 131 | input._unuse(this, i); 132 | input._use(this, i - 1); 133 | this.inputs[i - 1] = input; 134 | } 135 | this.inputs.length--; 136 | }; 137 | 138 | Node.prototype.addLiteral = function addLiteral(literal) { 139 | this.literals.push(literal); 140 | return this; 141 | }; 142 | 143 | Node.prototype.setControl = function setControl(left, right) { 144 | if (right) { 145 | this.control.length = 0; 146 | this.control.push(left, right); 147 | left._use(this, 0, true); 148 | right._use(this, 1, true); 149 | } else { 150 | this.control.length = 0; 151 | this.control.push(left); 152 | left._use(this, 0, true); 153 | } 154 | return this; 155 | }; 156 | 157 | Node.prototype.splitControl = function splitControl(parent) { 158 | for (var i = 0; i < parent.controlUses.length; i += 2) { 159 | var use = parent.controlUses[i]; 160 | var index = parent.controlUses[i + 1]; 161 | 162 | use.control[index] = this; 163 | this._use(use, index, true); 164 | } 165 | parent.controlUses.length = 0; 166 | 167 | this.control.length = 0; 168 | this.control.push(parent); 169 | parent._use(this, 0, true); 170 | return this; 171 | }; 172 | 173 | Node.prototype.isControl = function isControl() { 174 | return this.opcode === 'start' || this.control.length !== 0; 175 | }; 176 | 177 | Node.prototype.replace = function replace(other) { 178 | // Propagate location information through replacement 179 | if (other.loc === null) 180 | other.loc = this.loc; 181 | 182 | for (var i = 0; i < this.uses.length; i += 2) { 183 | var node = this.uses[i]; 184 | var index = this.uses[i + 1]; 185 | 186 | node.inputs[index] = other; 187 | other._use(node, index, false); 188 | } 189 | this.uses.length = 0; 190 | 191 | // Re-route control chain 192 | for (var i = 0; i < this.control.length; i++) { 193 | var control = this.control[i]; 194 | for (var j = 0; j < control.controlUses.length; j += 2) { 195 | var use = control.controlUses[j]; 196 | if (use !== this) 197 | continue; 198 | 199 | control.controlUses[j] = other; 200 | control.controlUses[j + 1] = other.control.length; 201 | other.control.push(control); 202 | } 203 | } 204 | this.control.length = 0; 205 | 206 | for (var i = 0; i < this.controlUses.length; i += 2) { 207 | var node = this.controlUses[i]; 208 | var index = this.controlUses[i + 1]; 209 | 210 | node.control[index] = other; 211 | other._use(node, index, true); 212 | } 213 | this.controlUses.length = 0; 214 | }; 215 | 216 | Node.prototype._shortId = function _shortId() { 217 | return 'i' + this.index + '[' + this.opcode + ']'; 218 | }; 219 | 220 | Node.prototype.verify = function verify() { 221 | var id = this._shortId(); 222 | 223 | // Verify that every input has use at this node 224 | for (var i = 0; i < this.inputs.length; i++) { 225 | var input = this.inputs[i]; 226 | 227 | var found = false; 228 | for (var j = 0; !found && j < input.uses.length; j += 2) { 229 | var use = input.uses[j]; 230 | var useIndex = input.uses[j + 1]; 231 | if (use === this && useIndex === i) 232 | found = true; 233 | } 234 | 235 | assert(found, 236 | id + '->' + input._shortId() + 237 | ': input without use in other node'); 238 | } 239 | 240 | // Verify that every control has use at this node 241 | assert(this.control.length <= 2, id + ': too much control inputs'); 242 | for (var i = 0; i < this.control.length; i++) { 243 | var control = this.control[i]; 244 | 245 | var found = false; 246 | for (var j = 0; !found && j < control.controlUses.length; j += 2) { 247 | var use = control.controlUses[j]; 248 | var useIndex = control.controlUses[j + 1]; 249 | if (use === this && useIndex === i) 250 | found = true; 251 | } 252 | 253 | assert(found, 254 | id + '<-' + control._shortId() + 255 | ': control without use in other node'); 256 | } 257 | 258 | // Verify that uses have their inputs 259 | for (var i = 0; i < this.uses.length; i += 2) { 260 | var use = this.uses[i]; 261 | var useIndex = this.uses[i + 1]; 262 | 263 | assert.equal(use.inputs[useIndex], 264 | this, 265 | 'Use for ' + id + ' not found at ' + use._shortId()); 266 | } 267 | 268 | for (var i = 0; i < this.controlUses.length; i += 2) { 269 | var use = this.controlUses[i]; 270 | var useIndex = this.controlUses[i + 1]; 271 | 272 | assert.equal(use.control[useIndex], 273 | this, 274 | 'Use for ' + id + ' not found at ' + use._shortId()); 275 | } 276 | }; 277 | -------------------------------------------------------------------------------- /lib/pipeline/register/format/json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function JSONFormat(sections, pipeline) { 4 | this.sections = sections; 5 | this.pipeline = pipeline; 6 | } 7 | module.exports = JSONFormat; 8 | 9 | JSONFormat.prototype.parse = function parse(json) { 10 | var pipeline = this.pipeline; 11 | var instructions = new Array(json.instructions.length); 12 | 13 | for (var i = 0; i < instructions.length; i++) { 14 | var instr = json.instructions[i]; 15 | var current = pipeline.add(instr.opcode); 16 | 17 | instructions[i] = current; 18 | 19 | if (instr.output !== null) 20 | current.setOutput(this.parseOperand(instr.output, json)); 21 | for (var j = 0; j < instr.inputs.length; j++) 22 | current.addInput(this.parseOperand(instr.inputs[j], json)); 23 | for (var j = 0; j < instr.literals.length; j++) 24 | current.addLiteral(instr.literals[j]); 25 | } 26 | 27 | // Process links 28 | for (var i = 0; i < instructions.length; i++) { 29 | var instr = json.instructions[i]; 30 | var current = instructions[i]; 31 | 32 | for (var j = 0; j < instr.links.length; j++) 33 | current.link(instructions[instr.links[j]]); 34 | } 35 | }; 36 | 37 | JSONFormat.prototype.parseOperand = function parseOperand(operand, json) { 38 | if (operand >= 0) 39 | return this.pipeline.reg(json.registers[operand]); 40 | 41 | return this.pipeline.spill(-1 - operand); 42 | }; 43 | 44 | JSONFormat.prototype.render = function render() { 45 | var pipeline = this.pipeline; 46 | 47 | var registerMap = {}; 48 | var registerList = []; 49 | 50 | var output = { 51 | registers: registerList, 52 | spills: pipeline.spills, 53 | instructions: new Array(pipeline.instructions.length) 54 | }; 55 | 56 | for (var i = 0; i < pipeline.instructions.length; i++) { 57 | var instr = pipeline.instructions[i]; 58 | 59 | var current = { 60 | opcode: instr.opcode, 61 | literals: instr.literals.slice(), 62 | output: null, 63 | inputs: new Array(instr.inputs.length), 64 | links: new Array(instr.links.length) 65 | }; 66 | output.instructions[i] = current; 67 | 68 | if (instr.output !== null) { 69 | current.output = this.renderOperand(registerMap, 70 | registerList, 71 | instr.output); 72 | } 73 | 74 | for (var j = 0; j < instr.inputs.length; j++) { 75 | var input = instr.inputs[j]; 76 | 77 | current.inputs[j] = this.renderOperand(registerMap, registerList, input); 78 | } 79 | 80 | for (var j = 0; j < instr.links.length; j++) 81 | current.links[j] = instr.links[j].index; 82 | } 83 | 84 | return output; 85 | }; 86 | 87 | JSONFormat.prototype.renderOperand = function renderOperand(map, 88 | list, 89 | operand) { 90 | if (operand.isSpill()) 91 | return -1 - operand.value; 92 | 93 | if (map[operand.value] !== undefined) 94 | return map[operand.value]; 95 | 96 | var res = list.length; 97 | map[operand.value] = res; 98 | list.push(operand.value); 99 | 100 | return res; 101 | }; 102 | -------------------------------------------------------------------------------- /lib/pipeline/register/format/printable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | 6 | var pipeline = require('../../../pipeline'); 7 | var JSONFormat = pipeline.Register.formats.json; 8 | 9 | function Printable(sections, pipeline) { 10 | this.sections = sections; 11 | this.pipeline = pipeline; 12 | 13 | this.json = new JSONFormat(sections, pipeline); 14 | } 15 | util.inherits(Printable, JSONFormat); 16 | module.exports = Printable; 17 | util.inherits(Printable, JSONFormat); 18 | 19 | Printable.prototype.parse = function parse(data) { 20 | throw new Error('Not implemented yet'); 21 | }; 22 | 23 | Printable.prototype.render = function render() { 24 | var out = 'register {\n'; 25 | 26 | out += this.renderSpillType(); 27 | 28 | for (var i = 0; i < this.pipeline.instructions.length; i++) 29 | out += ' ' + this.renderInstruction(this.pipeline.instructions[i]) + '\n'; 30 | 31 | out += '}'; 32 | 33 | return out; 34 | }; 35 | 36 | Printable.prototype.renderSpillType = function renderSpillType() { 37 | var out = ''; 38 | for (var i = 0; i < this.pipeline.spillType.length; i++) { 39 | var type = this.pipeline.spillType[i]; 40 | out += ' # [' + type.from + ', ' + type.to + ') as ' + type.type + '\n'; 41 | } 42 | return out; 43 | }; 44 | 45 | Printable.prototype.renderInstruction = function renderInstruction(instr) { 46 | var out = ''; 47 | 48 | if (instr.output === null) 49 | out += instr.opcode; 50 | else 51 | out += this.renderOperand(instr.output) + ' = ' + instr.opcode; 52 | 53 | var list = []; 54 | for (var i = 0; i < instr.literals.length; i++) 55 | list.push(JSON.stringify(instr.literals[i])); 56 | 57 | for (var i = 0; i < instr.links.length; i++) { 58 | var delta = instr.links[i].index - instr.index; 59 | var tmp = '&'; 60 | if (delta >= 0) 61 | tmp += '+' + delta; 62 | else 63 | tmp += delta; 64 | 65 | list.push(tmp); 66 | } 67 | 68 | for (var i = 0; i < instr.inputs.length; i++) 69 | list.push(this.renderOperand(instr.inputs[i])); 70 | 71 | if (list.length === 0) 72 | return out; 73 | 74 | return out + ' ' + list.join(', '); 75 | }; 76 | 77 | Printable.prototype.renderOperand = function renderOperand(operand) { 78 | if (operand.kind === 'register') 79 | return '%' + operand.value; 80 | 81 | return '[' + operand.value + ']'; 82 | }; 83 | -------------------------------------------------------------------------------- /lib/pipeline/register/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | var pipeline = require('../../pipeline'); 6 | var Base = pipeline.Base; 7 | 8 | function Register() { 9 | Base.call(this); 10 | 11 | this.instructions = []; 12 | this.spills = 0; 13 | this.spillType = []; 14 | this._registers = {}; 15 | } 16 | util.inherits(Register, Base); 17 | module.exports = Register; 18 | 19 | Register.Instruction = require('./instruction'); 20 | Register.Operand = require('./operand'); 21 | Register.SpillTypeRange = require('./spill-type-range'); 22 | 23 | Register.create = function create() { 24 | return new Register(); 25 | }; 26 | 27 | Register.formats = {}; 28 | Register.prototype.formats = Register.formats; 29 | 30 | Register.prototype.getUsedRegisters = function getUsedRegisters() { 31 | return Object.keys(this._registers); 32 | }; 33 | 34 | Register.prototype.reg = function reg(name) { 35 | this._registers[name] = true; 36 | return new Register.Operand('register', name); 37 | }; 38 | 39 | Register.prototype.spill = function spill(index) { 40 | this.spills = Math.max(this.spills, index + 1); 41 | return new Register.Operand('spill', index); 42 | }; 43 | 44 | Register.prototype.setSpillType = function setSpillType(type, from, to) { 45 | this.spillType.push(new Register.SpillTypeRange(type, from, to)); 46 | }; 47 | 48 | Register.prototype.create = function create(opcode, output, inputs) { 49 | var instr = Register.Instruction.create(opcode); 50 | 51 | if (output) 52 | instr.setOutput(output); 53 | 54 | if (inputs) { 55 | // Single input 56 | if (!Array.isArray(inputs)) { 57 | instr.addInput(inputs); 58 | 59 | // Multiple inputs 60 | } else { 61 | for (var i = 0; i < inputs.length; i++) 62 | instr.addInput(inputs[i]); 63 | } 64 | } 65 | 66 | return instr; 67 | }; 68 | 69 | Register.prototype.add = function add(opcode, output, inputs) { 70 | var instr = this.create(opcode, output, inputs); 71 | 72 | return this.append(instr); 73 | }; 74 | 75 | Register.prototype.append = function append(instr) { 76 | instr.index = this.instructions.length; 77 | this.instructions.push(instr); 78 | 79 | return instr; 80 | }; 81 | -------------------------------------------------------------------------------- /lib/pipeline/register/instruction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Instruction(opcode) { 4 | this.index = null; 5 | this.opcode = opcode; 6 | this.output = null; 7 | this.inputs = []; 8 | this.literals = []; 9 | 10 | this.links = []; 11 | this.linkUses = []; 12 | } 13 | module.exports = Instruction; 14 | 15 | Instruction.create = function create(opcode) { 16 | return new Instruction(opcode); 17 | }; 18 | 19 | Instruction.prototype.setOutput = function setOutput(output) { 20 | this.output = output; 21 | return this; 22 | }; 23 | 24 | Instruction.prototype.addInput = function addInput(input) { 25 | this.inputs.push(input); 26 | return this; 27 | }; 28 | 29 | Instruction.prototype.addLiteral = function addLiteral(value) { 30 | this.literals.push(value); 31 | return this; 32 | }; 33 | 34 | Instruction.prototype.link = function link(instr) { 35 | instr.linkUses.push(instr, this.links.length); 36 | this.links.push(instr); 37 | return this; 38 | }; 39 | -------------------------------------------------------------------------------- /lib/pipeline/register/operand.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Operand(kind, value) { 4 | this.kind = kind; 5 | this.value = value; 6 | } 7 | module.exports = Operand; 8 | 9 | Operand.prototype.isEqual = function isEqual(other) { 10 | return this.kind === other.kind && this.value === other.value; 11 | }; 12 | 13 | Operand.prototype.isRegister = function isRegister() { 14 | return this.kind === 'register'; 15 | }; 16 | 17 | Operand.prototype.isSpill = function isSpill() { 18 | return this.kind === 'spill'; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/pipeline/register/spill-type-range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function SpillTypeRange(type, from, to) { 4 | this.type = type; 5 | this.from = from; 6 | this.to = to; 7 | } 8 | module.exports = SpillTypeRange; 9 | 10 | SpillTypeRange.sort = function sort(a, b) { 11 | return a.from - b.from; 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-pipeline", 3 | "version": "3.12.3", 4 | "description": "JSON pipeline for abstract compiler", 5 | "main": "lib/pipeline.js", 6 | "scripts": { 7 | "test": "jscs lib/**/*.js test/*.js && jshint lib/**/*.js && mocha --reporter=spec test/*-test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/indutny/json-pipeline.git" 12 | }, 13 | "keywords": [ 14 | "JSON", 15 | "pipeline", 16 | "CFG", 17 | "SSA", 18 | "Sea", 19 | "of", 20 | "nodes", 21 | "Compiler", 22 | "JIT" 23 | ], 24 | "author": "Fedor Indutny ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/indutny/json-pipeline/issues" 28 | }, 29 | "homepage": "https://github.com/indutny/json-pipeline#readme", 30 | "devDependencies": { 31 | "assert-text": "^1.1.0", 32 | "jscs": "^1.13.1", 33 | "jshint": "^2.8.0", 34 | "mocha": "^2.2.5" 35 | }, 36 | "dependencies": { 37 | "bitfield.js": "^1.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /register-allocation.md: -------------------------------------------------------------------------------- 1 | # Output Format 2 | 3 | ## JSON 4 | 5 | ```js 6 | { 7 | "registers": [ "rax", "rbx", ... ], 8 | "spills": ...spill count..., 9 | 10 | "instructions": [ 11 | { 12 | "opcode": "...opcode name...", 13 | "output": null or operand, 14 | "inputs": [ 15 | // Input operands 16 | 0, 1, 2, 3, // positive numbers for registers 17 | -1, -2, -3, -4 // negative numbers for spills 18 | ], 19 | "literals": [ 20 | // Any javascript literal values 21 | ], 22 | "links": [ 23 | 0, 1, 2 // indexes of other instructions (useful for jumps/ifs) 24 | ] 25 | } 26 | ] 27 | } 28 | ``` 29 | 30 | ## Printable 31 | 32 | General format for each line is: 33 | 34 | `out = opcode literals, links, inputs` 35 | 36 | Where output, input, link, and literal have following format: 37 | 38 | * `%...` for register 39 | * `[...]` for spill slot 40 | * `&+...`, `&-...` for link: `+...` and `-...` specifies offset from the current 41 | instruction (in instruction count) 42 | * everything else - literal 43 | 44 | Example: 45 | 46 | ``` 47 | register { 48 | %rax = literal 1 49 | %rbx = literal 2 50 | %rax = add %rax, %rbx 51 | if &+1, &+3 52 | 53 | %rax = add %rax, %rax 54 | jump &+3 55 | 56 | %rax = add %rax, %rbx 57 | jump &+1 58 | 59 | return %rax 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /test/cfg-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var assertText = require('assert-text'); 3 | 4 | assertText.options.trim = true; 5 | 6 | var pipeline = require('../'); 7 | 8 | var fixtures = require('./fixtures'); 9 | 10 | describe('JSON CFG Builder', function() { 11 | var p; 12 | beforeEach(function() { 13 | p = pipeline.create('cfg'); 14 | }); 15 | 16 | it('should generate CFG', function() { 17 | var start = p.block('start'); 18 | assert.equal(start.index, 0); 19 | 20 | var one = p.add('literal').addLiteral(1); 21 | var two = p.add('literal').addLiteral(2); 22 | var add = p.add('add', [ one, two ]); 23 | var branch = p.addControl('if', [ add ]); 24 | 25 | var left = p.jumpFrom(start); 26 | assert.equal(left.blockIndex, 1); 27 | 28 | var x0 = p.add('literal').addLiteral('ok'); 29 | var leftEnd = p.addControl('jump'); 30 | 31 | var right = p.jumpFrom(start); 32 | 33 | var x1 = p.add('literal').addLiteral('not-ok'); 34 | var rightEnd = p.addControl('jump'); 35 | 36 | var merge = p.merge(left, right); 37 | var phi = p.addControl('phi', [ x0, x1 ]); 38 | p.addControl('return', [ phi ]); 39 | 40 | assert(phi.block === merge); 41 | 42 | p.link(); 43 | p.verify(); 44 | 45 | // Normal export 46 | assert.deepEqual(p.render('json'), fixtures.json.p1); 47 | 48 | // CFG export 49 | assert.deepEqual(p.render({ cfg: true }, 'json'), fixtures.json.p1cfg); 50 | }); 51 | 52 | it('should parse CFG', function() { 53 | p.parse(fixtures.json.p1cfg, { cfg: true }, 'json'); 54 | p.verify(); 55 | 56 | // NOTE: we can't use `.render()` + `deepEqual()` here, because the indexes 57 | // are off after parsing 58 | assert.equal(p.blocks[0].nodes.length, 4); 59 | assert.equal(p.blocks[0].successors.length, 2); 60 | assert.equal(p.blocks[1].nodes.length, 2); 61 | assert.equal(p.blocks[1].successors.length, 1); 62 | assert.equal(p.blocks[2].nodes.length, 2); 63 | assert.equal(p.blocks[2].successors.length, 1); 64 | assert.equal(p.blocks[3].nodes.length, 2); 65 | assert.equal(p.blocks[3].successors.length, 0); 66 | }); 67 | 68 | it('should reindex branch', function() { 69 | var start = p.block('start'); 70 | 71 | var one = p.add('literal').addLiteral(1); 72 | var two = p.add('literal').addLiteral(2); 73 | var add = p.add('add', [ one, two ]); 74 | var branch = p.addControl('if', [ add ]); 75 | 76 | var left = p.jumpFrom(start); 77 | 78 | var x0 = p.add('literal').addLiteral('ok'); 79 | var leftEnd = p.addControl('jump'); 80 | 81 | var right = p.jumpFrom(start); 82 | 83 | var x1 = p.add('literal').addLiteral('not-ok'); 84 | var rightEnd = p.addControl('jump'); 85 | 86 | var merge = p.merge(left, right); 87 | var phi = p.addControl('phi', [ x0, x1 ]); 88 | p.addControl('return', [ phi ]); 89 | 90 | p.reindex(); 91 | p.link(); 92 | p.verify(); 93 | 94 | // CFG export 95 | var text = p.render({ cfg: true }, 'printable'); 96 | assertText.equal(text, fixtures.fn2str(function() {/* 97 | pipeline { 98 | b0 { 99 | i0 = literal 1 100 | i1 = literal 2 101 | i2 = add i0, i1 102 | i3 = if ^b0, i2 103 | } 104 | b0 -> b1, b2 105 | b1 { 106 | i4 = literal "ok" 107 | i5 = jump ^b1 108 | } 109 | b1 -> b3 110 | b2 { 111 | i6 = literal "not-ok" 112 | i7 = jump ^b2 113 | } 114 | b2 -> b3 115 | b3 { 116 | i8 = phi ^b3, i4, i6 117 | i9 = return ^i8, i8 118 | } 119 | } 120 | */})); 121 | 122 | // Index of nodes 123 | assert.equal(start.index, 0); 124 | assert.equal(left.index, 1); 125 | assert.equal(right.index, 2); 126 | }); 127 | 128 | it('should reindex loop', function() { 129 | var start = p.block('start'); 130 | p.addControl('jump'); 131 | 132 | var head = p.jumpFrom(start); 133 | var read = p.add('read'); 134 | 135 | var next = p.jumpFrom(head); 136 | 137 | var branch = p.addControl('if', [ read ]); 138 | var body = p.jumpFrom(next); 139 | p.add('print'); 140 | 141 | // Ensure no X intersection 142 | body.jump(head); 143 | 144 | var end = p.jumpFrom(next); 145 | p.addControl('return', read); 146 | 147 | p.reindex(); 148 | p.link(); 149 | p.verify(); 150 | 151 | // CFG export 152 | var text = p.render({ cfg: true }, 'printable'); 153 | assertText.equal(text, fixtures.fn2str(function() {/* 154 | pipeline { 155 | b0 { 156 | i0 = jump ^b0 157 | } 158 | b0 -> b1 159 | b1 { 160 | i1 = read 161 | } 162 | b1 -> b2 163 | b2 { 164 | i2 = if ^b2, i1 165 | } 166 | b2 -> b3, b4 167 | b3 { 168 | i3 = print 169 | } 170 | b3 -> b1 171 | b4 { 172 | i4 = return ^b4, i1 173 | } 174 | } 175 | */})); 176 | }); 177 | 178 | it('should link', function() { 179 | var start = p.block(); 180 | p.addControl('if'); 181 | 182 | var left = p.block(); 183 | p.addControl('jump'); 184 | 185 | var right = p.block(); 186 | p.addControl('jump'); 187 | 188 | var merge = p.block(); 189 | var one = p.add('literal').addLiteral(1); 190 | p.addControl('return', one); 191 | 192 | start.jump(left); 193 | start.jump(right); 194 | left.jump(merge); 195 | right.jump(merge); 196 | 197 | p.link(); 198 | p.verify(); 199 | 200 | var text = p.render('printable'); 201 | assertText.equal(text, fixtures.fn2str(function() {/* 202 | pipeline { 203 | i0 = start 204 | i1 = if ^i0 205 | i2 = region ^i1 206 | i3 = jump ^i2 207 | i4 = region ^i1 208 | i5 = jump ^i4 209 | i6 = region ^i3, ^i5 210 | i7 = literal 1 211 | i8 = return ^i6, i7 212 | } 213 | */})); 214 | }); 215 | 216 | it('should remove', function() { 217 | var start = p.block(); 218 | var rem = p.add('to-be-removed'); 219 | p.addControl('return'); 220 | 221 | p.remove(rem); 222 | p.link(); 223 | p.verify(); 224 | 225 | var text = p.render({ cfg: true }, 'printable'); 226 | assertText.equal(text, fixtures.fn2str(function() {/* 227 | pipeline { 228 | b0 { 229 | i0 = return ^b0 230 | } 231 | } 232 | */})); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /test/dominance-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var pipeline = require('../'); 4 | 5 | var fixtures = require('./fixtures'); 6 | 7 | describe('JSON Dominance', function() { 8 | var p; 9 | beforeEach(function() { 10 | p = pipeline.create('dominance'); 11 | }); 12 | 13 | it('should generate CFG with Dominator tree', function() { 14 | var start = p.block('start'); 15 | var t = p.add('literal').addLiteral(true); 16 | var branch = p.addControl('if', [ t ]); 17 | 18 | var left = p.jumpFrom(start); 19 | start.addChild(left); 20 | 21 | var x0 = p.add('literal').addLiteral('ok'); 22 | var leftEnd = p.addControl('jump'); 23 | 24 | var right = p.jumpFrom(start); 25 | start.addChild(right); 26 | 27 | var x1 = p.add('literal').addLiteral('not-ok'); 28 | var rightEnd = p.addControl('jump'); 29 | 30 | var merge = p.merge(left, right); 31 | start.addChild(merge); 32 | left.addFrontier(merge); 33 | right.addFrontier(merge); 34 | 35 | var phi = p.addControl('phi', [ x0, x1 ]); 36 | p.addControl('return', [ phi ]); 37 | 38 | p.link(); 39 | p.verify(); 40 | 41 | // Dominance export 42 | assert.deepEqual(p.render({ 43 | cfg: true, 44 | dominance: true 45 | }, 'json'), fixtures.json.p2dom); 46 | }); 47 | 48 | it('should parse CFG with Dominator tree', function() { 49 | p.parse(fixtures.json.p2dom, { cfg: true, dominance: true }, 'json'); 50 | 51 | p.verify(); 52 | 53 | // Tree 54 | assert(p.blocks[0].parent === null); 55 | assert(p.blocks[1].parent === p.blocks[0]); 56 | assert(p.blocks[2].parent === p.blocks[0]); 57 | assert(p.blocks[3].parent === p.blocks[0]); 58 | 59 | // Frontier 60 | assert(p.blocks[1].frontier[0] === p.blocks[3]); 61 | assert(p.blocks[2].frontier[0] === p.blocks[3]); 62 | }); 63 | 64 | it('should enumerate Dominator tree and do `dominates` checks', function() { 65 | var input = fixtures.fn2str(function() {/* 66 | pipeline { 67 | b0 { 68 | } 69 | b0 => b1, b4, b6 70 | 71 | b1 { 72 | } 73 | b1 => b2, b3 74 | 75 | b2 { 76 | } 77 | b3 { 78 | } 79 | 80 | b4 { 81 | } 82 | b4 => b5 83 | 84 | b5 { 85 | } 86 | 87 | b6 { 88 | } 89 | } 90 | */}); 91 | p.parse(input, { cfg: true, dominance: true }, 'printable'); 92 | 93 | p.enumerate(); 94 | p.verify(); 95 | 96 | /* 97 | * b0 98 | * / | \ 99 | * b1 b4 b6 100 | * / | | 101 | * b2 b3 b5 102 | */ 103 | function range(index) { 104 | var block = p.blocks[index]; 105 | return '[' + block.dominanceStart + ';' + block.dominanceEnd + ']'; 106 | } 107 | assert.equal(range(0), '[0;6]'); 108 | assert.equal(range(1), '[1;3]'); 109 | assert.equal(range(2), '[2;2]'); 110 | assert.equal(range(3), '[3;3]'); 111 | assert.equal(range(4), '[4;5]'); 112 | assert.equal(range(6), '[6;6]'); 113 | 114 | assert(p.blocks[0].dominates(p.blocks[2])); 115 | assert(p.blocks[0].dominates(p.blocks[3])); 116 | assert(p.blocks[0].dominates(p.blocks[5])); 117 | assert(p.blocks[0].dominates(p.blocks[6])); 118 | assert(!p.blocks[1].dominates(p.blocks[5])); 119 | assert(!p.blocks[6].dominates(p.blocks[2])); 120 | assert(!p.blocks[6].dominates(p.blocks[0])); 121 | 122 | assert.equal(p.blocks[0].dominanceDepth, 0); 123 | assert.equal(p.blocks[1].dominanceDepth, 1); 124 | assert.equal(p.blocks[4].dominanceDepth, 1); 125 | assert.equal(p.blocks[6].dominanceDepth, 1); 126 | assert.equal(p.blocks[2].dominanceDepth, 2); 127 | assert.equal(p.blocks[3].dominanceDepth, 2); 128 | assert.equal(p.blocks[5].dominanceDepth, 2); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/fixtures/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | exports.json = { 4 | p0: require('./p0.json'), 5 | p1: require('./p1.json'), 6 | p1cfg: require('./p1cfg.json'), 7 | p2dom: require('./p2dom.json'), 8 | r0: require('./r0.json') 9 | }; 10 | 11 | exports.fn2str = function fn2str(fn) { 12 | return fn.toString().replace(/^function[^{]+{\/\*|\*\/}$/g, ''); 13 | }; 14 | -------------------------------------------------------------------------------- /test/fixtures/p0.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "opcode": "start", 5 | "control": [], 6 | "literals": [], 7 | "inputs": [] 8 | }, 9 | 10 | { 11 | "opcode": "literal", 12 | "control": [], 13 | "literals": [ 1 ], 14 | "inputs": [] 15 | }, 16 | 17 | { 18 | "opcode": "literal", 19 | "control": [], 20 | "literals": [ 2 ], 21 | "inputs": [] 22 | }, 23 | 24 | { 25 | "opcode": "add", 26 | "control": [], 27 | "literals": [], 28 | "inputs": [ 1, 2 ] 29 | }, 30 | 31 | { 32 | "opcode": "return", 33 | "control": [ 0 ], 34 | "literals": [], 35 | "inputs": [ 3 ] 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/fixtures/p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "opcode": "start", 5 | "control": [], 6 | "literals": [], 7 | "inputs": [] 8 | }, 9 | { 10 | "opcode": "literal", 11 | "control": [], 12 | "literals": [ 1 ], 13 | "inputs": [] 14 | }, 15 | { 16 | "opcode": "literal", 17 | "control": [], 18 | "literals": [ 2 ], 19 | "inputs": [] 20 | }, 21 | { 22 | "opcode": "add", 23 | "control": [], 24 | "literals": [], 25 | "inputs": [ 1, 2 ] 26 | }, 27 | { 28 | "opcode": "if", 29 | "control": [ 0 ], 30 | "literals": [], 31 | "inputs": [ 3 ] 32 | }, 33 | { 34 | "opcode": "region", 35 | "control": [ 4 ], 36 | "literals": [], 37 | "inputs": [] 38 | }, 39 | { 40 | "opcode": "literal", 41 | "control": [], 42 | "literals": [ "ok" ], 43 | "inputs": [] 44 | }, 45 | { 46 | "opcode": "jump", 47 | "control": [ 5 ], 48 | "literals": [], 49 | "inputs": [] 50 | }, 51 | { 52 | "opcode": "region", 53 | "control": [ 4 ], 54 | "literals": [], 55 | "inputs": [] 56 | }, 57 | { 58 | "opcode": "literal", 59 | "control": [], 60 | "literals": [ "not-ok" ], 61 | "inputs": [] 62 | }, 63 | { 64 | "opcode": "jump", 65 | "control": [ 8 ], 66 | "literals": [], 67 | "inputs": [] 68 | }, 69 | { 70 | "opcode": "region", 71 | "control": [ 7, 10 ], 72 | "literals": [], 73 | "inputs": [] 74 | }, 75 | { 76 | "opcode": "phi", 77 | "control": [ 11 ], 78 | "literals": [], 79 | "inputs": [ 6, 9 ] 80 | }, 81 | { 82 | "opcode": "return", 83 | "control": [ 12 ], 84 | "literals": [], 85 | "inputs": [ 12 ] 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /test/fixtures/p1cfg.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "opcode": "start", 5 | "control": [], 6 | "literals": [], 7 | "inputs": [] 8 | }, 9 | { 10 | "opcode": "literal", 11 | "control": [], 12 | "literals": [ 1 ], 13 | "inputs": [] 14 | }, 15 | { 16 | "opcode": "literal", 17 | "control": [], 18 | "literals": [ 2 ], 19 | "inputs": [] 20 | }, 21 | { 22 | "opcode": "add", 23 | "control": [], 24 | "literals": [], 25 | "inputs": [ 1, 2 ] 26 | }, 27 | { 28 | "opcode": "if", 29 | "control": [ 0 ], 30 | "literals": [], 31 | "inputs": [ 3 ] 32 | }, 33 | { 34 | "opcode": "region", 35 | "control": [ 4 ], 36 | "literals": [], 37 | "inputs": [] 38 | }, 39 | { 40 | "opcode": "literal", 41 | "control": [], 42 | "literals": [ "ok" ], 43 | "inputs": [] 44 | }, 45 | { 46 | "opcode": "jump", 47 | "control": [ 5 ], 48 | "literals": [], 49 | "inputs": [] 50 | }, 51 | { 52 | "opcode": "region", 53 | "control": [ 4 ], 54 | "literals": [], 55 | "inputs": [] 56 | }, 57 | { 58 | "opcode": "literal", 59 | "control": [], 60 | "literals": [ "not-ok" ], 61 | "inputs": [] 62 | }, 63 | { 64 | "opcode": "jump", 65 | "control": [ 8 ], 66 | "literals": [], 67 | "inputs": [] 68 | }, 69 | { 70 | "opcode": "region", 71 | "control": [ 7, 10 ], 72 | "literals": [], 73 | "inputs": [] 74 | }, 75 | { 76 | "opcode": "phi", 77 | "control": [ 11 ], 78 | "literals": [], 79 | "inputs": [ 6, 9 ] 80 | }, 81 | { 82 | "opcode": "return", 83 | "control": [ 12 ], 84 | "literals": [], 85 | "inputs": [ 12 ] 86 | } 87 | ], 88 | "cfg": { 89 | "blocks": [ 90 | { 91 | "node": 0, 92 | "nodes": [ 1, 2, 3, 4 ], 93 | "successors": [ 5, 8 ] 94 | }, 95 | { 96 | "node": 5, 97 | "nodes": [ 6, 7 ], 98 | "successors": [ 11 ] 99 | }, 100 | { 101 | "node": 8, 102 | "nodes": [ 9, 10 ], 103 | "successors": [ 11 ] 104 | }, 105 | { 106 | "node": 11, 107 | "nodes": [ 12, 13 ], 108 | "successors": [] 109 | } 110 | ] 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/fixtures/p2dom.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "opcode": "start", 5 | "control": [], 6 | "literals": [], 7 | "inputs": [] 8 | }, 9 | { 10 | "opcode": "literal", 11 | "control": [], 12 | "literals": [ 13 | true 14 | ], 15 | "inputs": [] 16 | }, 17 | { 18 | "opcode": "if", 19 | "control": [ 20 | 0 21 | ], 22 | "literals": [], 23 | "inputs": [ 24 | 1 25 | ] 26 | }, 27 | { 28 | "opcode": "region", 29 | "control": [ 30 | 2 31 | ], 32 | "literals": [], 33 | "inputs": [] 34 | }, 35 | { 36 | "opcode": "literal", 37 | "control": [], 38 | "literals": [ 39 | "ok" 40 | ], 41 | "inputs": [] 42 | }, 43 | { 44 | "opcode": "jump", 45 | "control": [ 46 | 3 47 | ], 48 | "literals": [], 49 | "inputs": [] 50 | }, 51 | { 52 | "opcode": "region", 53 | "control": [ 54 | 2 55 | ], 56 | "literals": [], 57 | "inputs": [] 58 | }, 59 | { 60 | "opcode": "literal", 61 | "control": [], 62 | "literals": [ 63 | "not-ok" 64 | ], 65 | "inputs": [] 66 | }, 67 | { 68 | "opcode": "jump", 69 | "control": [ 70 | 6 71 | ], 72 | "literals": [], 73 | "inputs": [] 74 | }, 75 | { 76 | "opcode": "region", 77 | "control": [ 78 | 5, 79 | 8 80 | ], 81 | "literals": [], 82 | "inputs": [] 83 | }, 84 | { 85 | "opcode": "phi", 86 | "control": [ 87 | 9 88 | ], 89 | "literals": [], 90 | "inputs": [ 91 | 4, 92 | 7 93 | ] 94 | }, 95 | { 96 | "opcode": "return", 97 | "control": [ 98 | 10 99 | ], 100 | "literals": [], 101 | "inputs": [ 102 | 10 103 | ] 104 | } 105 | ], 106 | "cfg": { 107 | "blocks": [ 108 | { 109 | "node": 0, 110 | "successors": [ 111 | 3, 112 | 6 113 | ], 114 | "nodes": [ 115 | 1, 116 | 2 117 | ] 118 | }, 119 | { 120 | "node": 3, 121 | "successors": [ 122 | 9 123 | ], 124 | "nodes": [ 125 | 4, 126 | 5 127 | ] 128 | }, 129 | { 130 | "node": 6, 131 | "successors": [ 132 | 9 133 | ], 134 | "nodes": [ 135 | 7, 136 | 8 137 | ] 138 | }, 139 | { 140 | "node": 9, 141 | "successors": [], 142 | "nodes": [ 143 | 10, 144 | 11 145 | ] 146 | } 147 | ] 148 | }, 149 | "dominance": { 150 | "blocks": [ 151 | { 152 | "node": 0, 153 | "parent": null, 154 | "frontier": [] 155 | }, 156 | { 157 | "node": 3, 158 | "parent": 0, 159 | "frontier": [ 160 | 9 161 | ] 162 | }, 163 | { 164 | "node": 6, 165 | "parent": 0, 166 | "frontier": [ 167 | 9 168 | ] 169 | }, 170 | { 171 | "node": 9, 172 | "parent": 0, 173 | "frontier": [] 174 | } 175 | ] 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /test/fixtures/r0.json: -------------------------------------------------------------------------------- 1 | { 2 | "registers": [ "rax", "rbx" ], 3 | "spills": 0, 4 | 5 | "instructions": [ 6 | { 7 | "opcode": "literal", 8 | "inputs": [], 9 | "output": 0, 10 | "literals": [ 1 ], 11 | "links": [] 12 | }, 13 | { 14 | "opcode": "literal", 15 | "inputs": [], 16 | "output": 1, 17 | "literals": [ 2 ], 18 | "links": [] 19 | }, 20 | { 21 | "opcode": "add", 22 | "inputs": [ 0, 1 ], 23 | "output": 0, 24 | "literals": [], 25 | "links": [] 26 | }, 27 | { 28 | "opcode": "return", 29 | "inputs": [ 0 ], 30 | "output": null, 31 | "literals": [], 32 | "links": [] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/pipeline-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var assertText = require('assert-text'); 3 | 4 | assertText.options.trim = true; 5 | 6 | var pipeline = require('../'); 7 | 8 | var fixtures = require('./fixtures'); 9 | 10 | describe('JSON Pipeline', function() { 11 | var p; 12 | beforeEach(function() { 13 | p = pipeline.create(); 14 | }); 15 | 16 | it('should render JSON', function() { 17 | var start = p.add('start'); 18 | var one = p.add('literal').addLiteral(1); 19 | var two = p.add('literal').addLiteral(2); 20 | var add = p.add('add', [ one, two ]); 21 | var ret = p.add('return', [ add ]).setControl(start); 22 | 23 | assert(start.isControl()); 24 | assert(ret.isControl()); 25 | 26 | assert.equal(one.index, 1); 27 | 28 | assert.deepEqual(p.render('json'), fixtures.json.p0); 29 | }); 30 | 31 | it('should parse JSON', function() { 32 | p.parse(fixtures.json.p0, 'json'); 33 | assert.deepEqual(p.render('json'), fixtures.json.p0); 34 | }); 35 | 36 | it('should remove nodes and clean up uses', function() { 37 | var start = p.add('start'); 38 | var one = p.add('literal').addLiteral(1); 39 | var two = p.add('literal').addLiteral(2); 40 | var add = p.add('add', [ one, two ]); 41 | var extra1 = p.add('add', [ one, two ]); 42 | var ret = p.add('return', [ add ]).setControl(start); 43 | var extra2 = p.add('add', [ one, two ]).setControl(start); 44 | 45 | p.remove(extra1); 46 | p.remove(extra2); 47 | p.verify(); 48 | 49 | assert.equal(one.uses.length, 2); 50 | assert.equal(two.uses.length, 2); 51 | assert.equal(start.controlUses.length, 2); 52 | 53 | assert.deepEqual(p.render('json'), fixtures.json.p0); 54 | }); 55 | 56 | it('should remove control nodes', function() { 57 | var start = p.add('start'); 58 | var middle = p.add('middle').setControl(start); 59 | var end = p.add('end').setControl(middle); 60 | 61 | p.remove(middle); 62 | p.verify(); 63 | 64 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 65 | pipeline { 66 | i0 = start 67 | i1 = end ^i0 68 | } 69 | */})); 70 | }); 71 | 72 | it('should cut control nodes', function() { 73 | var start = p.add('start'); 74 | var branch = p.add('if').setControl(start); 75 | var left = p.add('region').setControl(branch); 76 | var right = p.add('region').setControl(branch); 77 | var merge = p.add('end').setControl(left, right); 78 | 79 | p.cut(branch); 80 | assert.equal(start.controlUses.length, 0); 81 | p.verify(); 82 | 83 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 84 | pipeline { 85 | i0 = start 86 | i1 = if 87 | i2 = region ^i1 88 | i3 = region ^i1 89 | i4 = end ^i2, ^i3 90 | } 91 | */})); 92 | }); 93 | 94 | it('should replace node uses with other node', function() { 95 | var start = p.add('start'); 96 | var one = p.add('literal').setControl(start).addLiteral(1); 97 | var two = p.add('literal').addLiteral(2); 98 | var add = p.add('add', [ one, two ]).setControl(one); 99 | 100 | var three = p.add('literal').addLiteral(3); 101 | one.loc = 'ok'; 102 | one.replace(three); 103 | assert.equal(add.inputs[0], three); 104 | assert.equal(three.uses.length, 2); 105 | assert.equal(three.uses[0], add); 106 | assert.equal(three.uses[1], 0); 107 | assert.equal(three.loc, 'ok'); 108 | p.verify(); 109 | 110 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 111 | pipeline { 112 | i0 = start 113 | i1 = literal 1 114 | i2 = literal 2 115 | i3 = add ^i4, i4, i2 116 | i4 = literal ^i0, 3 117 | } 118 | */})); 119 | }); 120 | 121 | it('should replace control node', function() { 122 | var start = p.add('start'); 123 | var middle = p.add('middle').setControl(start); 124 | var end = p.add('end').setControl(middle); 125 | 126 | middle.replace(p.add('replaced')); 127 | 128 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 129 | pipeline { 130 | i0 = start 131 | i1 = middle 132 | i2 = end ^i3 133 | i3 = replaced ^i0 134 | } 135 | */})); 136 | }); 137 | 138 | it('should replace with control node', function() { 139 | var start = p.add('start'); 140 | var middle = p.add('middle'); 141 | var end = p.add('end').setControl(middle); 142 | 143 | var replaced = p.add('replaced').setControl(start); 144 | middle.replace(replaced); 145 | assert.equal(start.controlUses.length, 2); 146 | assert.equal(replaced.controlUses.length, 2); 147 | assert.equal(replaced.control.length, 1); 148 | assert.equal(end.control.length, 1); 149 | p.verify(); 150 | 151 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 152 | pipeline { 153 | i0 = start 154 | i1 = middle 155 | i2 = end ^i3 156 | i3 = replaced ^i0 157 | } 158 | */})); 159 | }); 160 | 161 | it('should replace input uses with other node', function() { 162 | var one = p.add('literal').addLiteral(1); 163 | var two = p.add('literal').addLiteral(2); 164 | var add = p.add('add', [ one, two ]); 165 | 166 | var three = p.add('literal').addLiteral(3); 167 | add.replaceInput(0, three); 168 | 169 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 170 | pipeline { 171 | i0 = literal 1 172 | i1 = literal 2 173 | i2 = add i3, i1 174 | i3 = literal 3 175 | } 176 | */})); 177 | }); 178 | 179 | it('should remove input', function() { 180 | var one = p.add('literal').addLiteral(1); 181 | var two = p.add('literal').addLiteral(2); 182 | var use = p.add('use', [ one, two ]); 183 | 184 | var three = p.add('literal').addLiteral(3); 185 | use.removeInput(0); 186 | assert.equal(one.uses.length, 0); 187 | assert.equal(two.uses.length, 2); 188 | assert.equal(two.uses[0], use); 189 | assert.equal(two.uses[1], 0); 190 | 191 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 192 | pipeline { 193 | i0 = literal 1 194 | i1 = literal 2 195 | i2 = use i1 196 | i3 = literal 3 197 | } 198 | */})); 199 | }); 200 | 201 | it('should remove control from control node', function() { 202 | var start = p.add('start'); 203 | var middle = p.add('middle').setControl(start); 204 | var end = p.add('end').setControl(middle); 205 | 206 | middle.removeControl(); 207 | p.verify(); 208 | 209 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 210 | pipeline { 211 | i0 = start 212 | i1 = middle 213 | i2 = end ^i0 214 | } 215 | */})); 216 | }); 217 | 218 | it('should split control', function() { 219 | var start = p.add('start'); 220 | var end = p.add('end').setControl(start); 221 | 222 | var middle = p.add('middle').splitControl(start); 223 | 224 | assert.equal(start.controlUses.length, 2); 225 | assert.equal(middle.controlUses.length, 2); 226 | p.verify(); 227 | 228 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 229 | pipeline { 230 | i0 = start 231 | i1 = end ^i2 232 | i2 = middle ^i0 233 | } 234 | */})); 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /test/printable-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var assertText = require('assert-text'); 3 | 4 | assertText.options.trim = true; 5 | 6 | var pipeline = require('../'); 7 | var Printable = pipeline.Pipeline.formats.printable; 8 | 9 | var fixtures = require('./fixtures'); 10 | 11 | describe('Printable format', function() { 12 | var p; 13 | var printable; 14 | beforeEach(function() { 15 | p = pipeline.create('dominance'); 16 | printable = new Printable({ cfg: true, dominance: true }, p); 17 | }); 18 | 19 | it('should have proper line regexp', function() { 20 | var re = Printable.re; 21 | var match; 22 | 23 | match = 'pipeline {'.match(re); 24 | assert(match && match[1]); 25 | 26 | match = 'b123 {'.match(re); 27 | assert(match && match[2] && !match[4]); 28 | assert.equal(match[2], 'b123'); 29 | 30 | match = 'b123 => b456, b7, b8'.match(re); 31 | assert(match && match[2] && match[4]); 32 | assert.equal(match[2], 'b123'); 33 | assert.equal(match[4], '=>'); 34 | assert.equal(match[5], 'b456, b7, b8'); 35 | 36 | match = 'i23 = opcode 1, "hel lo", i1, i2'.match(re); 37 | assert(match && match[6]); 38 | assert.equal(match[6], 'i23'); 39 | assert.equal(match[7], 'opcode'); 40 | assert.equal(match[8], '1, "hel lo", i1, i2'); 41 | 42 | match = 'i23 = opcode 1'.match(re); 43 | assert(match && match[6]); 44 | assert.equal(match[6], 'i23'); 45 | assert.equal(match[7], 'opcode'); 46 | assert.equal(match[8], '1'); 47 | 48 | match = 'i23 = opcode '.match(re); 49 | assert(match && match[6]); 50 | assert.equal(match[6], 'i23'); 51 | assert.equal(match[7], 'opcode'); 52 | assert.equal(match[8], undefined); 53 | 54 | match = '}'.match(re); 55 | assert(match && match[9]); 56 | 57 | match = '} # comment'.match(re); 58 | assert(match && match[9]); 59 | assert.equal(match[10], 'comment'); 60 | }); 61 | 62 | it('should parse plain input', function() { 63 | var input = fixtures.fn2str(function() {/* 64 | pipeline { 65 | i0 = start 66 | i1 = literal 1 67 | i2 = literal 2 68 | i3 = add i1, i2 69 | i4 = if ^i0 70 | } 71 | */}); 72 | 73 | printable.parse(input); 74 | 75 | var text = p.render('printable'); 76 | assertText.equal(text, fixtures.fn2str(function() {/* 77 | pipeline { 78 | i0 = start 79 | i1 = literal 1 80 | i2 = literal 2 81 | i3 = add i1, i2 82 | i4 = if ^i0 83 | } 84 | */})); 85 | }); 86 | 87 | it('should parse CFG input', function() { 88 | var input = fixtures.fn2str(function() {/* 89 | pipeline { 90 | b0 { 91 | i0 = literal 1 92 | i1 = literal 2 93 | i2 = add i0, i1 94 | i3 = if ^b0 95 | } 96 | b0 -> b1 97 | b0 => b1 98 | b0 ~> b1 99 | 100 | b1 { 101 | i4 = ret ^b1, i2 102 | } 103 | } 104 | */}); 105 | 106 | printable.parse(input); 107 | 108 | assert.deepEqual(p.blocks[0].loc, { line: 2, end: 7 }); 109 | assert.deepEqual(p.blocks[0].nodes[0].loc, { line: 3 }); 110 | 111 | var text = p.render({ cfg: true }, 'printable'); 112 | assertText.equal(text, fixtures.fn2str(function() {/* 113 | pipeline { 114 | b0 { 115 | i0 = literal 1 116 | i1 = literal 2 117 | i2 = add i0, i1 118 | i3 = if ^b0 119 | } 120 | b0 -> b1 121 | b1 { 122 | i4 = ret ^b1, i2 123 | } 124 | } 125 | */})); 126 | }); 127 | 128 | it('should fail to parse some CFG input', function() { 129 | var input = fixtures.fn2str(function() {/* 130 | pipeline { 131 | block b0 { 132 | i0 = literal 1 133 | i1 = literal 2 134 | i2 = add i0, i1 135 | i3 = if ^b0 136 | } 137 | b0 -> b1 138 | b0 => b1 139 | b0 ~> b1 140 | 141 | b1 { 142 | i4 = ret i2 143 | } 144 | } 145 | */}); 146 | 147 | assert.throws(function() { 148 | printable.parse(input); 149 | }, 'block b0'); 150 | }); 151 | 152 | it('should render plain output', function() { 153 | p = pipeline.create(); 154 | p.parse(fixtures.json.p0, 'json'); 155 | 156 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 157 | pipeline { 158 | i0 = start 159 | i1 = literal 1 160 | i2 = literal 2 161 | i3 = add i1, i2 162 | i4 = return ^i0, i3 163 | } 164 | */})); 165 | }); 166 | 167 | it('should render second plain output', function() { 168 | p = pipeline.create(); 169 | p.parse(fixtures.json.p1, 'json'); 170 | 171 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 172 | pipeline { 173 | i0 = start 174 | i1 = literal 1 175 | i2 = literal 2 176 | i3 = add i1, i2 177 | i4 = if ^i0, i3 178 | i5 = region ^i4 179 | i6 = literal "ok" 180 | i7 = jump ^i5 181 | i8 = region ^i4 182 | i9 = literal "not-ok" 183 | i10 = jump ^i8 184 | i11 = region ^i7, ^i10 185 | i12 = phi ^i11, i6, i9 186 | i13 = return ^i12, i12 187 | } 188 | */})); 189 | }); 190 | 191 | it('should render CFG output as plain', function() { 192 | p = pipeline.create('cfg'); 193 | p.parse(fixtures.json.p1cfg, { cfg: true }, 'json'); 194 | 195 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 196 | pipeline { 197 | i0 = start 198 | i1 = literal 1 199 | i2 = literal 2 200 | i3 = add i1, i2 201 | i4 = if ^i0, i3 202 | i5 = region ^i4 203 | i6 = literal "ok" 204 | i7 = jump ^i5 205 | i8 = region ^i4 206 | i9 = literal "not-ok" 207 | i10 = jump ^i8 208 | i11 = region ^i7, ^i10 209 | i12 = phi ^i11, i6, i9 210 | i13 = return ^i12, i12 211 | } 212 | */})); 213 | }); 214 | 215 | it('should render CFG output as CFG', function() { 216 | p = pipeline.create('cfg'); 217 | p.parse(fixtures.json.p1cfg, { cfg: true }, 'json'); 218 | 219 | assertText.equal(p.render({ 220 | cfg: true 221 | }, 'printable'), fixtures.fn2str(function() {/* 222 | pipeline { 223 | b0 { 224 | i0 = literal 1 225 | i1 = literal 2 226 | i2 = add i0, i1 227 | i3 = if ^b0, i2 228 | } 229 | b0 -> b1, b2 230 | b1 { 231 | i4 = literal "ok" 232 | i5 = jump ^b1 233 | } 234 | b1 -> b3 235 | b2 { 236 | i6 = literal "not-ok" 237 | i7 = jump ^b2 238 | } 239 | b2 -> b3 240 | b3 { 241 | i8 = phi ^b3, i4, i6 242 | i9 = return ^i8, i8 243 | } 244 | } 245 | */})); 246 | }); 247 | 248 | it('should render Dominance output', function() { 249 | var sections = { cfg: true, dominance: true }; 250 | p = pipeline.create('dominance'); 251 | p.parse(fixtures.json.p2dom, sections, 'json'); 252 | 253 | var text = p.render(sections, 'printable'); 254 | assertText.equal(text, fixtures.fn2str(function() {/* 255 | pipeline { 256 | b0 { 257 | i0 = literal true 258 | i1 = if ^b0, i0 259 | } 260 | b0 -> b1, b2 261 | b0 => b1, b2, b3 262 | b1 { 263 | i2 = literal "ok" 264 | i3 = jump ^b1 265 | } 266 | b1 -> b3 267 | b1 ~> b3 268 | b2 { 269 | i4 = literal "not-ok" 270 | i5 = jump ^b2 271 | } 272 | b2 -> b3 273 | b2 ~> b3 274 | b3 { 275 | i6 = phi ^b3, i2, i4 276 | i7 = return ^i6, i6 277 | } 278 | } 279 | */})); 280 | }); 281 | 282 | it('should parse out-of-order CFG', function() { 283 | var input = fixtures.fn2str(function() {/* 284 | pipeline { 285 | b0 { 286 | i0 = literal 1 287 | i2 = literal 2 288 | i4 = add i0, i2 289 | i6 = if ^b0 290 | } 291 | b0 -> b1 292 | 293 | b1 { 294 | i8 = ssa:phi ^b1, i4, i2 295 | i10 = ret ^i8, i8 296 | } 297 | } 298 | */}); 299 | 300 | printable.parse(input); 301 | 302 | var text = p.render({ cfg: true }, 'printable'); 303 | assertText.equal(text, fixtures.fn2str(function() {/* 304 | pipeline { 305 | b0 { 306 | i0 = literal 1 307 | i1 = literal 2 308 | i2 = add i0, i1 309 | i3 = if ^b0 310 | } 311 | b0 -> b1 312 | b1 { 313 | i4 = ssa:phi ^b1, i2, i1 314 | i5 = ret ^i4, i4 315 | } 316 | } 317 | */})); 318 | }); 319 | }); 320 | -------------------------------------------------------------------------------- /test/register-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var assertText = require('assert-text'); 3 | 4 | assertText.options.trim = true; 5 | 6 | var pipeline = require('../'); 7 | 8 | var fixtures = require('./fixtures'); 9 | 10 | describe('JSON Pipeline', function() { 11 | var p; 12 | beforeEach(function() { 13 | p = pipeline.create('register'); 14 | }); 15 | 16 | it('should render JSON', function() { 17 | var one = p.add('literal', p.reg('rax')).addLiteral(1); 18 | var two = p.add('literal', p.reg('rbx')).addLiteral(2); 19 | var add = p.add('add', p.reg('rax'), [ p.reg('rax'), p.reg('rbx') ]); 20 | var ret = p.add('return', null, p.reg('rax')); 21 | 22 | assert.equal(two.index, 1); 23 | 24 | assert.deepEqual(p.render('json'), fixtures.json.r0); 25 | }); 26 | 27 | it('should parse JSON', function() { 28 | p.parse(fixtures.json.r0, 'json'); 29 | assert.deepEqual(p.render('json'), fixtures.json.r0); 30 | }); 31 | 32 | it('should report used registers', function() { 33 | p.parse(fixtures.json.r0, 'json'); 34 | assert.deepEqual(p.getUsedRegisters(), [ 'rax', 'rbx' ]); 35 | }); 36 | 37 | it('should render printable', function() { 38 | var one = p.add('literal', p.reg('rax')).addLiteral(1); 39 | var two = p.add('literal', p.spill(0)).addLiteral(2); 40 | var add = p.add('add', p.reg('rax'), [ p.reg('rax'), p.spill(0) ]); 41 | var branch = p.add('if'); 42 | 43 | var left = p.add('add', p.spill(1), [ p.reg('rax'), p.reg('rax') ]); 44 | var leftJump = p.add('jump'); 45 | 46 | var right = p.add('add', p.reg('rax'), [ p.reg('rax'), p.spill(1) ]); 47 | var rightJump = p.add('jump'); 48 | 49 | branch.link(left); 50 | branch.link(right); 51 | 52 | var ret = p.add('return', null, p.reg('rax')); 53 | leftJump.link(ret); 54 | rightJump.link(ret); 55 | 56 | p.setSpillType('gp', 0, 1); 57 | p.setSpillType('fp', 1, 2); 58 | 59 | assertText.equal(p.render('printable'), fixtures.fn2str(function() {/* 60 | register { 61 | # [0, 1) as gp 62 | # [1, 2) as fp 63 | 64 | %rax = literal 1 65 | [0] = literal 2 66 | %rax = add %rax, [0] 67 | if &+1, &+3 68 | 69 | [1] = add %rax, %rax 70 | jump &+3 71 | 72 | %rax = add %rax, [1] 73 | jump &+1 74 | 75 | return %rax 76 | } 77 | */})); 78 | }); 79 | }); 80 | --------------------------------------------------------------------------------