gpu.js
Version:
GPU Accelerated JavaScript
668 lines (611 loc) • 19.3 kB
JavaScript
const { FunctionNode } = require('../function-node');
/**
* @desc [INTERNAL] Represents a single function, inside JS
*
* <p>This handles all the raw state, converted state, etc. Of a single function.</p>
*/
class CPUFunctionNode extends FunctionNode {
/**
* @desc Parses the abstract syntax tree for to its *named function*
* @param {Object} ast - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astFunction(ast, retArr) {
// Setup function return type and name
if (!this.isRootKernel) {
retArr.push('function');
retArr.push(' ');
retArr.push(this.name);
retArr.push('(');
// Arguments handling
for (let i = 0; i < this.argumentNames.length; ++i) {
const argumentName = this.argumentNames[i];
if (i > 0) {
retArr.push(', ');
}
retArr.push('user_');
retArr.push(argumentName);
}
// Function opening
retArr.push(') {\n');
}
// Body statement iteration
for (let i = 0; i < ast.body.body.length; ++i) {
this.astGeneric(ast.body.body[i], retArr);
retArr.push('\n');
}
if (!this.isRootKernel) {
// Function closing
retArr.push('}\n');
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for to *return* statement
* @param {Object} ast - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astReturnStatement(ast, retArr) {
const type = this.returnType || this.getType(ast.argument);
if (!this.returnType) {
this.returnType = type;
}
if (this.isRootKernel) {
retArr.push(this.leadingReturnStatement);
this.astGeneric(ast.argument, retArr);
retArr.push(';\n');
retArr.push(this.followingReturnStatement);
retArr.push('continue;\n');
} else if (this.isSubKernel) {
retArr.push(`subKernelResult_${ this.name } = `);
this.astGeneric(ast.argument, retArr);
retArr.push(';');
retArr.push(`return subKernelResult_${ this.name };`);
} else {
retArr.push('return ');
this.astGeneric(ast.argument, retArr);
retArr.push(';');
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *literal value*
* @param {Object} ast - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astLiteral(ast, retArr) {
// Reject non numeric literals
if (isNaN(ast.value)) {
throw this.astErrorOutput(
'Non-numeric literal not supported : ' + ast.value,
ast
);
}
retArr.push(ast.value);
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *binary* expression
* @param {Object} ast - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astBinaryExpression(ast, retArr) {
retArr.push('(');
this.astGeneric(ast.left, retArr);
retArr.push(ast.operator);
this.astGeneric(ast.right, retArr);
retArr.push(')');
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *identifier* expression
* @param {Object} idtNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astIdentifierExpression(idtNode, retArr) {
if (idtNode.type !== 'Identifier') {
throw this.astErrorOutput(
'IdentifierExpression - not an Identifier',
idtNode
);
}
switch (idtNode.name) {
case 'Infinity':
retArr.push('Infinity');
break;
default:
if (this.constants && this.constants.hasOwnProperty(idtNode.name)) {
retArr.push('constants_' + idtNode.name);
} else {
retArr.push('user_' + idtNode.name);
}
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *for-loop* expression
* @param {Object} forNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the parsed webgl string
*/
astForStatement(forNode, retArr) {
if (forNode.type !== 'ForStatement') {
throw this.astErrorOutput('Invalid for statement', forNode);
}
const initArr = [];
const testArr = [];
const updateArr = [];
const bodyArr = [];
let isSafe = null;
if (forNode.init) {
this.pushState('in-for-loop-init');
this.astGeneric(forNode.init, initArr);
for (let i = 0; i < initArr.length; i++) {
if (initArr[i].includes && initArr[i].includes(',')) {
isSafe = false;
}
}
this.popState('in-for-loop-init');
} else {
isSafe = false;
}
if (forNode.test) {
this.astGeneric(forNode.test, testArr);
} else {
isSafe = false;
}
if (forNode.update) {
this.astGeneric(forNode.update, updateArr);
} else {
isSafe = false;
}
if (forNode.body) {
this.pushState('loop-body');
this.astGeneric(forNode.body, bodyArr);
this.popState('loop-body');
}
// have all parts, now make them safe
if (isSafe === null) {
isSafe = this.isSafe(forNode.init) && this.isSafe(forNode.test);
}
if (isSafe) {
retArr.push(`for (${initArr.join('')};${testArr.join('')};${updateArr.join('')}){\n`);
retArr.push(bodyArr.join(''));
retArr.push('}\n');
} else {
const iVariableName = this.getInternalVariableName('safeI');
if (initArr.length > 0) {
retArr.push(initArr.join(''), ';\n');
}
retArr.push(`for (let ${iVariableName}=0;${iVariableName}<LOOP_MAX;${iVariableName}++){\n`);
if (testArr.length > 0) {
retArr.push(`if (!${testArr.join('')}) break;\n`);
}
retArr.push(bodyArr.join(''));
retArr.push(`\n${updateArr.join('')};`);
retArr.push('}\n');
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *while* loop
* @param {Object} whileNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the parsed javascript string
*/
astWhileStatement(whileNode, retArr) {
if (whileNode.type !== 'WhileStatement') {
throw this.astErrorOutput(
'Invalid while statement',
whileNode
);
}
retArr.push('for (let i = 0; i < LOOP_MAX; i++) {');
retArr.push('if (');
this.astGeneric(whileNode.test, retArr);
retArr.push(') {\n');
this.astGeneric(whileNode.body, retArr);
retArr.push('} else {\n');
retArr.push('break;\n');
retArr.push('}\n');
retArr.push('}\n');
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *do while* loop
* @param {Object} doWhileNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the parsed webgl string
*/
astDoWhileStatement(doWhileNode, retArr) {
if (doWhileNode.type !== 'DoWhileStatement') {
throw this.astErrorOutput(
'Invalid while statement',
doWhileNode
);
}
retArr.push('for (let i = 0; i < LOOP_MAX; i++) {');
this.astGeneric(doWhileNode.body, retArr);
retArr.push('if (!');
this.astGeneric(doWhileNode.test, retArr);
retArr.push(') {\n');
retArr.push('break;\n');
retArr.push('}\n');
retArr.push('}\n');
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *Assignment* Expression
* @param {Object} assNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astAssignmentExpression(assNode, retArr) {
const declaration = this.getDeclaration(assNode.left);
if (declaration && !declaration.assignable) {
throw this.astErrorOutput(`Variable ${assNode.left.name} is not assignable here`, assNode);
}
this.astGeneric(assNode.left, retArr);
retArr.push(assNode.operator);
this.astGeneric(assNode.right, retArr);
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *Block* statement
* @param {Object} bNode - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astBlockStatement(bNode, retArr) {
if (this.isState('loop-body')) {
this.pushState('block-body'); // this prevents recursive removal of braces
for (let i = 0; i < bNode.body.length; i++) {
this.astGeneric(bNode.body[i], retArr);
}
this.popState('block-body');
} else {
retArr.push('{\n');
for (let i = 0; i < bNode.body.length; i++) {
this.astGeneric(bNode.body[i], retArr);
}
retArr.push('}\n');
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *Variable Declaration*
* @param {Object} varDecNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astVariableDeclaration(varDecNode, retArr) {
retArr.push(`${varDecNode.kind} `);
const { declarations } = varDecNode;
for (let i = 0; i < declarations.length; i++) {
if (i > 0) {
retArr.push(',');
}
const declaration = declarations[i];
const info = this.getDeclaration(declaration.id);
if (!info.valueType) {
info.valueType = this.getType(declaration.init);
}
this.astGeneric(declaration, retArr);
}
if (!this.isState('in-for-loop-init')) {
retArr.push(';');
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *If* Statement
* @param {Object} ifNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astIfStatement(ifNode, retArr) {
retArr.push('if (');
this.astGeneric(ifNode.test, retArr);
retArr.push(')');
if (ifNode.consequent.type === 'BlockStatement') {
this.astGeneric(ifNode.consequent, retArr);
} else {
retArr.push(' {\n');
this.astGeneric(ifNode.consequent, retArr);
retArr.push('\n}\n');
}
if (ifNode.alternate) {
retArr.push('else ');
if (ifNode.alternate.type === 'BlockStatement' || ifNode.alternate.type === 'IfStatement') {
this.astGeneric(ifNode.alternate, retArr);
} else {
retArr.push(' {\n');
this.astGeneric(ifNode.alternate, retArr);
retArr.push('\n}\n');
}
}
return retArr;
}
astSwitchStatement(ast, retArr) {
const { discriminant, cases } = ast;
retArr.push('switch (');
this.astGeneric(discriminant, retArr);
retArr.push(') {\n');
for (let i = 0; i < cases.length; i++) {
if (cases[i].test === null) {
retArr.push('default:\n');
this.astGeneric(cases[i].consequent, retArr);
if (cases[i].consequent && cases[i].consequent.length > 0) {
retArr.push('break;\n');
}
continue;
}
retArr.push('case ');
this.astGeneric(cases[i].test, retArr);
retArr.push(':\n');
if (cases[i].consequent && cases[i].consequent.length > 0) {
this.astGeneric(cases[i].consequent, retArr);
retArr.push('break;\n');
}
}
retArr.push('\n}');
}
/**
* @desc Parses the abstract syntax tree for *This* expression
* @param {Object} tNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astThisExpression(tNode, retArr) {
retArr.push('_this');
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *Member* Expression
* @param {Object} mNode - An ast Node
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astMemberExpression(mNode, retArr) {
const {
signature,
type,
property,
xProperty,
yProperty,
zProperty,
name,
origin
} = this.getMemberExpressionDetails(mNode);
switch (signature) {
case 'this.thread.value':
retArr.push(`_this.thread.${ name }`);
return retArr;
case 'this.output.value':
switch (name) {
case 'x':
retArr.push('outputX');
break;
case 'y':
retArr.push('outputY');
break;
case 'z':
retArr.push('outputZ');
break;
default:
throw this.astErrorOutput('Unexpected expression', mNode);
}
return retArr;
case 'value':
throw this.astErrorOutput('Unexpected expression', mNode);
case 'value[]':
case 'value[][]':
case 'value[][][]':
case 'value.value':
if (origin === 'Math') {
retArr.push(Math[name]);
return retArr;
}
switch (property) {
case 'r':
retArr.push(`user_${ name }[0]`);
return retArr;
case 'g':
retArr.push(`user_${ name }[1]`);
return retArr;
case 'b':
retArr.push(`user_${ name }[2]`);
return retArr;
case 'a':
retArr.push(`user_${ name }[3]`);
return retArr;
}
break;
case 'this.constants.value':
case 'this.constants.value[]':
case 'this.constants.value[][]':
case 'this.constants.value[][][]':
break;
case 'fn()[]':
this.astGeneric(mNode.object, retArr);
retArr.push('[');
this.astGeneric(mNode.property, retArr);
retArr.push(']');
return retArr;
case 'fn()[][]':
this.astGeneric(mNode.object.object, retArr);
retArr.push('[');
this.astGeneric(mNode.object.property, retArr);
retArr.push(']');
retArr.push('[');
this.astGeneric(mNode.property, retArr);
retArr.push(']');
return retArr;
default:
throw this.astErrorOutput('Unexpected expression', mNode);
}
if (!mNode.computed) {
// handle simple types
switch (type) {
case 'Number':
case 'Integer':
case 'Float':
case 'Boolean':
retArr.push(`${origin}_${name}`);
return retArr;
}
}
// handle more complex types
// argument may have come from a parent
const markupName = `${origin}_${name}`;
switch (type) {
case 'Array(2)':
case 'Array(3)':
case 'Array(4)':
case 'Matrix(2)':
case 'Matrix(3)':
case 'Matrix(4)':
case 'HTMLImageArray':
case 'ArrayTexture(1)':
case 'ArrayTexture(2)':
case 'ArrayTexture(3)':
case 'ArrayTexture(4)':
case 'HTMLImage':
default:
let size;
let isInput;
if (origin === 'constants') {
const constant = this.constants[name];
isInput = this.constantTypes[name] === 'Input';
size = isInput ? constant.size : null;
} else {
isInput = this.isInput(name);
size = isInput ? this.argumentSizes[this.argumentNames.indexOf(name)] : null;
}
retArr.push(`${ markupName }`);
if (zProperty && yProperty) {
if (isInput) {
retArr.push('[(');
this.astGeneric(zProperty, retArr);
retArr.push(`*${ this.dynamicArguments ? '(outputY * outputX)' : size[1] * size[0] })+(`);
this.astGeneric(yProperty, retArr);
retArr.push(`*${ this.dynamicArguments ? 'outputX' : size[0] })+`);
this.astGeneric(xProperty, retArr);
retArr.push(']');
} else {
retArr.push('[');
this.astGeneric(zProperty, retArr);
retArr.push(']');
retArr.push('[');
this.astGeneric(yProperty, retArr);
retArr.push(']');
retArr.push('[');
this.astGeneric(xProperty, retArr);
retArr.push(']');
}
} else if (yProperty) {
if (isInput) {
retArr.push('[(');
this.astGeneric(yProperty, retArr);
retArr.push(`*${ this.dynamicArguments ? 'outputX' : size[0] })+`);
this.astGeneric(xProperty, retArr);
retArr.push(']');
} else {
retArr.push('[');
this.astGeneric(yProperty, retArr);
retArr.push(']');
retArr.push('[');
this.astGeneric(xProperty, retArr);
retArr.push(']');
}
} else if (typeof xProperty !== 'undefined') {
retArr.push('[');
this.astGeneric(xProperty, retArr);
retArr.push(']');
}
}
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *call* expression
* @param {Object} ast - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astCallExpression(ast, retArr) {
if (ast.type !== 'CallExpression') {
// Failure, unknown expression
throw this.astErrorOutput('Unknown CallExpression', ast);
}
// Get the full function call, unrolled
let functionName = this.astMemberExpressionUnroll(ast.callee);
// Register the function into the called registry
if (this.calledFunctions.indexOf(functionName) < 0) {
this.calledFunctions.push(functionName);
}
const isMathFunction = this.isAstMathFunction(ast);
// track the function was called
if (this.onFunctionCall) {
this.onFunctionCall(this.name, functionName, ast.arguments);
}
// Call the function
retArr.push(functionName);
// Open arguments space
retArr.push('(');
const targetTypes = this.lookupFunctionArgumentTypes(functionName) || [];
// Add the arguments
for (let i = 0; i < ast.arguments.length; ++i) {
const argument = ast.arguments[i];
// in order to track return type, even though this is CPU
let argumentType = this.getType(argument);
if (!targetTypes[i]) {
this.triggerImplyArgumentType(functionName, i, argumentType, this);
}
if (i > 0) {
retArr.push(', ');
}
this.astGeneric(argument, retArr);
}
// Close arguments space
retArr.push(')');
return retArr;
}
/**
* @desc Parses the abstract syntax tree for *Array* Expression
* @param {Object} arrNode - the AST object to parse
* @param {Array} retArr - return array string
* @returns {Array} the append retArr
*/
astArrayExpression(arrNode, retArr) {
const returnType = this.getType(arrNode);
const arrLen = arrNode.elements.length;
const elements = [];
for (let i = 0; i < arrLen; ++i) {
const element = [];
this.astGeneric(arrNode.elements[i], element);
elements.push(element.join(''));
}
switch (returnType) {
case 'Matrix(2)':
case 'Matrix(3)':
case 'Matrix(4)':
retArr.push(`[${elements.join(', ')}]`);
break;
default:
retArr.push(`new Float32Array([${elements.join(', ')}])`);
}
return retArr;
}
astDebuggerStatement(arrNode, retArr) {
retArr.push('debugger;');
return retArr;
}
}
module.exports = {
CPUFunctionNode
};