How GPU.js Compiles JavaScript to GPU Pipelines
GPU.js is an acceleration library that transpiles standard JavaScript functions into parallel GPU compute pipelines using WebGL. This article explains the internal pipeline of GPU.js, detailing how it stringifies JavaScript functions, parses them into Abstract Syntax Trees (ASTs), transpiles dynamic JavaScript syntax into statically typed OpenGL Shading Language (GLSL), and configures WebGL textures, framebuffers, and shaders to execute computation across parallel GPU cores.
Function Stringification and AST Generation
The compilation process begins when a JavaScript function is passed
into the gpu.createKernel() method. Because JavaScript does
not expose raw bytecode or native AST representations of functions at
runtime, GPU.js extracts the function's source code by calling
Function.prototype.toString().
Once the source string is retrieved, GPU.js uses an embedded parser (historically Acorn) to convert the source text into an Abstract Syntax Tree (AST). The AST represents the syntactic structure of the code, breaking down expressions, assignments, variable declarations, loops, and conditional statements into structured nodes that the compiler can traverse.
Semantic Analysis and Type Inference
GLSL is a strictly typed language designed for graphics hardware, whereas JavaScript is dynamically typed. GPU.js traverses the AST to perform semantic analysis, infer variable types, and validate that the JavaScript syntax is compatible with GPU execution. Certain JavaScript features—such as recursion, string manipulation, object instantiations, and dynamic array resizing—cannot map to hardware-level shaders and are flagged as unsupported during this phase.
GPU.js infers data types (such as float,
int, vec2, vec3, or
vec4) based on literal values, input argument metadata, and
arithmetic operations. It also intercepts library-specific
thread-context variables like this.thread.x,
this.thread.y, and this.thread.z, preparing
them for mapping to thread indices in the shader.
AST-to-GLSL Transpilation
After validating the tree, GPU.js walks the AST nodes and generates equivalent GLSL source code. The transpiler maps standard JavaScript constructs to their GLSL counterparts:
- Math Functions: Standard
Mathmethods (such asMath.sin,Math.floor, andMath.sqrt) are converted directly to native GLSL functions (sin,floor,sqrt), which run directly on GPU hardware instructions. - Loops and Conditionals: Standard
forloops andif/elsebranches are transcribed into GLSL-compliant control flow structures. - Thread Coordinates: Usages of
this.thread.x,this.thread.y, andthis.thread.zare converted to formulas derived from the built-in GLSL fragment coordinate,gl_FragCoord. - Array Access: Accesses to multi-dimensional input arrays are converted into texture lookups using normalized UV coordinates.
WebGL Pipeline Assembly
Because standard WebGL lacks dedicated compute shaders (which are native to WebGL 2.0 Compute or WebGPU), GPU.js repurposes the WebGL 1 or WebGL 2 rendering pipeline to perform general-purpose computing (GPGPU):
- Vertex Shader: GPU.js generates a minimal, static vertex shader that draws a simple 2D quad (two triangles covering the entire viewport). This quad guarantees that a fragment shader run will be triggered for every target pixel.
- Fragment Shader: The transpiled kernel logic is
injected into the fragment shader's
main()function. Each output pixel rendered on the quad represents a single execution thread of the original JavaScript function. - Compilation and Linking: GPU.js calls the
underlying WebGL API methods (
gl.createShader,gl.shaderSource,gl.compileShader,gl.createProgram, andgl.linkProgram) to pass the generated GLSL code to the graphics driver, which compiles it into machine instructions for the host GPU.
Memory Encoding and Execution
Before executing the pipeline, input arguments (such as JavaScript arrays or matrices) are bound to the shader. GPU.js encodes input arrays into WebGL textures, assigning data values across the Red, Green, Blue, and Alpha (RGBA) channels of floating-point textures.
To run the kernel, GPU.js binds the input textures to the WebGL
context, targets a custom Framebuffer Object (FBO) backed by an output
texture, and issues a draw call (gl.drawArrays). The GPU
renders the quad in parallel, executing the fragment shader for every
coordinate.
Finally, GPU.js can either leave the result stored in GPU VRAM as a
texture for subsequent kernel operations, or read the data back into
system memory using gl.readPixels and decode it into a
standard JavaScript typed array.