Skip to content

Commit 7bcfbed

Browse files
feat: Pre type check with FunctionTracer
feat: Add cat demo feat: Type check even on CPU fix: ArrayTexture support as arguments for internal arrays fix: Typo from "Interger" to "Integer" feat: Bump and build version number
1 parent 3361ec8 commit 7bcfbed

33 files changed

+3396
-1280
lines changed

bin/gpu-browser-core.js

Lines changed: 610 additions & 254 deletions
Large diffs are not rendered by default.

bin/gpu-browser-core.min.js

Lines changed: 612 additions & 256 deletions
Large diffs are not rendered by default.

bin/gpu-browser.js

Lines changed: 610 additions & 254 deletions
Large diffs are not rendered by default.

bin/gpu-browser.min.js

Lines changed: 612 additions & 256 deletions
Large diffs are not rendered by default.

examples/cat-image/cat.jpg

40.1 KB
Loading

examples/cat-image/index.html

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Cat image with GPU.js</title>
6+
<script src="../../bin/gpu-browser.js"></script>
7+
</head>
8+
<body>
9+
<h1>Image to GPU.js from <a href="https://observablehq.com/@fil/image-to-gpu">https://observablehq.com/@fil/image-to-gpu</a></h1>
10+
<div id="log-fps"></div>
11+
</body>
12+
<script>
13+
const gpu = new GPU();
14+
function imageToArray(image) {
15+
const kernel = gpu.createKernel(function(image) {
16+
const pixel = image[this.thread.y][this.thread.x];
17+
this.color(pixel.r, pixel.g, pixel.b, pixel.a);
18+
}, {
19+
output: [image.width, image.height],
20+
graphical: true,
21+
pipeline: true,
22+
});
23+
kernel(image);
24+
const result = kernel.getPixels(true);
25+
kernel.destroy();
26+
return result;
27+
}
28+
const kernel = function(data, wobble) {
29+
var x = this.thread.x,
30+
y = this.thread.y;
31+
32+
//var data = this.constants.data;
33+
// wouldn't be fun if the kernel did _nothing_
34+
x = Math.floor(x + wobble * Math.sin(y / 10));
35+
y = Math.floor(y + wobble * Math.cos(x / 10));
36+
37+
var n = 4 * (x + (this.constants.w * y));
38+
this.color(data[n]/256, data[n+1]/256,data[n+2]/256,1);
39+
};
40+
41+
const logFps = document.querySelector('#log-fps');
42+
const image = new Image();
43+
image.src = './cat.jpg';
44+
image.onload = () => {
45+
const array = imageToArray(image);
46+
const render = (new GPU({mode: "gpu"}))
47+
.createKernel(kernel)
48+
.setConstants({ w: image.width, h: image.height })
49+
.setOutput([image.width, image.height])
50+
.setGraphical(true);
51+
const canvas = render.canvas;
52+
document.body.appendChild(canvas);
53+
var lastCalledTime;
54+
var fps;
55+
function callRender() {
56+
const wobble = 14 * Math.sin(Date.now() / 400);
57+
render(array, wobble);
58+
var delta = (Date.now() - lastCalledTime)/1000;
59+
lastCalledTime = Date.now();
60+
fps = 1/delta;
61+
logFps.innerHTML = fps.toFixed(0) + ' FPS';
62+
window.requestAnimationFrame(() => {
63+
callRender();
64+
});
65+
}
66+
callRender();
67+
};
68+
</script>
69+
</html>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gpu.js",
3-
"version": "2.0.0-rc.14",
3+
"version": "2.0.0-rc.15",
44
"description": "GPU Accelerated JavaScript",
55
"engines": {
66
"node": ">=10.0.0"

src/backend/cpu/function-node.js

Lines changed: 59 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class CPUFunctionNode extends FunctionNode {
1212
* @param {Array} retArr - return array string
1313
* @returns {Array} the append retArr
1414
*/
15-
astFunctionExpression(ast, retArr) {
15+
astFunction(ast, retArr) {
1616

1717
// Setup function return type and name
1818
if (!this.isRootKernel) {
@@ -56,6 +56,12 @@ class CPUFunctionNode extends FunctionNode {
5656
* @returns {Array} the append retArr
5757
*/
5858
astReturnStatement(ast, retArr) {
59+
const type = this.returnType || this.getType(ast.argument);
60+
61+
if (!this.returnType) {
62+
this.returnType = type;
63+
}
64+
5965
if (this.isRootKernel) {
6066
retArr.push(this.leadingReturnStatement);
6167
this.astGeneric(ast.argument, retArr);
@@ -279,6 +285,10 @@ class CPUFunctionNode extends FunctionNode {
279285
* @returns {Array} the append retArr
280286
*/
281287
astAssignmentExpression(assNode, retArr) {
288+
const declaration = this.getDeclaration(assNode.left);
289+
if (declaration && !declaration.assignable) {
290+
throw new this.astErrorOutput(`Variable ${assNode.left.name} is not assignable here`, assNode);
291+
}
282292
this.astGeneric(assNode.left, retArr);
283293
retArr.push(assNode.operator);
284294
this.astGeneric(assNode.right, retArr);
@@ -319,21 +329,12 @@ class CPUFunctionNode extends FunctionNode {
319329
this.varWarn();
320330
}
321331
retArr.push(`${varDecNode.kind} `);
322-
const firstDeclaration = varDecNode.declarations[0];
323-
const type = this.getType(firstDeclaration.init);
324-
for (let i = 0; i < varDecNode.declarations.length; i++) {
325-
this.declarations[varDecNode.declarations[i].id.name] = {
326-
type: type === 'LiteralInteger' ? 'Number' : type,
327-
dependencies: {
328-
constants: [],
329-
arguments: []
330-
},
331-
isUnsafe: false
332-
};
332+
const { declarations } = varDecNode;
333+
for (let i = 0; i < declarations.length; i++) {
333334
if (i > 0) {
334335
retArr.push(',');
335336
}
336-
this.astGeneric(varDecNode.declarations[i], retArr);
337+
this.astGeneric(declarations[i], retArr);
337338
}
338339
if (!this.isState('in-for-loop-init')) {
339340
retArr.push(';');
@@ -485,14 +486,16 @@ class CPUFunctionNode extends FunctionNode {
485486
throw this.astErrorOutput('Unexpected expression', mNode);
486487
}
487488

488-
// handle simple types
489-
switch (type) {
490-
case 'Number':
491-
case 'Integer':
492-
case 'Float':
493-
case 'Boolean':
494-
retArr.push(`${ origin }_${ name}`);
495-
return retArr;
489+
if (!mNode.computed) {
490+
// handle simple types
491+
switch (type) {
492+
case 'Number':
493+
case 'Integer':
494+
case 'Float':
495+
case 'Boolean':
496+
retArr.push(`${origin}_${name}`);
497+
return retArr;
498+
}
496499
}
497500

498501
// handle more complex types
@@ -573,56 +576,50 @@ class CPUFunctionNode extends FunctionNode {
573576
* @returns {Array} the append retArr
574577
*/
575578
astCallExpression(ast, retArr) {
576-
if (ast.callee) {
577-
// Get the full function call, unrolled
578-
let funcName = this.astMemberExpressionUnroll(ast.callee);
579+
if (ast.type !== 'CallExpression') {
580+
// Failure, unknown expression
581+
throw this.astErrorOutput('Unknown CallExpression', ast);
582+
}
583+
// Get the full function call, unrolled
584+
let functionName = this.astMemberExpressionUnroll(ast.callee);
579585

580-
// Register the function into the called registry
581-
if (this.calledFunctions.indexOf(funcName) < 0) {
582-
this.calledFunctions.push(funcName);
583-
}
584-
if (!this.calledFunctionsArguments[funcName]) {
585-
this.calledFunctionsArguments[funcName] = [];
586-
}
586+
// Register the function into the called registry
587+
if (this.calledFunctions.indexOf(functionName) < 0) {
588+
this.calledFunctions.push(functionName);
589+
}
587590

588-
const functionArguments = [];
589-
this.calledFunctionsArguments[funcName].push(functionArguments);
591+
const isMathFunction = this.isAstMathFunction(ast);
590592

591-
// Call the function
592-
retArr.push(funcName);
593+
// track the function was called
594+
if (this.onFunctionCall) {
595+
this.onFunctionCall(this.name, functionName, ast.arguments);
596+
}
593597

594-
// Open arguments space
595-
retArr.push('(');
598+
// Call the function
599+
retArr.push(functionName);
596600

597-
// Add the vars
598-
for (let i = 0; i < ast.arguments.length; ++i) {
599-
const argument = ast.arguments[i];
600-
if (i > 0) {
601-
retArr.push(', ');
602-
}
603-
this.astGeneric(argument, retArr);
604-
const argumentType = this.getType(argument);
605-
if (argumentType) {
606-
functionArguments.push({
607-
name: argument.name || null,
608-
type: argumentType
609-
});
610-
} else {
611-
functionArguments.push(null);
612-
}
601+
// Open arguments space
602+
retArr.push('(');
603+
const targetTypes = this.lookupFunctionArgumentTypes(functionName) || [];
604+
// Add the arguments
605+
for (let i = 0; i < ast.arguments.length; ++i) {
606+
const argument = ast.arguments[i];
607+
608+
// in order to track return type, even though this is CPU
609+
let argumentType = this.getType(argument);
610+
if (!targetTypes[i]) {
611+
this.triggerImplyArgumentType(functionName, i, argumentType, this);
613612
}
614613

615-
// Close arguments space
616-
retArr.push(')');
617-
618-
return retArr;
614+
if (i > 0) {
615+
retArr.push(', ');
616+
}
617+
this.astGeneric(argument, retArr);
619618
}
619+
// Close arguments space
620+
retArr.push(')');
620621

621-
// Failure, unknown expression
622-
throw this.astErrorOutput(
623-
'Unknown CallExpression',
624-
ast
625-
);
622+
return retArr;
626623
}
627624

628625
/**

src/backend/function-builder.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,20 @@ class FunctionBuilder {
6969
return functionBuilder.lookupArgumentSynonym(originFunctionName, functionName, argumentName);
7070
};
7171

72-
const onFunctionCall = (functionName, calleeFunctionName) => {
73-
functionBuilder.trackFunctionCall(functionName, calleeFunctionName);
72+
const onFunctionCall = (functionName, calleeFunctionName, args) => {
73+
functionBuilder.trackFunctionCall(functionName, calleeFunctionName, args);
7474
};
7575

76-
const onNestedFunction = (fnString, returnType) => {
77-
functionBuilder.addFunctionNode(new FunctionNode(fnString, Object.assign({}, nodeOptions, {
78-
returnType: returnType,
76+
const onNestedFunction = (ast, returnType) => {
77+
const argumentNames = [];
78+
for (let i = 0; i < ast.params.length; i++) {
79+
argumentNames.push(ast.params[i].name);
80+
}
81+
const nestedFunction = new FunctionNode(null, Object.assign({}, nodeOptions, {
82+
returnType: null,
83+
ast,
84+
name: ast.id.name,
85+
argumentNames,
7986
lookupReturnType,
8087
lookupArgumentType,
8188
lookupFunctionArgumentTypes,
@@ -85,7 +92,9 @@ class FunctionBuilder {
8592
triggerTrackArgumentSynonym,
8693
lookupArgumentSynonym,
8794
onFunctionCall
88-
})));
95+
}));
96+
nestedFunction.traceFunctionAST(ast);
97+
functionBuilder.addFunctionNode(nestedFunction);
8998
};
9099

91100
const nodeOptions = Object.assign({
@@ -192,6 +201,7 @@ class FunctionBuilder {
192201
this.lookupChain = [];
193202
this.argumentChain = [];
194203
this.functionNodeDependencies = {};
204+
this.functionCalls = {};
195205

196206
if (this.rootNode) {
197207
this.functionMap['kernel'] = this.rootNode;
@@ -224,6 +234,7 @@ class FunctionBuilder {
224234
*
225235
*/
226236
addFunctionNode(functionNode) {
237+
if (!functionNode.name) throw new Error('functionNode.name needs set');
227238
this.functionMap[functionNode.name] = functionNode;
228239
if (functionNode.isRootKernel) {
229240
this.rootNode = functionNode;
@@ -551,11 +562,13 @@ class FunctionBuilder {
551562
return argumentSynonym.argumentName;
552563
}
553564

554-
trackFunctionCall(functionName, calleeFunctionName) {
565+
trackFunctionCall(functionName, calleeFunctionName, args) {
555566
if (!this.functionNodeDependencies[functionName]) {
556567
this.functionNodeDependencies[functionName] = new Set();
568+
this.functionCalls[functionName] = [];
557569
}
558570
this.functionNodeDependencies[functionName].add(calleeFunctionName);
571+
this.functionCalls[functionName].push(args);
559572
}
560573

561574
getKernelResultType() {
@@ -564,6 +577,16 @@ class FunctionBuilder {
564577

565578
getSubKernelResultType(index) {
566579
const subKernelNode = this.subKernelNodes[index];
580+
let called = false;
581+
for (let functionCallIndex = 0; functionCallIndex < this.rootNode.functionCalls.length; functionCallIndex++) {
582+
const functionCall = this.rootNode.functionCalls[functionCallIndex];
583+
if (functionCall.ast.callee.name === subKernelNode.name) {
584+
called = true;
585+
}
586+
}
587+
if (!called) {
588+
throw new Error(`SubKernel ${ subKernelNode.name } never called by kernel`);
589+
}
567590
return subKernelNode.returnType || subKernelNode.getType(subKernelNode.getJsAST());
568591
}
569592

0 commit comments

Comments
 (0)