diff --git a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html index db412b1f2..9dee7a12a 100644 --- a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html +++ b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html @@ -2,8 +2,6 @@
-
Predictions (-.--ms)
-
O
1
diff --git a/packages/tinyest-for-wgsl/src/parsers.ts b/packages/tinyest-for-wgsl/src/parsers.ts index 644ad99a5..81fcd3688 100644 --- a/packages/tinyest-for-wgsl/src/parsers.ts +++ b/packages/tinyest-for-wgsl/src/parsers.ts @@ -143,6 +143,14 @@ const Transpilers: Partial< return node.name; }, + ThisExpression(ctx) { + if (ctx.ignoreExternalDepth === 0) { + ctx.externalNames.add('this'); + } + + return 'this'; + }, + BinaryExpression(ctx, node) { const wgslOp = BINARY_OP_MAP[node.operator]; const left = transpile(ctx, node.left) as tinyest.Expression; diff --git a/packages/typegpu/src/core/function/comptime.ts b/packages/typegpu/src/core/function/comptime.ts new file mode 100644 index 000000000..29723b427 --- /dev/null +++ b/packages/typegpu/src/core/function/comptime.ts @@ -0,0 +1,76 @@ +import type { DualFn } from '../../data/dualFn.ts'; +import type { MapValueToSnippet } from '../../data/snippet.ts'; +import { WgslTypeError } from '../../errors.ts'; +import { inCodegenMode } from '../../execMode.ts'; +import { setName, type TgpuNamable } from '../../shared/meta.ts'; +import { $getNameForward, $internal } from '../../shared/symbols.ts'; +import { coerceToSnippet } from '../../tgsl/generationHelpers.ts'; +import { isKnownAtComptime } from '../../types.ts'; + +export type TgpuComptime unknown> = + & DualFn + & TgpuNamable + & { [$getNameForward]: unknown }; + +/** + * Creates a version of `func` that can called safely in a TypeGPU function to + * precompute and inject a value into the final shader code. + * + * Note how the function passed into `comptime` doesn't have to be marked with + * 'use gpu'. That's because the function doesn't execute on the GPU, it gets + * executed before the shader code gets sent to the GPU. + * + * @example + * ```ts + * const injectRand01 = tgpu['~unstable'] + * .comptime(() => Math.random()); + * + * const getColor = (diffuse: d.v3f) => { + * 'use gpu'; + * const albedo = hsvToRgb(injectRand01(), 1, 0.5); + * return albedo.mul(diffuse); + * }; + * ``` + */ +export function comptime unknown>( + func: T, +): TgpuComptime { + const gpuImpl = (...args: MapValueToSnippet>) => { + const argSnippets = args as MapValueToSnippet>; + + if (!argSnippets.every((s) => isKnownAtComptime(s))) { + throw new WgslTypeError( + `Called comptime function with runtime-known values: ${ + argSnippets.filter((s) => !isKnownAtComptime(s)).map((s) => + `'${s.value}'` + ).join(', ') + }`, + ); + } + + return coerceToSnippet(func(...argSnippets.map((s) => s.value) as never[])); + }; + + const impl = ((...args: Parameters) => { + if (inCodegenMode()) { + return gpuImpl(...args as MapValueToSnippet>); + } + return func(...args); + }) as TgpuComptime; + + impl.toString = () => 'comptime'; + impl[$getNameForward] = func; + impl.$name = (label: string) => { + setName(func, label); + return impl; + }; + Object.defineProperty(impl, $internal, { + value: { + jsImpl: func, + gpuImpl, + argConversionHint: 'keep', + }, + }); + + return impl as TgpuComptime; +} diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index abd6b0cd4..f409e0a4a 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -142,9 +142,13 @@ export function createFnCore( // get data generated by the plugin const pluginData = getMetaData(implementation); - if (pluginData?.externals) { + const pluginExternals = typeof pluginData?.externals === 'function' + ? pluginData.externals() + : pluginData?.externals; + + if (pluginExternals) { const missing = Object.fromEntries( - Object.entries(pluginData.externals).filter( + Object.entries(pluginExternals).filter( ([name]) => !(name in externalMap), ), ); diff --git a/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts b/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts new file mode 100644 index 000000000..815a5b2da --- /dev/null +++ b/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts @@ -0,0 +1,166 @@ +import type { AnyData } from '../../data/dataTypes.ts'; +import { type Origin, type ResolvedSnippet, snip } from '../../data/snippet.ts'; +import { inCodegenMode } from '../../execMode.ts'; +import type { InferGPU } from '../../shared/repr.ts'; +import { + $gpuValueOf, + $internal, + $ownSnippet, + $resolve, +} from '../../shared/symbols.ts'; +import type { ResolutionCtx, SelfResolvable } from '../../types.ts'; +import { + applyExternals, + type ExternalMap, + replaceExternalsInWgsl, +} from '../resolve/externals.ts'; +import { valueProxyHandler } from '../valueProxyUtils.ts'; + +// ---------- +// Public API +// ---------- + +/** + * Extra declaration that shall be included in final WGSL code, + * when resolving objects that use it. + */ +export interface TgpuRawCodeSnippet { + $: InferGPU; + value: InferGPU; + + $uses(dependencyMap: Record): this; +} + +// The origin 'function' refers to values passed in from the calling scope, which means +// we would have access to this value anyway. Same goes for 'argument' and 'this-function', +// the values literally exist in the function we're writing. +// +// 'constant-ref' was excluded because it's a special origin reserved for tgpu.const values. +export type RawCodeSnippetOrigin = Exclude< + Origin, + 'function' | 'this-function' | 'argument' | 'constant-ref' +>; + +/** + * An advanced API that creates a typed shader expression which + * can be injected into the final shader bundle upon use. + * + * @param expression The code snippet that will be injected in place of `foo.$` + * @param type The type of the expression + * @param [origin='runtime'] Where the value originates from. + * + * **-- Which origin to choose?** + * + * Usually 'runtime' (the default) is a safe bet, but if you're sure that the expression or + * computation is constant (either a reference to a constant, a numeric literal, + * or an operation on constants), then pass 'constant' as it might lead to better + * optimisations. + * + * If what the expression is a direct reference to an existing value (e.g. a uniform, a + * storage binding, ...), then choose from 'uniform', 'mutable', 'readonly', 'workgroup', + * 'private' or 'handle' depending on the address space of the referred value. + * + * @example + * ```ts + * // An identifier that we know will be in the + * // final shader bundle, but we cannot + * // refer to it in any other way. + * const existingGlobal = tgpu['~unstable'] + * .rawCodeSnippet('EXISTING_GLOBAL', d.f32, 'constant'); + * + * const foo = () => { + * 'use gpu'; + * return existingGlobal.$ * 2; + * }; + * + * const wgsl = tgpu.resolve([foo]); + * // fn foo() -> f32 { + * // return EXISTING_GLOBAL * 2; + * // } + * ``` + */ +export function rawCodeSnippet( + expression: string, + type: TDataType, + origin: RawCodeSnippetOrigin | undefined = 'runtime', +): TgpuRawCodeSnippet { + return new TgpuRawCodeSnippetImpl(expression, type, origin); +} + +// -------------- +// Implementation +// -------------- + +class TgpuRawCodeSnippetImpl + implements TgpuRawCodeSnippet, SelfResolvable { + readonly [$internal]: true; + readonly dataType: TDataType; + readonly origin: RawCodeSnippetOrigin; + + #expression: string; + #externalsToApply: ExternalMap[]; + + constructor( + expression: string, + type: TDataType, + origin: RawCodeSnippetOrigin, + ) { + this[$internal] = true; + this.dataType = type; + this.origin = origin; + + this.#expression = expression; + this.#externalsToApply = []; + } + + $uses(dependencyMap: Record): this { + this.#externalsToApply.push(dependencyMap); + return this; + } + + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + const externalMap: ExternalMap = {}; + + for (const externals of this.#externalsToApply) { + applyExternals(externalMap, externals); + } + + const replacedExpression = replaceExternalsInWgsl( + ctx, + externalMap, + this.#expression, + ); + + return snip(replacedExpression, this.dataType, this.origin); + } + + toString() { + return `raw(${String(this.dataType)}): "${this.#expression}"`; + } + + get [$gpuValueOf](): InferGPU { + const dataType = this.dataType; + const origin = this.origin; + + return new Proxy({ + [$internal]: true, + get [$ownSnippet]() { + return snip(this, dataType, origin); + }, + [$resolve]: (ctx) => ctx.resolve(this), + toString: () => `raw(${String(this.dataType)}): "${this.#expression}".$`, + }, valueProxyHandler) as InferGPU; + } + + get $(): InferGPU { + if (!inCodegenMode()) { + throw new Error('Raw code snippets can only be used on the GPU.'); + } + + return this[$gpuValueOf]; + } + + get value(): InferGPU { + return this.$; + } +} diff --git a/packages/typegpu/src/index.ts b/packages/typegpu/src/index.ts index 2b260af81..92a2b15d7 100644 --- a/packages/typegpu/src/index.ts +++ b/packages/typegpu/src/index.ts @@ -6,8 +6,10 @@ import { constant } from './core/constant/tgpuConstant.ts'; import { declare } from './core/declare/tgpuDeclare.ts'; import { computeFn } from './core/function/tgpuComputeFn.ts'; import { fn } from './core/function/tgpuFn.ts'; +import { rawCodeSnippet } from './core/rawCodeSnippet/tgpuRawCodeSnippet.ts'; import { fragmentFn } from './core/function/tgpuFragmentFn.ts'; import { vertexFn } from './core/function/tgpuVertexFn.ts'; +import { comptime } from './core/function/comptime.ts'; import { resolve, resolveWithContext } from './core/resolve/tgpuResolve.ts'; import { simulate } from './core/simulate/tgpuSimulate.ts'; import { init, initFromDevice } from './core/root/init.ts'; @@ -44,6 +46,7 @@ export const tgpu = { fragmentFn, vertexFn, computeFn, + comptime, /** * @deprecated This feature is now stable, use tgpu.vertexLayout. */ @@ -68,6 +71,7 @@ export const tgpu = { */ const: constant, declare, + rawCodeSnippet, simulate, }, @@ -139,6 +143,10 @@ export type { TgpuDerived, TgpuSlot, } from './core/slot/slotTypes.ts'; +export type { + RawCodeSnippetOrigin, + TgpuRawCodeSnippet, +} from './core/rawCodeSnippet/tgpuRawCodeSnippet.ts'; export type { TgpuTexture, TgpuTextureView } from './core/texture/texture.ts'; export type { TextureProps } from './core/texture/textureProps.ts'; export type { RenderFlag, SampledFlag } from './core/texture/usageExtension.ts'; @@ -167,6 +175,7 @@ export type { TgpuLayoutUniform, } from './tgpuBindGroupLayout.ts'; export type { TgpuFn, TgpuFnShell } from './core/function/tgpuFn.ts'; +export type { TgpuComptime } from './core/function/comptime.ts'; export type { TgpuVertexFn, TgpuVertexFnShell, diff --git a/packages/typegpu/src/shared/meta.ts b/packages/typegpu/src/shared/meta.ts index 747fdd5e8..9c260b126 100644 --- a/packages/typegpu/src/shared/meta.ts +++ b/packages/typegpu/src/shared/meta.ts @@ -10,7 +10,12 @@ export interface MetaData { body: Block; externalNames: string[]; } | undefined; - externals?: Record | undefined; + externals?: + // Passing a record happens prior to version 0.9.0 + // TODO: Support for this can be removed down the line + | Record + | (() => Record) + | undefined; } /** diff --git a/packages/typegpu/src/std/extensions.ts b/packages/typegpu/src/std/extensions.ts index 4be522df0..d67ce4080 100644 --- a/packages/typegpu/src/std/extensions.ts +++ b/packages/typegpu/src/std/extensions.ts @@ -1,53 +1,25 @@ -import type { DualFn } from '../data/dualFn.ts'; -import { bool } from '../data/index.ts'; -import { snip, type Snippet } from '../data/snippet.ts'; -import { getResolutionCtx, inCodegenMode } from '../execMode.ts'; -import { $internal } from '../shared/symbols.ts'; +import { comptime } from '../core/function/comptime.ts'; +import { getResolutionCtx } from '../execMode.ts'; import { type WgslExtension, wgslExtensions } from '../wgslExtensions.ts'; -export const extensionEnabled: DualFn< - (extensionName: WgslExtension) => boolean -> = (() => { - const jsImpl = (extensionName: WgslExtension) => { +export const extensionEnabled = comptime( + (extensionName: WgslExtension): boolean => { const resolutionCtx = getResolutionCtx(); if (!resolutionCtx) { throw new Error( - 'extensionEnabled can only be called in a GPU codegen context.', + "Functions using `extensionEnabled` cannot be called directly. Either generate WGSL from them, or use tgpu['~unstable'].simulate(...)", ); } - return (resolutionCtx.enableExtensions ?? []).includes(extensionName); - }; - const gpuImpl = (extensionNameSnippet: Snippet) => { - const { value } = extensionNameSnippet; if ( - typeof value !== 'string' || - !(wgslExtensions.includes(value as WgslExtension)) + typeof extensionName !== 'string' || + !(wgslExtensions.includes(extensionName as WgslExtension)) ) { throw new Error( - `extensionEnabled has to be called with a string literal representing a valid WGSL extension name. Got: ${value}`, + `extensionEnabled has to be called with a string literal representing a valid WGSL extension name. Got: '${extensionName}'`, ); } - return snip(jsImpl(value as WgslExtension), bool, /* origin */ 'constant'); - }; - - const impl = (extensionName: WgslExtension) => { - if (inCodegenMode()) { - return gpuImpl(extensionName as unknown as Snippet); - } - throw new Error( - 'extensionEnabled can only be called in a GPU codegen context.', - ); - }; - Object.defineProperty(impl, $internal, { - value: { - jsImpl, - gpuImpl, - argConversionHint: 'keep' as const, - }, - }); - return impl; -})() as unknown as DualFn< - (extensionName: WgslExtension) => boolean ->; + return (resolutionCtx.enableExtensions ?? []).includes(extensionName); + }, +); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 7149274e7..a3c4078e8 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -682,7 +682,7 @@ ${this.ctx.pre}}`; } if (expression[0] === NODE.stringLiteral) { - return snip(expression[1], UnknownData, /* origin */ 'runtime'); // arbitrary ref + return snip(expression[1], UnknownData, /* origin */ 'constant'); } if (expression[0] === NODE.preUpdate) { diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 261f16b23..6d10ffc1d 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -321,7 +321,8 @@ export function getOwnSnippet(value: unknown): Snippet | undefined { } export function isKnownAtComptime(snippet: Snippet): boolean { - return typeof snippet.value !== 'string' && + return (typeof snippet.value !== 'string' || + snippet.dataType.type === 'unknown') && getOwnSnippet(snippet.value) === undefined; } diff --git a/packages/typegpu/tests/tgsl/codeGen.test.ts b/packages/typegpu/tests/tgsl/codeGen.test.ts index eb2245d75..6582b4638 100644 --- a/packages/typegpu/tests/tgsl/codeGen.test.ts +++ b/packages/typegpu/tests/tgsl/codeGen.test.ts @@ -34,4 +34,23 @@ describe('codeGen', () => { `); }); }); + + it('should properly resolve this', ({ root }) => { + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(() => { + return this.myBuffer.$; + }); + } + + const myController = new MyController(); + + expect(tgpu.resolve([myController.myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var item_1: u32; + + fn item() -> u32 { + return item_1; + }" + `); + }); }); diff --git a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts index 711b07c53..4ccc6dc4f 100644 --- a/packages/typegpu/tests/tgsl/extensionEnabled.test.ts +++ b/packages/typegpu/tests/tgsl/extensionEnabled.test.ts @@ -17,14 +17,14 @@ describe('extension based pruning', () => { expect(tgpu.resolve([someFn], { enableExtensions: ['f16'] })) .toMatchInlineSnapshot(` - "enable f16; + "enable f16; - fn someFn() -> f32 { - { - return 6.599609375f; - } - }" - `); + fn someFn() -> f32 { + { + return 6.599609375f; + } + }" + `); expect(tgpu.resolve([someFn])).toMatchInlineSnapshot(` "fn someFn() -> f32 { diff --git a/packages/typegpu/tests/tgsl/rawCodeSnippet.test.ts b/packages/typegpu/tests/tgsl/rawCodeSnippet.test.ts new file mode 100644 index 000000000..d01876e79 --- /dev/null +++ b/packages/typegpu/tests/tgsl/rawCodeSnippet.test.ts @@ -0,0 +1,119 @@ +import { describe, expect } from 'vitest'; +import { it } from '../utils/extendedIt.ts'; +import * as d from '../../src/data/index.ts'; +import tgpu from '../../src/index.ts'; + +describe('rawCodeSnippet', () => { + it('should throw a descriptive error when called in JS', () => { + const rawSnippet = tgpu['~unstable'].rawCodeSnippet('3', d.f32); + + const myFn = tgpu.fn([], d.f32)(() => { + return rawSnippet.$; + }); + + expect(() => myFn()).toThrowErrorMatchingInlineSnapshot(` + [Error: Execution of the following tree failed: + - fn:myFn: Raw code snippets can only be used on the GPU.] + `); + }); + + it('should properly inline', () => { + const rawSnippet = tgpu['~unstable'].rawCodeSnippet('3f', d.f32); + + const myFn = tgpu.fn([], d.f32)(() => { + return rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 3f; + }" + `); + }); + + it('should use the origin', () => { + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + '3f', + d.f32, + 'constant', + ); + + const myFn = tgpu.fn([], d.f32)(() => { + const a = rawSnippet.$; // should resolve to 'const' instead of 'let' + return a; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + const a = 3f; + return a; + }" + `); + }); + + it('should properly resolve dependencies', ({ root }) => { + const myBuffer = root.createUniform(d.u32, 7); + + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + 'myBuffer', + d.u32, + 'uniform', + ).$uses({ myBuffer }); + + const myFn = tgpu.fn([], d.u32)(() => { + return rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var myBuffer: u32; + + fn myFn() -> u32 { + return myBuffer; + }" + `); + }); + + it('should properly resolve layout dependencies', ({ root }) => { + const myLayout = tgpu.bindGroupLayout({ myBuffer: { uniform: d.u32 } }); + + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + 'myLayout.$.myBuffer', + d.u32, + 'uniform', + ).$uses({ myLayout }); + + const myFn = tgpu.fn([], d.u32)(() => { + return rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var myBuffer: u32; + + fn myFn() -> u32 { + return myBuffer; + }" + `); + }); + + it('should not duplicate dependencies', ({ root }) => { + const myBuffer = root.createUniform(d.u32, 7); + + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + 'myBuffer', + d.u32, + 'uniform', + ).$uses({ myBuffer }); + + const myFn = tgpu.fn([], d.u32)(() => { + return myBuffer.$ + rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var myBuffer: u32; + + fn myFn() -> u32 { + return (myBuffer + myBuffer); + }" + `); + }); +}); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index a12d14d04..1e688b4e9 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -176,7 +176,7 @@ describe('wgslGenerator', () => { [], {}, d.u32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); provideCtx(ctx, () => { @@ -243,7 +243,7 @@ describe('wgslGenerator', () => { [], {}, d.u32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); // Check for: return testUsage.value[3]; @@ -320,7 +320,7 @@ describe('wgslGenerator', () => { args, {}, d.vec4f, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); // Check for: const value = std.atomicLoad(testUsage.value.b.aa[idx]!.y); @@ -478,7 +478,7 @@ describe('wgslGenerator', () => { [], {}, d.vec4u, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); wgslGenerator.initGenerator(ctx); @@ -515,7 +515,7 @@ describe('wgslGenerator', () => { [snip('idx', d.u32, /* origin */ 'runtime')], {}, d.f32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); // Check for: return derivedV2f.value[idx]; @@ -559,7 +559,7 @@ describe('wgslGenerator', () => { [], {}, d.u32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); // Check for: const arr = [1, 2, 3] @@ -656,7 +656,7 @@ describe('wgslGenerator', () => { [], {}, d.f32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); // Check for: const arr = [TestStruct({ x: 1, y: 2 }), TestStruct({ x: 3, y: 4 })]; @@ -745,7 +745,7 @@ describe('wgslGenerator', () => { [], {}, d.f32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); wgslGenerator.initGenerator(ctx); @@ -791,7 +791,7 @@ describe('wgslGenerator', () => { [], {}, d.f32, - astInfo.externals ?? {}, + (astInfo.externals as () => Record)() ?? {}, ); // Check for: const value = testSlot.value.value; diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index 139f127dd..fc59207de 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -34,7 +34,7 @@ function extractSnippetFromFn(cb: () => unknown): Snippet { [], {}, undefined, - meta.externals ?? {}, + (meta.externals as () => Record)() ?? {}, ); ctx.pushBlockScope(); pushedFnScope = true; diff --git a/packages/unplugin-typegpu/src/babel.ts b/packages/unplugin-typegpu/src/babel.ts index 87c7ecbf0..d25f2a6d7 100644 --- a/packages/unplugin-typegpu/src/babel.ts +++ b/packages/unplugin-typegpu/src/babel.ts @@ -63,19 +63,25 @@ function functionToTranspiled( i('ast'), template.expression`${embedJSON({ params, body, externalNames })}`(), ), - types.objectMethod( - 'get', + types.objectProperty( i('externals'), - [], - types.blockStatement([ - types.returnStatement( - types.objectExpression( - externalNames.map((name) => - types.objectProperty(i(name), i(name), false, true) + types.arrowFunctionExpression( + [], + types.blockStatement([ + types.returnStatement( + types.objectExpression( + externalNames.map((name) => + types.objectProperty( + i(name), + i(name), + false, + /* shorthand */ name !== 'this', + ) + ), ), ), - ), - ]), + ]), + ), ), ]); diff --git a/packages/unplugin-typegpu/src/common.ts b/packages/unplugin-typegpu/src/common.ts index d55076fef..222e15216 100644 --- a/packages/unplugin-typegpu/src/common.ts +++ b/packages/unplugin-typegpu/src/common.ts @@ -145,12 +145,13 @@ const resourceConstructors: string[] = [ // tgpu 'bindGroupLayout', 'vertexLayout', - // tgpu['~unstable'] - 'slot', - 'accessor', 'privateVar', 'workgroupVar', 'const', + // tgpu['~unstable'] + 'slot', + 'accessor', + 'comptime', ...fnShellFunctionNames, // d 'struct', diff --git a/packages/unplugin-typegpu/src/rollup-impl.ts b/packages/unplugin-typegpu/src/rollup-impl.ts index 6c6f4cf35..657be67ee 100644 --- a/packages/unplugin-typegpu/src/rollup-impl.ts +++ b/packages/unplugin-typegpu/src/rollup-impl.ts @@ -198,7 +198,11 @@ export const rollUpImpl = (rawOptions: Options) => { v: ${FORMAT_VERSION}, name: ${name ? `"${name}"` : 'undefined'}, ast: ${embedJSON({ params, body, externalNames })}, - get externals() { return {${externalNames.join(', ')}}; }, + externals: () => ({${ + externalNames.map((e) => e === 'this' ? '"this": this' : e).join( + ', ', + ) + }}), }`; assignMetadata(magicString, def, metadata); diff --git a/packages/unplugin-typegpu/test/aliasing.test.ts b/packages/unplugin-typegpu/test/aliasing.test.ts index 4c280e563..f2c514cd0 100644 --- a/packages/unplugin-typegpu/test/aliasing.test.ts +++ b/packages/unplugin-typegpu/test/aliasing.test.ts @@ -19,7 +19,7 @@ describe('[BABEL] tgpu alias gathering', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[13,"x",[1,[5,"2"],"+",[5,"2"]]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}));" @@ -43,7 +43,7 @@ describe('[BABEL] tgpu alias gathering', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[13,"x",[1,[5,"2"],"+",[5,"2"]]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}));" @@ -67,7 +67,7 @@ describe('[BABEL] tgpu alias gathering', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[13,"x",[1,[5,"2"],"+",[5,"2"]]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}));" @@ -92,7 +92,7 @@ describe('[ROLLUP] tgpu alias gathering', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); " `); @@ -115,7 +115,7 @@ describe('[ROLLUP] tgpu alias gathering', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); " `); @@ -138,7 +138,7 @@ describe('[ROLLUP] tgpu alias gathering', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); " `); diff --git a/packages/unplugin-typegpu/test/auto-naming.test.ts b/packages/unplugin-typegpu/test/auto-naming.test.ts index cc027431d..59912fc58 100644 --- a/packages/unplugin-typegpu/test/auto-naming.test.ts +++ b/packages/unplugin-typegpu/test/auto-naming.test.ts @@ -11,6 +11,7 @@ describe('[BABEL] auto naming', () => { const vertexLayout = tgpu.vertexLayout(d.arrayOf(d.u32)); var fn = tgpu.fn([])(() => {}); let shell = tgpu.fn([]); + const cst = tgpu.const(d.u32, 1); console.log(bindGroupLayout, vertexLayout); `; @@ -25,11 +26,12 @@ describe('[BABEL] auto naming', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})), "fn"); let shell = (globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu.fn([]), "shell"); + const cst = (globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu.const(d.u32, 1), "cst"); console.log(bindGroupLayout, vertexLayout);" `); }); @@ -40,7 +42,7 @@ describe('[BABEL] auto naming', () => { import * as d from 'typegpu/data'; let nothing, accessor = tgpu['~unstable'].accessor(d.u32); - const cst = tgpu.const(d.u32, 1); + const hello = tgpu['~unstable'].comptime(() => 1 + 2); console.log(accessor, shell, fn, cst); `; @@ -51,7 +53,7 @@ describe('[BABEL] auto naming', () => { import * as d from 'typegpu/data'; let nothing, accessor = (globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu['~unstable'].accessor(d.u32), "accessor"); - const cst = (globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu.const(d.u32, 1), "cst"); + const hello = (globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu['~unstable'].comptime(() => 1 + 2), "hello"); console.log(accessor, shell, fn, cst);" `); }); @@ -130,7 +132,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})), "myFunction"); @@ -140,7 +142,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})), "myComputeFn"); @@ -154,7 +156,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[10,[104,{"ret":[5,"0"]}]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})), "myVertexFn"); @@ -167,7 +169,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[10,[6,[7,"d","vec4f"],[]]]]],"externalNames":["d"]}, - get externals() { + externals: () => { return { d }; @@ -321,7 +323,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: "myFun1", ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}); @@ -333,7 +335,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: "myFun2", ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}); @@ -345,7 +347,7 @@ describe('[BABEL] auto naming', () => { v: 1, name: "myFun3", ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({});" @@ -379,7 +381,7 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))), "fn")); console.log(bindGroupLayout, vertexLayout); @@ -488,14 +490,14 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))), "myFunction")); ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu['~unstable'].computeFn({ workgroupSize: [1] })( (($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = (() => {}), { v: 1, name: undefined, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})), ), "myComputeFn")); ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu['~unstable'].vertexFn({ out: { ret: d.i32 } })( @@ -503,7 +505,7 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[[10,[104,{"ret":[5,"0"]}]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})), ), "myVertexFn")); ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu['~unstable'].fragmentFn({ @@ -514,7 +516,7 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[[10,[6,[7,"d","vec4f"],[]]]]],"externalNames":["d"]}, - get externals() { return {d}; }, + externals: () => ({d}), }) && $.f)({})), ), "myFragmentFn")); " @@ -675,7 +677,7 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: "myFun1", ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})); const myFun2 = (($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = (function () { @@ -685,7 +687,7 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: "myFun2", ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})); const myFun3 = (($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = (function myFun3() { @@ -695,7 +697,7 @@ describe('[ROLLUP] auto naming', () => { v: 1, name: "myFun3", ast: {"params":[],"body":[0,[[10,[5,"0"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})); console.log(myFun1, myFun2, myFun3); diff --git a/packages/unplugin-typegpu/test/parser-options.test.ts b/packages/unplugin-typegpu/test/parser-options.test.ts index 238dd1099..6d75e8e50 100644 --- a/packages/unplugin-typegpu/test/parser-options.test.ts +++ b/packages/unplugin-typegpu/test/parser-options.test.ts @@ -21,7 +21,7 @@ describe('[BABEL] parser options', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[13,"x",[1,[5,"2"],"+",[5,"2"]]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}));" @@ -64,7 +64,7 @@ describe('[ROLLUP] tgpu alias gathering', async () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); console.log(increment); diff --git a/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts b/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts index 8f60221be..12270509e 100644 --- a/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts +++ b/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts @@ -40,7 +40,7 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"tmp",[7,[7,"counter","value"],"x"]],[2,[7,[7,"counter","value"],"x"],"=",[7,[7,"counter","value"],"y"]],[2,[7,[7,"counter","value"],"y"],"+=","tmp"],[2,[7,[7,"counter","value"],"z"],"+=",[6,[7,"d","f32"],[[7,[7,"input","num"],"x"]]]]]],"externalNames":["counter","d"]}, - get externals() { + externals: () => { return { counter, d @@ -78,7 +78,7 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"x",true]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -88,7 +88,7 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[13,"y",[1,[5,"2"],"+",[5,"2"]]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -97,7 +97,7 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[],"body":[0,[[10,"cx"]]],"externalNames":["cx"]}, - get externals() { + externals: () => { return { cx }; @@ -149,7 +149,7 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"x",true]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -161,7 +161,7 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"x",true]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -173,12 +173,54 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"x",true]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}));" `); }); + + it('correctly lists "this" in externals', () => { + const code = ` + import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + + const root = await tgpu.init(); + + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(() => { + return this.myBuffer.$; + }); + } + + const myController = new MyController(); + + console.log(tgpu.resolve([myController.myFn]));`; + + expect(babelTransform(code)).toMatchInlineSnapshot(` + "import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + const root = await tgpu.init(); + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = () => { + return this.myBuffer.$; + }, { + v: 1, + name: void 0, + ast: {"params":[],"body":[0,[[10,[7,[7,"this","myBuffer"],"$"]]]],"externalNames":["this"]}, + externals: () => { + return { + this: this + }; + } + }) && $.f)({})); + } + const myController = new MyController(); + console.log(tgpu.resolve([myController.myFn]));" + `); + }); }); describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { @@ -220,7 +262,7 @@ describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: undefined, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"tmp",[7,[7,"counter","value"],"x"]],[2,[7,[7,"counter","value"],"x"],"=",[7,[7,"counter","value"],"y"]],[2,[7,[7,"counter","value"],"y"],"+=","tmp"],[2,[7,[7,"counter","value"],"z"],"+=",[6,[7,"d","f32"],[[7,[7,"input","num"],"x"]]]]]],"externalNames":["counter","d"]}, - get externals() { return {counter, d}; }, + externals: () => ({counter, d}), }) && $.f)({}))); " `); @@ -252,7 +294,7 @@ describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: undefined, ast: {"params":[{"type":"i","name":"input"}],"body":[0,[[13,"x",true]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); tgpu.fn([])((($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = (() => { @@ -260,7 +302,7 @@ describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[[13,"y",[1,[5,"2"],"+",[5,"2"]]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); const cx = 2; @@ -268,7 +310,7 @@ describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { v: 1, name: undefined, ast: {"params":[],"body":[0,[[10,"cx"]]],"externalNames":["cx"]}, - get externals() { return {cx}; }, + externals: () => ({cx}), }) && $.f)({}))); tgpu.fn([])('() {}'); @@ -292,4 +334,47 @@ describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { " `); }); + + it('correctly lists "this" in externals', async () => { + const code = ` + import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + + const root = await tgpu.init(); + + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(() => { + return this.myBuffer.$; + }); + } + + const myController = new MyController(); + + console.log(tgpu.resolve([myController.myFn]));`; + + expect(await rollupTransform(code)).toMatchInlineSnapshot(` + "import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + + const root = await tgpu.init(); + + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)((($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = (() => { + return this.myBuffer.$; + }), { + v: 1, + name: undefined, + ast: {"params":[],"body":[0,[[10,[7,[7,"this","myBuffer"],"$"]]]],"externalNames":["this"]}, + externals: () => ({"this": this}), + }) && $.f)({}))); + } + + const myController = new MyController(); + + console.log(tgpu.resolve([myController.myFn])); + " + `); + }); }); diff --git a/packages/unplugin-typegpu/test/use-gpu-directive.test.ts b/packages/unplugin-typegpu/test/use-gpu-directive.test.ts index afb06e921..b8e073be5 100644 --- a/packages/unplugin-typegpu/test/use-gpu-directive.test.ts +++ b/packages/unplugin-typegpu/test/use-gpu-directive.test.ts @@ -26,7 +26,7 @@ describe('[BABEL] "use gpu" directive', () => { v: 1, name: "addGPU", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}); @@ -63,7 +63,7 @@ describe('[BABEL] "use gpu" directive', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -100,7 +100,7 @@ describe('[BABEL] "use gpu" directive', () => { v: 1, name: void 0, ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -137,7 +137,7 @@ describe('[BABEL] "use gpu" directive', () => { v: 1, name: "addGPU", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({})); @@ -171,7 +171,7 @@ describe('[BABEL] "use gpu" directive', () => { v: 1, name: "addGPU", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}); @@ -198,7 +198,7 @@ describe('[BABEL] "use gpu" directive', () => { v: 1, name: "add", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { + externals: () => { return {}; } }) && $.f)({}); @@ -261,7 +261,7 @@ describe('[ROLLUP] "use gpu" directive', () => { v: 1, name: "addGPU", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})); console.log(addGPU); @@ -303,7 +303,7 @@ describe('[ROLLUP] "use gpu" directive', () => { v: 1, name: undefined, ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); shell((a, b) => { @@ -340,7 +340,7 @@ describe('[ROLLUP] "use gpu" directive', () => { v: 1, name: undefined, ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); shell(function(a, b) { @@ -378,7 +378,7 @@ describe('[ROLLUP] "use gpu" directive', () => { v: 1, name: "addGPU", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({}))); shell(function addCPU(a, b) { @@ -416,7 +416,7 @@ describe('[ROLLUP] "use gpu" directive', () => { v: 1, name: "addGPU", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})); console.log(addGPU); @@ -461,7 +461,7 @@ describe('[ROLLUP] "use gpu" directive', () => { v: 1, name: "add", ast: {"params":[{"type":"i","name":"a"},{"type":"i","name":"b"}],"body":[0,[[10,[1,"a","+","b"]]]],"externalNames":[]}, - get externals() { return {}; }, + externals: () => ({}), }) && $.f)({})); " `);