continuation.js
Advanced tools
+2
-2
| { | ||
| "name": "continuation.js", | ||
| "description": "A module for tail call optimization by Continuation Passing Style (CPS) transformation with trampoline technique for Node.js", | ||
| "version": "0.1.1", | ||
| "version": "0.2.0", | ||
| "author": "Daishi Kato <daishi@axlight.com>", | ||
@@ -25,3 +25,3 @@ "dependencies": { | ||
| "cps", | ||
| "tail calls", | ||
| "tail-call", | ||
| "tco", | ||
@@ -28,0 +28,0 @@ "compiler", |
+15
-16
@@ -138,19 +138,18 @@ continuation.js | ||
| |-----------------|---------------|-----------------|-----------| | ||
| | Richards | 370 ops/sec | 16.90 ops/sec | No | | ||
| | DeltaBlue | 178 ops/sec | 6.24 ops/sec | No | | ||
| | Encrypt | 171 ops/sec | 79.93 ops/sec | No | | ||
| | Decrypt | 9.23 ops/sec | 3.65 ops/sec | No | | ||
| | RayTrace | 19.82 ops/sec | 2.23 ops/sec | No | | ||
| | Earley | 268 ops/sec | 20.77 ops/sec | No | | ||
| | Boyer | 19.01 ops/sec | 2.14 ops/sec | No | | ||
| | RegExp | 7.20 ops/sec | 5.00 ops/sec | No | | ||
| | Splay | 116 ops/sec | 69.90 ops/sec | No | | ||
| | NavierStokes | 2.57 ops/sec | 2.43 ops/sec | No | | ||
| | PdfJS | 2.67 ops/sec | 2.49 ops/sec | No | | ||
| | Gameboy | 0.99 ops/sec | 0.28 ops/sec | No | | ||
| | CodeLoadClosure | 343 ops/sec | 392 ops/sec | Yes | | ||
| | CodeLoadJQuery | 10.23 ops/sec | 10.36 ops/sec | Yes | | ||
| | Box2D | 2.28 ops/sec | 2.48 ops/sec | Yes | | ||
| | Richards | 324 ops/sec | 38.28 ops/sec | No | | ||
| | DeltaBlue | 188 ops/sec | 12.90 ops/sec | No | | ||
| | Encrypt | 162 ops/sec | 122 ops/sec | No | | ||
| | Decrypt | 8.62 ops/sec | 6.86 ops/sec | No | | ||
| | RayTrace | 19.71 ops/sec | 4.10 ops/sec | No | | ||
| | Earley | 278 ops/sec | 33.91 ops/sec | No | | ||
| | Boyer | 18.19 ops/sec | 1.60 ops/sec | No | | ||
| | RegExp | 7.12 ops/sec | 5.14 ops/sec | No | | ||
| | Splay | 123 ops/sec | 75.21 ops/sec | No | | ||
| | NavierStokes | 2.40 ops/sec | 3.13 ops/sec | Yes | | ||
| | PdfJS | 2.85 ops/sec | 2.68 ops/sec | No | | ||
| | Gameboy | 0.98 ops/sec | 0.35 ops/sec | No | | ||
| | CodeLoadClosure | 370 ops/sec | 372 ops/sec | Yes | | ||
| | CodeLoadJQuery | 8.83 ops/sec | 10.61 ops/sec | Yes | | ||
| | Box2D | 2.33 ops/sec | 2.54 ops/sec | Yes | | ||
| Limitations | ||
@@ -157,0 +156,0 @@ ----------- |
+214
-148
@@ -65,5 +65,5 @@ /* | ||
| root.ast_prog_header = function() { | ||
| var code = root.parse("if (typeof CpsFunction === 'undefined') { CpsFunction = function(f, k) { this.f = f; this.k = k; }; } if (typeof CpsContinuation === 'undefined') { CpsContinuation = function(k) { if (k) { this.k = k; } else { this.k = function(r) { return r; }; }}; } if (typeof CpsResult === 'undefined') { CpsResult = function(r) { this.r = r; }; } if (typeof CpsRun === 'undefined') { CpsRun = function(x) { var last_k; while (x instanceof CpsFunction) { last_k = x.k; x = x.f(x.k); } if (x instanceof CpsResult) { return x.r; } else { return last_k.k(x); }}; }"); | ||
| assert.equal(code.type, 'Program'); | ||
| return code.body; | ||
| var ast = root.parse("if (typeof CpsFunction === 'undefined') { CpsFunction = function(f, k) { this.f = f; this.k = k; }; } if (typeof CpsContinuation === 'undefined') { CpsContinuation = function(k) { if (k) { this.k = k; } else { this.k = function(r) { return r; }; }}; } if (typeof CpsResult === 'undefined') { CpsResult = function(r) { this.r = r; }; } if (typeof CpsRun === 'undefined') { CpsRun = function(x) { var last_k; while (x instanceof CpsFunction) { last_k = x.k; x = x.f(x.k); } if (x instanceof CpsResult) { return x.r; } else { return last_k.k(x); }}; }"); | ||
| assert.equal(ast.type, 'Program'); | ||
| return ast.body; | ||
| }; | ||
@@ -83,32 +83,31 @@ | ||
| var i_varname = root.generate_new_variable_name('i', exclude_ids); | ||
| var thisCopy = ''; | ||
| var code = "var " + l_varname + " = arguments.length - 1;"; | ||
| if (t_varname) { | ||
| thisCopy = "var " + t_varname + " = this;"; | ||
| code += "var " + t_varname + " = this;"; | ||
| } | ||
| var argsCopy = ''; | ||
| if (a_varname) { | ||
| argsCopy = "var " + a_varname + " = {}; for(var " + i_varname + " = 0; " + i_varname + " <= " + l_varname + "; " + i_varname + "++) { " + a_varname + "[" + i_varname + "] = arguments[" + i_varname + "]; }" + a_varname + ".length = 1 + " + l_varname + ";" + a_varname + ".callee = arguments.callee;"; | ||
| code += "var " + a_varname + " = {}; for(var " + i_varname + " = 0; " + i_varname + " <= " + l_varname + "; " + i_varname + "++) { " + a_varname + "[" + i_varname + "] = arguments[" + i_varname + "]; }" + a_varname + ".length = 1 + " + l_varname + ";" + a_varname + ".callee = arguments.callee;"; | ||
| } | ||
| var code = root.parse("var " + l_varname + " = arguments.length - 1;" + thisCopy + argsCopy); | ||
| assert.equal(code.type, 'Program'); | ||
| return code.body; | ||
| var ast = root.parse(code); | ||
| assert.equal(ast.type, 'Program'); | ||
| return ast.body; | ||
| }; | ||
| root.ast_func_wrapper = function(k_varname, l_varname, a_varname, params) { | ||
| var argsPop = ''; | ||
| var code = "var " + k_varname + " = arguments[" + l_varname + "]; if (" + k_varname + " instanceof CpsContinuation) { "; | ||
| if (a_varname) { | ||
| argsPop = "delete " + a_varname + "[" + l_varname + "]; " + a_varname + ".length--;"; | ||
| code += "delete " + a_varname + "[" + l_varname + "]; " + a_varname + ".length--;"; | ||
| } | ||
| var fixParams = ''; | ||
| if (params.length > 0) { | ||
| fixParams = 'if (' + l_varname + ' >= ' + params.length + ') {'; | ||
| code += "if (" + l_varname + " >= " + params.length + ") {"; | ||
| for (var i = params.length - 1; i >= 0; i--) { | ||
| assert.equal(params[i].type, 'Identifier'); | ||
| fixParams += '} else if (' + l_varname + ' >= ' + i + ') {' + params[i].name + ' = undefined;'; | ||
| code += "} else if (" + l_varname + " >= " + i + ") {" + params[i].name + " = undefined;"; | ||
| } | ||
| fixParams += '}'; | ||
| code += "}"; | ||
| } | ||
| var code = root.parse("var " + k_varname + " = arguments[" + l_varname + "]; if (" + k_varname + " instanceof CpsContinuation) { " + argsPop + fixParams + "}"); | ||
| assert.equal(code.type, 'Program'); | ||
| return code.body; | ||
| code += "}"; | ||
| var ast = root.parse(code); | ||
| assert.equal(ast.type, 'Program'); | ||
| return ast.body; | ||
| }; | ||
@@ -198,9 +197,9 @@ | ||
| var header = root.ast_func_header(l_varname, using_this_var && t_varname, using_arguments && a_varname, exclude_ids); | ||
| var newbody = root.deep_clone(body.body); | ||
| var newbody = root.deep_clone(body); | ||
| root.convert_function_call_to_new_cps_call(body.body, exclude_ids); | ||
| var success = root.convert_normal_body_to_cps_body(k_varname, exclude_ids, newbody); | ||
| if (success) { | ||
| while (newbody.length > 0) { | ||
| if (newbody[0].type === 'FunctionDeclaration') { | ||
| newbody.shift(); | ||
| while (newbody.body.length > 0) { | ||
| if (newbody.body[0].type === 'FunctionDeclaration') { | ||
| newbody.body.shift(); | ||
| } else { | ||
@@ -212,3 +211,3 @@ break; | ||
| assert.ok(wrapper[1].consequent.body.length >= 0); | ||
| root.push(wrapper[1].consequent.body, newbody); | ||
| root.push(wrapper[1].consequent.body, newbody.body); | ||
| root.push(wrapper[1].consequent.body, { | ||
@@ -229,26 +228,27 @@ type: 'ReturnStatement', | ||
| root.has_side_effect = function(node) { | ||
| if (!node) { | ||
| return false; | ||
| } else if (node.type === 'FunctionExpression') { //not really side-effect | ||
| return true; | ||
| } else if (node.type === 'CallExpression') { | ||
| return true; | ||
| } else if (node.type === 'UpdateExpression') { | ||
| return true; | ||
| } else if (node.type === 'AssignmentExpression') { | ||
| return true; | ||
| } else if (node.type === 'NewExpression') { | ||
| return true; | ||
| } else if (node instanceof Object) { | ||
| return _.some(node, root.has_side_effect); | ||
| } else { | ||
| return false; | ||
| } | ||
| }; | ||
| root.convert_function_call_to_new_cps_call = function(body, exclude_ids) { | ||
| var has_side_effect = function(node) { | ||
| if (!node) { | ||
| return false; | ||
| } else if (node.type === 'FunctionExpression') { //not really side-effect | ||
| return true; | ||
| } else if (node.type === 'CallExpression') { | ||
| return true; | ||
| } else if (node.type === 'UpdateExpression') { | ||
| return true; | ||
| } else if (node.type === 'AssignmentExpression') { | ||
| return true; | ||
| } else if (node.type === 'NewExpression') { | ||
| return true; | ||
| } else if (node instanceof Object) { | ||
| return _.some(node, has_side_effect); | ||
| } else { | ||
| return false; | ||
| } | ||
| }; | ||
| var is_transformable_call = function(node) { | ||
| if (!node.callee) { | ||
| return false; | ||
| } else if (has_side_effect(node.callee)) { | ||
| } else if (root.has_side_effect(node.callee)) { | ||
| return false; | ||
@@ -341,31 +341,7 @@ } else if (node.callee.type === 'Identifier' && node.callee.name === 'CpsEnableWrapper') { | ||
| root.convert_statements_into_cps = function(k_varname, exclude_ids, statements) { | ||
| var i = 0; | ||
| while (i < statements.length) { | ||
| assert.ok(statements[i].type); | ||
| assert.equal(statements[i].type.slice(-9), 'Statement'); | ||
| var converted = root.convert_statement_into_cps(k_varname, exclude_ids, statements[i], statements.slice(i + 1)); | ||
| if (converted) { | ||
| statements.splice(i + 1); | ||
| statements[i] = { | ||
| type: 'ReturnStatement', | ||
| argument: converted | ||
| }; | ||
| break; | ||
| } | ||
| } | ||
| }; | ||
| root.convert_statement_into_cps = function(k_varname, exlude_ids, statement, rest) { | ||
| if (statement.type === 'ExpressionStatement') { | ||
| //TODO | ||
| } else { | ||
| return false; | ||
| } | ||
| }; | ||
| // only converting obvious tail calls or return calls to cps | ||
| // only converting tail calls | ||
| root.convert_normal_body_to_cps_body = function(k_varname, exclude_ids, body) { | ||
| var create_cps_expression = function(call_expression) { | ||
| var kk_varname = root.generate_new_variable_name('kk', exclude_ids); | ||
| var call_expression1 = root.deep_clone(call_expression); | ||
| var call_expression2 = root.deep_clone(call_expression); | ||
@@ -399,3 +375,3 @@ call_expression2.arguments.push({ | ||
| computed: false, | ||
| object: call_expression.callee, | ||
| object: call_expression1.callee, | ||
| property: { | ||
@@ -407,3 +383,3 @@ type: 'Identifier', | ||
| consequent: call_expression2, | ||
| alternate: call_expression | ||
| alternate: call_expression1 | ||
@@ -422,75 +398,162 @@ } | ||
| }; | ||
| var walk = function(node, tail) { | ||
| if (node && (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) { | ||
| return true; | ||
| } else if (node && node.type === 'CallExpression' && !(node.callee && node.callee.id && node.callee.id.type === 'Identifier' && node.callee.id.name === 'CpsEnableWrapper')) { | ||
| return false; | ||
| } else if (node && node.type === 'ReturnStatement') { | ||
| if (node.argument && node.argument.type === 'CallExpression' && !(node.argument.callee && node.argument.callee.type === 'FunctionExpression')) { | ||
| node.argument = create_cps_expression(node.argument); | ||
| return true; | ||
| var create_cps_result = function(expression) { | ||
| return { | ||
| type: 'NewExpression', | ||
| callee: { | ||
| type: 'Identifier', | ||
| name: 'CpsResult' | ||
| }, | ||
| arguments: [{ | ||
| type: 'CallExpression', | ||
| callee: { | ||
| type: 'MemberExpression', | ||
| computed: false, | ||
| object: { | ||
| type: 'Identifier', | ||
| name: k_varname | ||
| }, | ||
| property: { | ||
| type: 'Identifier', | ||
| name: 'k' | ||
| } | ||
| }, | ||
| arguments: [expression || { | ||
| type: 'Literal', | ||
| value: null | ||
| }] | ||
| }] | ||
| }; | ||
| }; | ||
| var is_callee_cpsenablewrapper = function(node) { | ||
| return node.callee && node.callee.id && node.callee.id.type === 'Identifier' && node.callee.id.name === 'CpsEnableWrapper'; | ||
| }; | ||
| // the walk function below returns the number of CpsFunction transformation. | ||
| var walk = function(node, tail, wrapped) { | ||
| var transformed; | ||
| var i; | ||
| if (node === undefined || node === null) { | ||
| return 0; | ||
| } else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') { | ||
| return 0; | ||
| } else if (node.type === 'CallExpression') { | ||
| if (tail && !wrapped && !is_callee_cpsenablewrapper(node) && !root.has_side_effect(node.callee)) { | ||
| var newnode = create_cps_expression(node); | ||
| _.each(node, function(value, key) { | ||
| delete node[key]; | ||
| }); | ||
| _.extend(node, newnode); | ||
| return 1; | ||
| } else { | ||
| var success = walk(node.argument); | ||
| if (success) { | ||
| node.argument = { | ||
| type: 'NewExpression', | ||
| callee: { | ||
| type: 'Identifier', | ||
| name: 'CpsResult' | ||
| }, | ||
| arguments: [{ | ||
| type: 'CallExpression', | ||
| callee: { | ||
| type: 'MemberExpression', | ||
| computed: false, | ||
| object: { | ||
| type: 'Identifier', | ||
| name: k_varname | ||
| }, | ||
| property: { | ||
| type: 'Identifier', | ||
| name: 'k' | ||
| } | ||
| }, | ||
| arguments: [node.argument || { | ||
| type: 'Literal', | ||
| value: null | ||
| }] | ||
| }] | ||
| }; | ||
| return true; | ||
| } else { | ||
| return false; | ||
| } | ||
| return 0; | ||
| } | ||
| } else if (tail && node && node.type === 'IfStatement') { | ||
| return walk(node.consequent, tail) && walk(node.alternate, tail); | ||
| } else if (tail && node && node.type === 'BlockStatement') { | ||
| return walk(node.body, tail); | ||
| } else if (tail && Array.isArray(node)) { | ||
| for (var i = 0; i < node.length - 1; i++) { | ||
| var result = walk(node[i]); | ||
| if (!result) { | ||
| return false; | ||
| } | ||
| } else if (node.type === 'ConditionalExpression') { | ||
| return walk(node.test, false, wrapped) + walk(node.consequent, tail, wrapped) + walk(node.alternate, tail, wrapped); | ||
| } else if (node.type === 'SequenceExpression') { | ||
| transformed = 0; | ||
| for (i = 0; i < node.expressions.length; i++) { | ||
| transformed += walk(node.expressions[i], false, wrapped); | ||
| } | ||
| var lastone = node[node.length - 1]; | ||
| if (lastone && lastone.type === 'ExpressionStatement' && lastone.expression.type === 'CallExpression' && !(lastone.expression.callee && lastone.expression.callee.type === 'FunctionExpression')) { | ||
| node[node.length - 1] = { | ||
| type: 'ReturnStatement', | ||
| argument: create_cps_expression(lastone.expression) | ||
| }; | ||
| return true; | ||
| } else { | ||
| return walk(lastone, tail); | ||
| return transformed; | ||
| } else if (node.type === 'AssignmentExpression' || node.type === 'BinaryExpression' || node.type === 'UpdateExpression' || node.type === 'MemberExpression' || node.type === 'LogicalExpression' || node.type === 'ArrayExpression' || node.type === 'ObjectExpression' || node.type === 'UnaryExpression' || node.type === 'NewExpression') { | ||
| return 0; | ||
| } else if (node.type === 'BlockStatement') { | ||
| transformed = 0; | ||
| for (i = 0; i < node.body.length; i++) { | ||
| transformed += walk(node.body[i], (i === node.body.length - 1 ? tail : false), wrapped); | ||
| } | ||
| } else if (node instanceof Object) { | ||
| return _.every(node, function(x) { | ||
| return walk(x); | ||
| }); | ||
| return transformed; | ||
| } else if (node.type === 'ExpressionStatement') { | ||
| transformed = walk(node.expression, tail, wrapped); | ||
| if (transformed) { | ||
| node.type = 'ReturnStatement'; | ||
| node.argument = node.expression; | ||
| delete node.expression; | ||
| } | ||
| return transformed; | ||
| } else if (node.type === 'DoWhileStatement' || node.type === 'WhileStatement') { | ||
| return walk(node.body, false, wrapped) + walk(node.test, false, wrapped); | ||
| } else if (node.type === 'ForStatement') { | ||
| return walk(node.body, false, wrapped) + walk(node.init, false, wrapped) + walk(node.test, false, wrapped) + walk(node.update, false, wrapped); | ||
| } else if (node.type === 'ForInStatement') { | ||
| return walk(node.body, false, wrapped) + walk(node.left, false, wrapped) + walk(node.right, false, wrapped); | ||
| } else if (node.type === 'IfStatement') { | ||
| return walk(node.consequent, tail, wrapped) + walk(node.alternate, tail, wrapped); | ||
| } else if (node.type === 'LabeledStatement') { | ||
| return walk(node.body, tail, wrapped); | ||
| } else if (node.type === 'WithStatement') { | ||
| return walk(node.body, tail, wrapped) + walk(node.object, false, wrapped); | ||
| } else if (node.type === 'ReturnStatement') { | ||
| transformed = walk(node.argument, !wrapped, false); | ||
| if (transformed === 0) { | ||
| node.argument = create_cps_result(node.argument); | ||
| } | ||
| return transformed; | ||
| } else if (node.type === 'TryStatement') { | ||
| transformed = walk(node.block, tail, true); | ||
| for (i = 0; i < node.guardedHandlers.length; i++) { | ||
| transformed += walk(node.guardedHandlers[i].body, tail, true); | ||
| } | ||
| for (i = 0; i < node.handlers.length; i++) { | ||
| transformed += walk(node.handlers[i].body, tail, true); | ||
| } | ||
| transformed += walk(node.finalizer, tail, wrapped); | ||
| return transformed; | ||
| } else if (node.type === 'ThrowStatement') { | ||
| return walk(node.argument, false, wrapped); | ||
| } else if (node.type === 'SwitchStatement') { | ||
| transformed = walk(node.discriminant, false, wrapped); | ||
| for (i = 0; i < node.cases.length; i++) { | ||
| transformed += walk(node.cases[i], false, wrapped); | ||
| } | ||
| return transformed; | ||
| } else if (node.type === 'SwitchCase') { | ||
| transformed = walk(node.test, false, wrapped); | ||
| for (i = 0; i < node.consequent.length; i++) { | ||
| transformed += walk(node.consequent[i], false, wrapped); | ||
| } | ||
| return transformed; | ||
| } else if (node.type === 'BreakStatement' || node.type === 'ContinueStatement' || node.type === 'EmptyStatement') { | ||
| return 0; | ||
| } else if (node.type === 'VariableDeclaration') { | ||
| transformed = 0; | ||
| for (i = 0; i < node.declarations.length; i++) { | ||
| transformed += walk(node.declarations[i].init, false, wrapped); | ||
| } | ||
| return transformed; | ||
| } else if (node.type === 'Identifier') { | ||
| return 0; | ||
| } else if (node.type === 'Literal') { | ||
| return 0; | ||
| } else { | ||
| return true; | ||
| console.warn('continuing with unsupported node type: ' + node.type); | ||
| return 0; | ||
| } | ||
| }; | ||
| return walk(body, true); | ||
| return walk(body, true, false); | ||
| }; | ||
@@ -620,17 +683,20 @@ | ||
| root.saved_require_js_function = null; | ||
| root.enable_on_require = function() { | ||
| var fs = require('fs'); | ||
| require.extensions['.js'] = function(module, filename) { | ||
| var data = fs.readFileSync(filename, 'utf8'); | ||
| module._compile(root.compile(data), filename); | ||
| }; | ||
| if (!root.saved_require_js_function) { | ||
| root.saved_require_js_function = require.extensions['.js']; | ||
| require.extensions['.js'] = function(module, filename) { | ||
| var data = fs.readFileSync(filename, 'utf8'); | ||
| module._compile(root.compile(data), filename); | ||
| }; | ||
| } | ||
| }; | ||
| //XXX not sure if this is really identical to the original function. | ||
| root.disable_on_require = function() { | ||
| var fs = require('fs'); | ||
| require.extensions['.js'] = function(module, filename) { | ||
| var data = fs.readFileSync(filename, 'utf8'); | ||
| module._compile(data, filename); | ||
| }; | ||
| if (root.saved_require_js_function) { | ||
| require.extensions['.js'] = root.saved_require_js_function; | ||
| root.saved_require_js_function = null; | ||
| } | ||
| }; | ||
@@ -637,0 +703,0 @@ |
Sorry, the diff of this file is too big to display
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
162234
43.87%2945
28.27%8
-20%167
-0.6%