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:

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):

  1. 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.
  2. 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.
  3. Compilation and Linking: GPU.js calls the underlying WebGL API methods (gl.createShader, gl.shaderSource, gl.compileShader, gl.createProgram, and gl.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.