Using taichi.js with Minifiers
The Problem
taichi.js
is a compiler. It takes Javascript code in your program and compiles them into WebGPU code. Under the hood, this is achieved by calling the .toString()
on the methods you pass to ti.kernel(..)
. However, in production environments, we often pass the Javascript code into some sort of minifier, which often changes the source code of the functions significantly. This could often lead to compilation errors thrown by the taichi.js
compiler when your program is running in release mode.
As an example, consider the following program
let fieldWithLongName = ti.field(ti.i32, 1000)
ti.addToKernelScope({ fieldWithLongName })
let k = ti.kernel(() => {
for(let i of ti.range(1000)){
fieldWithLongName[i] += 1
}
})
A Javascript minifier might decide to rename the fieldWithLongName
variable because its name is too long. The minifier will likely apply a code transformation that turns the code into
let f = ti.field(ti.i32, 1000)
ti.addToKernelScope({ fieldWithLongName: f })
let k = ti.kernel(() => {
for(let i of ti.range(1000)){
f[i] += 1
}
})
Now, all of a sudden, taichi.js
will start complaining that it does not recognize the variable f
. This is because the transformed code still adds a variable named fieldWithLongName
into the kernel scope, but the variable name within the kernel has already been modified.
This is only one example of how minifiers can mess-up the programming model of taichi.js
. In general, minifiers and other code transformation tools may apply code transformations based on their own assumptions of the semantics of your code, and these assumptions often fail when taichi.js
is involved.
Mitigations
There are a number of things that you can do to avoid problematic transformations by minifiers:
- Prefer
ti.template()
arguments toti.addToKernelScope({..})
- Use class kernels where possible.
However, these are not guaranteed to work, and it largely depends on the specific minifier you are using.
String for the win
The ultimate mitigation, which is almost guaranteed to work, is to pass a string directly. taichi.js
allows you to write this:
let fieldWithLongName = ti.field(ti.i32, 1000)
ti.addToKernelScope({ fieldWithLongName })
let k = ti.kernel(
`
() => {
for(let i of ti.range(1000)){
fieldWithLongName[i] += 1
}
}
`
)
Since you are now passing a string to ti.kernel()
, no minifiers would dare modify it in any way.
Rollup
If you use rollup to bundle your project, there is a rollup-plugin-taichi
which automatically turns every argument in ti.kernel()
into a string. You can enable it by putting this into your rollup.config.js
:
...
import taichi from "rollup-plugin-taichi"
export default {
...
plugins: [
taichi(),
]
};
This will make sure all the kernels you write are free from the tempering of minifiers. However, notice that this plugin only transforms kernel code to strings, but it does not transform any external functions your kernels use:
let f = (x) => { return x + 1 }
ti.addToKernelScope({ f })
let k = ti.kernel(() => {
f(...)
})
Here, minifiers may still wreck havoc on the lambda f
. To prevent this from happening, you can either specify f
as a raw string:
let f = `(x) => { return x + 1 }`
Or better yet, you can use the ti.func()
function:
let f = ti.func((x) => { return x + 1 })
In taichi.js
library, ti.func
has no effect, and it returns any argument as is. However, rollup-plugin-taichi
will identify this as a function for kernel consumption, and will transform it into a string to protect it from the hands of minifiers.