typegpu 0.12.4 → 0.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin.mjs CHANGED
@@ -1,79 +1,51 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
2
+ import { execFile, spawn } from 'node:child_process';
3
+ import { promisify } from 'node:util';
3
4
  import pkg from './package.json' with { type: 'json' };
4
5
 
5
- /**
6
- * Used to extract the version of `typegpu` that was used to
7
- * trigger the CLI, which then allows us to download the latest
8
- * version matching the major and minor of the `typegpu` package.
9
- */
10
- const versionPattern = /^(\d+)\.(\d+)\.(\d+)/;
11
-
12
- const result = versionPattern.exec(pkg.version);
13
- const [_, major, minor] = result;
6
+ const [major, minor] = pkg.version.split('.');
7
+ const semver = `^${major}.${minor}.0`;
14
8
 
15
- if (major === undefined || minor === undefined) {
16
- throw new Error(`TypeGPU version doesn't match the expected major.minor.patch format`);
9
+ const windows = process.platform === 'win32';
10
+ const npm = windows ? 'npm.cmd' : 'npm';
11
+ const npx = windows ? 'npx.cmd' : 'npx';
12
+ const execFileAsync = promisify(execFile);
13
+
14
+ /** True only when the registry confirms no `@typegpu/cli` satisfies `semver` */
15
+ async function noMatchingCli() {
16
+ const args = ['view', `@typegpu/cli@${semver}`, 'version', '--json'];
17
+ try {
18
+ const { stdout } = await execFileAsync(npm, args, { shell: windows, timeout: 3_000 });
19
+ return ['', '[]'].includes(stdout.trim());
20
+ } catch (err) {
21
+ return /E404|ETARGET/.test(err.stderr ?? '');
22
+ }
17
23
  }
18
24
 
19
- /**
20
- * Targeting the latest version with the same major and minor as `typegpu`
21
- */
22
- const semver = `^${major}.${minor}.0`;
23
-
24
- /**
25
- * @returns {Promise<number | undefined>}
26
- */
27
- function asyncSpawn(...args) {
25
+ /** Resolves with the child's exit code, re-raising a fatal signal on this process */
26
+ function run(command, args) {
28
27
  return new Promise((resolve, reject) => {
29
- const child = spawn(...args);
30
-
31
- child.on('exit', (code, signal) => {
32
- if (signal) {
33
- process.kill(process.pid, signal);
34
- process.exit(0);
35
- return;
36
- }
37
-
38
- resolve(code);
39
- });
40
-
41
- child.on('error', (err) => {
42
- reject(err);
43
- });
28
+ spawn(command, args, { stdio: 'inherit', shell: windows })
29
+ .on('error', reject)
30
+ .on('exit', (code, signal) => {
31
+ if (signal) {
32
+ process.kill(process.pid, signal);
33
+ } else {
34
+ resolve(code ?? 1);
35
+ }
36
+ });
44
37
  });
45
38
  }
46
39
 
47
- /**
48
- * @param {string} label
49
- */
50
- function failedToRunErrHandler(label) {
51
- return (err) => {
52
- console.error(`Failed to run '${label}':`, err);
53
- process.exit(1);
54
- };
40
+ const fallback = await noMatchingCli();
41
+ if (fallback) {
42
+ console.warn(`Couldn't find @typegpu/cli version matching ${semver}, falling back to latest...`);
55
43
  }
44
+ const spec = fallback ? '@typegpu/cli@latest' : `@typegpu/cli@${semver}`;
56
45
 
57
- (async () => {
58
- const windows = process.platform === 'win32';
59
- const npxCommand = windows ? 'npx.cmd' : 'npx';
60
-
61
- const code = await asyncSpawn(npxCommand, [`@typegpu/cli@${semver}`, ...process.argv.slice(2)], {
62
- stdio: 'inherit',
63
- shell: windows, // needs to be ran through the shell on Windows
64
- }).catch(failedToRunErrHandler(`npx @typegpu/cli@${semver}`));
65
-
66
- if (code !== 0) {
67
- console.warn(
68
- `Couldn't find @typegpu/cli version matching ${semver}, falling back to latest...`,
69
- );
70
- // Fallback to latest
71
- const code = await asyncSpawn(npxCommand, [`@typegpu/cli@latest`, ...process.argv.slice(2)], {
72
- stdio: 'inherit',
73
- shell: windows, // needs to be ran through the shell on Windows
74
- }).catch(failedToRunErrHandler('npx @typegpu/cli@latest'));
75
- process.exit(code ?? 0);
76
- }
77
-
78
- process.exit(code ?? 0);
79
- })();
46
+ try {
47
+ process.exit(await run(npx, [spec, ...process.argv.slice(2)]));
48
+ } catch (err) {
49
+ console.error(`Failed to run 'npx ${spec}':`, err);
50
+ process.exit(2);
51
+ }
@@ -54,6 +54,11 @@ export interface TgpuComputePipeline extends TgpuNamable, SelfResolvable, Timeab
54
54
  with(encoder: TgpuCommandEncoder): this;
55
55
  with(encoder: GPUCommandEncoder): this;
56
56
  with(pass: GPUComputePassEncoder): this;
57
+ /**
58
+ * Applies a transform to this pipeline, letting packages hand out reusable
59
+ * configuration steps, e.g. `pipeline.pipe(cache.inject())`.
60
+ */
61
+ pipe<T>(transform: (pipeline: this) => T): T;
57
62
  dispatchWorkgroups(x: number, y?: number, z?: number): void;
58
63
  /**
59
64
  * Immediately resolves the pipeline, then awaits `device.createComputePipelineAsync()`.
@@ -111,6 +116,7 @@ declare class TgpuComputePipelineImpl implements TgpuComputePipeline {
111
116
  with(encoder: TgpuCommandEncoder): this;
112
117
  with(encoder: GPUCommandEncoder): this;
113
118
  with(pass: GPUComputePassEncoder): this;
119
+ pipe<T>(transform: (pipeline: this) => T): T;
114
120
  withPerformanceCallback(callback: (start: bigint, end: bigint) => void | Promise<void>): this;
115
121
  withTimestampWrites(options: {
116
122
  querySet: TgpuQuerySet<'timestamp'> | GPUQuerySet;
@@ -111,6 +111,9 @@ class TgpuComputePipelineImpl {
111
111
  }
112
112
  throw new Error('Unsupported value passed into .with()');
113
113
  }
114
+ pipe(transform) {
115
+ return transform(this);
116
+ }
114
117
  withPerformanceCallback(callback) {
115
118
  const internals = this[$internal];
116
119
  if (internals.priors.timestampWrites) {
@@ -89,6 +89,11 @@ export interface TgpuRenderPipeline<in Targets = never> extends TgpuNamable, Sel
89
89
  with(encoder: GPUCommandEncoder): this;
90
90
  with(pass: GPURenderPassEncoder): this;
91
91
  with(bundleEncoder: GPURenderBundleEncoder): this;
92
+ /**
93
+ * Applies a transform to this pipeline, letting packages hand out reusable
94
+ * configuration steps, e.g. `pipeline.pipe(mesh.inject())`.
95
+ */
96
+ pipe<T>(transform: (pipeline: this) => T): T;
92
97
  /**
93
98
  * Attaches texture views to the pipeline's targets (outputs).
94
99
  *
@@ -223,6 +228,7 @@ declare class TgpuRenderPipelineImpl implements TgpuRenderPipeline {
223
228
  with(encoder: GPUCommandEncoder): this;
224
229
  with(pass: GPURenderPassEncoder): this;
225
230
  with(bundleEncoder: GPURenderBundleEncoder): this;
231
+ pipe<T>(transform: (pipeline: this) => T): T;
226
232
  withPerformanceCallback(callback: (start: bigint, end: bigint) => void | Promise<void>): this;
227
233
  withTimestampWrites(options: {
228
234
  querySet: TgpuQuerySet<'timestamp'> | GPUQuerySet;
@@ -143,6 +143,9 @@ class TgpuRenderPipelineImpl {
143
143
  }
144
144
  throw new Error('Unsupported value passed into .with()');
145
145
  }
146
+ pipe(transform) {
147
+ return transform(this);
148
+ }
146
149
  withPerformanceCallback(callback) {
147
150
  const internals = this[$internal];
148
151
  if (internals.priors.timestampWrites) {
@@ -1,6 +1,7 @@
1
1
  import { type AnyBooleanVecInstance, type AnyMatInstance, type AnyVec2Instance, type AnyVec3Instance, type AnyVec4Instance, type AnyVecInstance, type v2b, type v3b, type v4b } from './wgslTypes.ts';
2
2
  type Vec = AnyVecInstance;
3
3
  type Mat = AnyMatInstance;
4
+ type Kind = 'number' | 'boolean' | Vec['kind'] | Mat['kind'];
4
5
  type Algebraic = number | boolean | Vec | Mat;
5
6
  export type ToBool<T extends Algebraic> = T extends number | boolean ? boolean : T extends AnyVec2Instance ? v2b : T extends AnyVec3Instance ? v3b : T extends AnyVec4Instance ? v4b : never;
6
7
  /**
@@ -15,11 +16,26 @@ export declare function generalizeFn<T extends Algebraic>(fn: (a: number, b: num
15
16
  /**
16
17
  * Analogous to `generalizeFn`, but the return type is a boolean vector instead.
17
18
  */
19
+ export declare function generalizeBoolFn<T extends boolean | AnyBooleanVecInstance>(fn: (a: boolean) => boolean, args: [T]): T;
18
20
  export declare function generalizeBoolFn<T extends Algebraic>(fn: (a: number, b: number) => boolean, args: [T, T]): ToBool<T>;
19
21
  export declare function generalizeBoolFn<T extends boolean | AnyBooleanVecInstance>(fn: (a: boolean, b: boolean) => boolean, args: [T, T]): ToBool<T>;
22
+ export declare function kindOf(v: Algebraic): Kind;
23
+ export declare const u32Kind: Set<Kind>;
24
+ export declare const f32Kind: Set<Kind>;
25
+ export declare const f16Kind: Set<Kind>;
26
+ export declare const matrixKind: Set<Kind>;
27
+ export declare const booleanKind: Set<Kind>;
28
+ export declare const floatKind: Set<Kind>;
29
+ export declare const signedKind: Set<Kind>;
30
+ export declare const numericKind: Set<Kind>;
31
+ export declare const numericOrBooleanKind: Set<Kind>;
32
+ export declare const numericOrMatrixKind: Set<Kind>;
33
+ export declare const crossKind: Set<Kind>;
34
+ export declare function assertKind(v: Algebraic | Algebraic[], valid: Set<Kind>, excludeScalar?: boolean): void;
35
+ export declare function assertEqualKinds(...values: Algebraic[]): void;
20
36
  /**
21
37
  * If one of the arguments is a vector and other is a number,
22
38
  * the number is up-cased to a vector.
23
39
  */
24
- export declare function upCast<T extends number | Vec>(args: [T, T]): [Exclude<T, number>, Exclude<T, number>];
40
+ export declare function upCast<T extends number | Vec | Mat>(args: [T, T]): [Exclude<T, number>, Exclude<T, number>];
25
41
  export {};
@@ -1,7 +1,7 @@
1
1
  import { vec2b, vec3b, vec4b, vecTypeToConstructor } from "./vector.js";
2
2
  import { mat2x2f, mat3x3f, mat4x4f } from "./matrix.js";
3
3
  import { isVecInstance, } from "./wgslTypes.js";
4
- import { invariant } from "../errors.js";
4
+ import { invariant, WgslTypeError } from "../errors.js";
5
5
  const booleanFor = {
6
6
  vec2f: vec2b,
7
7
  vec2h: vec2b,
@@ -62,7 +62,7 @@ export function generalizeFn(fn, args) {
62
62
  export function generalizeBoolFn(fn, args) {
63
63
  return applyArgs(fn, args, 'boolean');
64
64
  }
65
- function kindOf(v) {
65
+ export function kindOf(v) {
66
66
  if (typeof v === 'number') {
67
67
  return 'number';
68
68
  }
@@ -71,6 +71,43 @@ function kindOf(v) {
71
71
  }
72
72
  return v.kind;
73
73
  }
74
+ // Unless matrix is mentioned in the name, it is not included.
75
+ const i32Kind = new Set(['number', 'vec2i', 'vec3i', 'vec4i']);
76
+ export const u32Kind = new Set(['number', 'vec2u', 'vec3u', 'vec4u']);
77
+ export const f32Kind = new Set(['number', 'vec2f', 'vec3f', 'vec4f']);
78
+ export const f16Kind = new Set(['number', 'vec2h', 'vec3h', 'vec4h']);
79
+ export const matrixKind = new Set(['mat2x2f', 'mat3x3f', 'mat4x4f']);
80
+ export const booleanKind = new Set([
81
+ 'boolean',
82
+ 'vec2<bool>',
83
+ 'vec3<bool>',
84
+ 'vec4<bool>',
85
+ ]);
86
+ export const floatKind = new Set([...f32Kind, ...f16Kind]);
87
+ export const signedKind = new Set([...i32Kind, ...f32Kind, ...f16Kind]);
88
+ export const numericKind = new Set([...signedKind, ...u32Kind]);
89
+ export const numericOrBooleanKind = new Set([...numericKind, ...booleanKind]);
90
+ export const numericOrMatrixKind = new Set([...numericKind, ...matrixKind]);
91
+ export const crossKind = new Set(['vec3f', 'vec3h']);
92
+ export function assertKind(v, valid, excludeScalar = false) {
93
+ if (!isVecInstance(v) && Array.isArray(v)) {
94
+ v.forEach((item) => assertKind(item, valid, excludeScalar));
95
+ return;
96
+ }
97
+ const kind = kindOf(v);
98
+ if (!valid.has(kind)) {
99
+ throw new WgslTypeError(`Unsupported signature. Expected one of '${[...valid].join(', ')}', got '${kind}'.`);
100
+ }
101
+ if (excludeScalar && (kind === 'number' || kind === 'boolean')) {
102
+ throw new WgslTypeError(`Unsupported signature. Expected kind to not be scalar, got '${kind}'.`);
103
+ }
104
+ }
105
+ export function assertEqualKinds(...values) {
106
+ const kinds = new Set(values.map(kindOf));
107
+ if (kinds.size !== 1) {
108
+ throw new WgslTypeError(`Unsupported signature. Expected the following kinds to be equal: '${[...kinds].join(', ')}'.`);
109
+ }
110
+ }
74
111
  /**
75
112
  * If one of the arguments is a vector and other is a number,
76
113
  * the number is up-cased to a vector.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
4
4
  "description": "A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.",
5
5
  "keywords": [
6
6
  "compute",
package/shared/meta.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.4";
1
+ const version = "0.12.5";
2
2
  import { DEV, TEST } from "./env.js";
3
3
  import { $getNameForward, $soul, isMarkedInternal } from "./symbols.js";
4
4
  import { normalizeMetadata } from "./normalizeMetadata.js";
package/shared/symbols.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.4";
1
+ const version = "0.12.5";
2
2
  export const $internal = Symbol(`typegpu:${version}:$internal`);
3
3
  /** A plain record of all definitional state of a resource, surviving transfer between runtimes */
4
4
  export const $soul = Symbol(`typegpu:${version}:$soul`);
@@ -39,7 +39,7 @@ export type Assume<T, U> = T extends U ? T : U;
39
39
  /**
40
40
  * Any typed array
41
41
  */
42
- export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Int32Array | Float32Array | Float64Array;
42
+ export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Int32Array | Float16Array | Float32Array | Float64Array;
43
43
  export declare function assertExhaustive(x: never, location: string): never;
44
44
  /**
45
45
  * Source: https://futurestud.io/tutorials/typescript-how-to-remove-index-signature-from-a-type
package/std/bitcast.js CHANGED
@@ -9,6 +9,7 @@ import { SignatureNotSupportedError } from "../errors.js";
9
9
  import { getName } from "../shared/meta.js";
10
10
  import { comptime } from "../core/function/comptime.js";
11
11
  import { coerceToSnippet } from "../tgsl/generationHelpers.js";
12
+ import { f32Kind, u32Kind, assertKind } from "../data/generalizeFn.js";
12
13
  const u32AllowedSchemas = [u32, vec2u, vec3u, vec4u];
13
14
  // TODO(#2731): Remove deprecated bitcasts. Remember about cpu implementations.
14
15
  /**
@@ -17,6 +18,7 @@ const u32AllowedSchemas = [u32, vec2u, vec3u, vec4u];
17
18
  export const bitcastU32toF32 = dualImpl({
18
19
  name: 'bitcastU32toF32',
19
20
  normalImpl: ((value) => {
21
+ assertKind(value, u32Kind);
20
22
  if (typeof value === 'number') {
21
23
  return bitcastU32toF32Impl(value);
22
24
  }
@@ -49,6 +51,7 @@ export const bitcastU32toF32 = dualImpl({
49
51
  export const bitcastU32toI32 = dualImpl({
50
52
  name: 'bitcastU32toI32',
51
53
  normalImpl: ((value) => {
54
+ assertKind(value, u32Kind);
52
55
  if (typeof value === 'number') {
53
56
  return bitcastU32toI32Impl(value);
54
57
  }
@@ -82,6 +85,7 @@ const f32AllowedSchemas = [f32, vec2f, vec3f, vec4f];
82
85
  export const bitcastF32toU32 = dualImpl({
83
86
  name: 'bitcastF32toU32',
84
87
  normalImpl: ((value) => {
88
+ assertKind(value, f32Kind);
85
89
  if (typeof value === 'number') {
86
90
  return bitcastF32toU32Impl(value);
87
91
  }
package/std/boolean.d.ts CHANGED
@@ -93,7 +93,7 @@ export declare const and: import("../types.ts").DualFn<(<T extends AnyBooleanVec
93
93
  * all(vec2b(false, true)) // returns false
94
94
  * all(vec3b(true, true, true)) // returns true
95
95
  */
96
- export declare const all: import("../types.ts").DualFn<(value: AnyBooleanVecInstance) => boolean>;
96
+ export declare const all: import("../types.ts").DualFn<(value: boolean | AnyBooleanVecInstance) => boolean>;
97
97
  /**
98
98
  * Returns `true` if any component of `value` is true.
99
99
  * @example
package/std/boolean.js CHANGED
@@ -4,9 +4,9 @@ import { bool, f16, f32, i32, u32 } from "../data/numeric.js";
4
4
  import { isSnippetNumeric, snip } from "../data/snippet.js";
5
5
  import { vec2b, vec2f, vec2h, vec2i, vec2u, vec3b, vec3f, vec3h, vec3i, vec3u, vec4b, vec4f, vec4h, vec4i, vec4u, } from "../data/vector.js";
6
6
  import { VectorOps } from "../data/vectorOps.js";
7
- import { generalizeBoolFn, generalizeFn } from "../data/generalizeFn.js";
8
- import { isBool, isVecBool, isVecBoolInstance, } from "../data/wgslTypes.js";
9
- import { SignatureNotSupportedError } from "../errors.js";
7
+ import { booleanKind, floatKind, generalizeBoolFn, generalizeFn, kindOf, numericKind, numericOrBooleanKind, assertEqualKinds, assertKind, } from "../data/generalizeFn.js";
8
+ import { isBool, isVecBool, isVecInstance, } from "../data/wgslTypes.js";
9
+ import { SignatureNotSupportedError, WgslTypeError } from "../errors.js";
10
10
  import { unify } from "../tgsl/conversion.js";
11
11
  import { cpuCopy } from "./copy.js";
12
12
  function correspondingBooleanVectorSchema(dataType) {
@@ -33,7 +33,11 @@ export const allEq = dualImpl({
33
33
  codegenImpl: (_ctx, [lhs, rhs]) => stitch `all(${lhs} == ${rhs})`,
34
34
  sideEffects: false,
35
35
  });
36
- const cpuEq = (lhs, rhs) => generalizeBoolFn((a, b) => a === b, [lhs, rhs]);
36
+ const cpuEq = (lhs, rhs) => {
37
+ assertKind([lhs, rhs], numericOrBooleanKind);
38
+ assertEqualKinds(lhs, rhs);
39
+ return generalizeBoolFn((a, b) => a === b, [lhs, rhs]);
40
+ };
37
41
  /**
38
42
  * Checks **component-wise** whether `lhs == rhs`.
39
43
  * This function does **not** return `bool`, for that use-case, wrap the result in `all`, or use `allEq`.
@@ -71,7 +75,11 @@ export const ne = dualImpl({
71
75
  codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} != ${rhs})`,
72
76
  sideEffects: false,
73
77
  });
74
- const cpuLt = (lhs, rhs) => generalizeBoolFn((a, b) => a < b, [lhs, rhs]);
78
+ const cpuLt = (lhs, rhs) => {
79
+ assertKind([lhs, rhs], numericKind);
80
+ assertEqualKinds(lhs, rhs);
81
+ return generalizeBoolFn((a, b) => a < b, [lhs, rhs]);
82
+ };
75
83
  /**
76
84
  * Checks **component-wise** whether `lhs < rhs`.
77
85
  * This function does **not** return `bool`, for that use-case, wrap the result in `all`.
@@ -145,20 +153,8 @@ export const ge = dualImpl({
145
153
  sideEffects: false,
146
154
  });
147
155
  function cpuNot(value) {
148
- if (typeof value === 'boolean') {
149
- return !value;
150
- }
151
- if (!isVecBoolInstance(value)) {
152
- throw new Error(`'std.not' requires a boolean or boolean vector.`);
153
- }
154
- switch (value.length) {
155
- case 2:
156
- return vec2b(cpuNot(value.x), cpuNot(value.y));
157
- case 3:
158
- return vec3b(cpuNot(value.x), cpuNot(value.y), cpuNot(value.z));
159
- case 4:
160
- return vec4b(cpuNot(value.x), cpuNot(value.y), cpuNot(value.z), cpuNot(value.w));
161
- }
156
+ assertKind(value, booleanKind);
157
+ return generalizeBoolFn((a) => !a, [value]);
162
158
  }
163
159
  /**
164
160
  * Returns the logical negation of the given value.
@@ -183,7 +179,11 @@ export const not = dualImpl({
183
179
  codegenImpl: (_ctx, [arg]) => stitch `!(${arg})`,
184
180
  sideEffects: false,
185
181
  });
186
- const cpuOr = (lhs, rhs) => generalizeBoolFn((a, b) => a || b, [lhs, rhs]);
182
+ const cpuOr = (lhs, rhs) => {
183
+ assertKind([lhs, rhs], booleanKind);
184
+ assertEqualKinds(lhs, rhs);
185
+ return generalizeBoolFn((a, b) => a || b, [lhs, rhs]);
186
+ };
187
187
  /**
188
188
  * Returns **component-wise** logical `or` result.
189
189
  * @example
@@ -212,7 +212,13 @@ export const and = dualImpl({
212
212
  sideEffects: false,
213
213
  });
214
214
  // logical aggregation
215
- const cpuAll = (value) => VectorOps.all[value.kind](value);
215
+ const cpuAll = (value) => {
216
+ assertKind(value, booleanKind);
217
+ if (typeof value === 'boolean') {
218
+ return value;
219
+ }
220
+ return VectorOps.all[value.kind](value);
221
+ };
216
222
  /**
217
223
  * Returns `true` if each component of `value` is true.
218
224
  * @example
@@ -257,6 +263,8 @@ export const isCloseTo = dualImpl({
257
263
  }),
258
264
  // CPU implementation
259
265
  normalImpl: (lhs, rhs, precision = 0.01) => {
266
+ assertKind([lhs, rhs], floatKind);
267
+ assertEqualKinds(lhs, rhs);
260
268
  const componentResult = generalizeBoolFn((lhs, rhs) => Math.abs(lhs - rhs) < precision, [lhs, rhs]);
261
269
  return typeof componentResult === 'boolean' ? componentResult : all(componentResult);
262
270
  },
@@ -275,9 +283,15 @@ export const isCloseTo = dualImpl({
275
283
  sideEffects: false,
276
284
  });
277
285
  function cpuSelect(f, t, cond) {
286
+ assertKind([f, t], numericOrBooleanKind);
287
+ assertEqualKinds(f, t);
288
+ assertKind(cond, booleanKind);
278
289
  if (typeof cond === 'boolean') {
279
290
  return cpuCopy(cond ? t : f);
280
291
  }
292
+ if (!isVecInstance(f) || f.length !== cond.length) {
293
+ throw new WgslTypeError(`Select shape '(${kindOf(f)}, ${kindOf(t)}, ${kindOf(cond)})' is invalid.`);
294
+ }
281
295
  // generalizeFn will handle this fine, it just has no mixed type overload.
282
296
  return generalizeFn((f, t, c) => (c ? t : f), [f, t, cond]);
283
297
  }
package/std/derivative.js CHANGED
@@ -5,7 +5,7 @@ export const dpdx = dualImpl({
5
5
  name: 'dpdx',
6
6
  normalImpl: derivativeNormalError,
7
7
  signature: (value) => ({ argTypes: [value], returnType: value }),
8
- codegenImpl: (_ctx, [value]) => stitch `dpdx(${value})`,
8
+ codegenImpl: (ctx, [value]) => ctx.gen.emitCall('dpdx', [], [value]),
9
9
  sideEffects: false,
10
10
  });
11
11
  export const dpdxCoarse = dualImpl({
@@ -26,7 +26,7 @@ export const dpdy = dualImpl({
26
26
  name: 'dpdy',
27
27
  normalImpl: derivativeNormalError,
28
28
  signature: (value) => ({ argTypes: [value], returnType: value }),
29
- codegenImpl: (_ctx, [value]) => stitch `dpdy(${value})`,
29
+ codegenImpl: (ctx, [value]) => ctx.gen.emitCall('dpdy', [], [value]),
30
30
  sideEffects: false,
31
31
  });
32
32
  export const dpdyCoarse = dualImpl({
@@ -47,7 +47,7 @@ export const fwidth = dualImpl({
47
47
  name: 'fwidth',
48
48
  normalImpl: derivativeNormalError,
49
49
  signature: (value) => ({ argTypes: [value], returnType: value }),
50
- codegenImpl: (_ctx, [value]) => stitch `fwidth(${value})`,
50
+ codegenImpl: (ctx, [value]) => ctx.gen.emitCall('fwidth', [], [value]),
51
51
  sideEffects: false,
52
52
  });
53
53
  export const fwidthCoarse = dualImpl({
package/std/numeric.js CHANGED
@@ -6,7 +6,7 @@ import { abstractFloat, abstractInt, f16, f32, i32, u32 } from "../data/numeric.
6
6
  import { abstruct } from "../data/struct.js";
7
7
  import { vec2f, vec2h, vec2i, vec2u, vec3f, vec3h, vec3i, vec3u, vec4f, vec4h, vec4i, vec4u, } from "../data/vector.js";
8
8
  import { VectorOps } from "../data/vectorOps.js";
9
- import { generalizeFn, upCast } from "../data/generalizeFn.js";
9
+ import { floatKind, matrixKind, numericKind, signedKind, crossKind, assertEqualKinds, assertKind, upCast, generalizeFn, } from "../data/generalizeFn.js";
10
10
  import { isHalfPrecisionSchema, WORKAROUND_getSchema, } from "../data/wgslTypes.js";
11
11
  import { SignatureNotSupportedError } from "../errors.js";
12
12
  import { assertExhaustive } from "../shared/utilityTypes.js";
@@ -61,6 +61,7 @@ const anyConcreteIntegerPrimitive = [i32, u32];
61
61
  const anyConcreteIntegerVec = [vec2i, vec3i, vec4i, vec2u, vec3u, vec4u];
62
62
  const anyConcreteInteger = [...anyConcreteIntegerPrimitive, ...anyConcreteIntegerVec];
63
63
  function cpuAbs(value) {
64
+ assertKind(value, numericKind);
64
65
  return generalizeFn(Math.abs, [value]);
65
66
  }
66
67
  export const abs = dualImpl({
@@ -71,6 +72,7 @@ export const abs = dualImpl({
71
72
  sideEffects: false,
72
73
  });
73
74
  function cpuAcos(value) {
75
+ assertKind(value, floatKind);
74
76
  return generalizeFn(Math.acos, [value]);
75
77
  }
76
78
  export const acos = dualImpl({
@@ -81,6 +83,7 @@ export const acos = dualImpl({
81
83
  sideEffects: false,
82
84
  });
83
85
  function cpuAcosh(value) {
86
+ assertKind(value, floatKind);
84
87
  return generalizeFn(Math.acosh, [value]);
85
88
  }
86
89
  export const acosh = dualImpl({
@@ -91,6 +94,7 @@ export const acosh = dualImpl({
91
94
  sideEffects: false,
92
95
  });
93
96
  function cpuAsin(value) {
97
+ assertKind(value, floatKind);
94
98
  return generalizeFn(Math.asin, [value]);
95
99
  }
96
100
  export const asin = dualImpl({
@@ -101,6 +105,7 @@ export const asin = dualImpl({
101
105
  sideEffects: false,
102
106
  });
103
107
  function cpuAsinh(value) {
108
+ assertKind(value, floatKind);
104
109
  return generalizeFn(Math.asinh, [value]);
105
110
  }
106
111
  export const asinh = dualImpl({
@@ -111,6 +116,7 @@ export const asinh = dualImpl({
111
116
  sideEffects: false,
112
117
  });
113
118
  function cpuAtan(value) {
119
+ assertKind(value, floatKind);
114
120
  return generalizeFn(Math.atan, [value]);
115
121
  }
116
122
  export const atan = dualImpl({
@@ -121,6 +127,7 @@ export const atan = dualImpl({
121
127
  sideEffects: false,
122
128
  });
123
129
  function cpuAtanh(value) {
130
+ assertKind(value, floatKind);
124
131
  return generalizeFn(Math.atanh, [value]);
125
132
  }
126
133
  export const atanh = dualImpl({
@@ -131,6 +138,8 @@ export const atanh = dualImpl({
131
138
  sideEffects: false,
132
139
  });
133
140
  function cpuAtan2(y, x) {
141
+ assertKind([y, x], floatKind);
142
+ assertEqualKinds(y, x);
134
143
  return generalizeFn(Math.atan2, [y, x]);
135
144
  }
136
145
  export const atan2 = dualImpl({
@@ -141,6 +150,7 @@ export const atan2 = dualImpl({
141
150
  sideEffects: false,
142
151
  });
143
152
  function cpuCeil(value) {
153
+ assertKind(value, floatKind);
144
154
  return generalizeFn(Math.ceil, [value]);
145
155
  }
146
156
  export const ceil = dualImpl({
@@ -151,6 +161,8 @@ export const ceil = dualImpl({
151
161
  sideEffects: false,
152
162
  });
153
163
  function cpuClamp(value, low, high) {
164
+ assertKind([value, low, high], numericKind);
165
+ assertEqualKinds(value, low, high);
154
166
  return generalizeFn(clampScalar, [value, low, high]);
155
167
  }
156
168
  export const clamp = dualImpl({
@@ -161,6 +173,7 @@ export const clamp = dualImpl({
161
173
  sideEffects: false,
162
174
  });
163
175
  function cpuCos(value) {
176
+ assertKind(value, floatKind);
164
177
  return generalizeFn(Math.cos, [value]);
165
178
  }
166
179
  export const cos = dualImpl({
@@ -171,6 +184,7 @@ export const cos = dualImpl({
171
184
  sideEffects: false,
172
185
  });
173
186
  function cpuCosh(value) {
187
+ assertKind(value, floatKind);
174
188
  return generalizeFn(Math.cosh, [value]);
175
189
  }
176
190
  export const cosh = dualImpl({
@@ -213,11 +227,16 @@ export const countTrailingZeros = dualImpl({
213
227
  export const cross = dualImpl({
214
228
  name: 'cross',
215
229
  signature: unifyRestrictedSignature([vec3f, vec3h]),
216
- normalImpl: (a, b) => VectorOps.cross[a.kind](a, b),
230
+ normalImpl: (a, b) => {
231
+ assertKind([a, b], crossKind);
232
+ assertEqualKinds(a, b);
233
+ return VectorOps.cross[a.kind](a, b);
234
+ },
217
235
  codegenImpl: (_ctx, [a, b]) => stitch `cross(${a}, ${b})`,
218
236
  sideEffects: false,
219
237
  });
220
238
  function cpuDegrees(value) {
239
+ assertKind(value, floatKind);
221
240
  if (typeof value === 'number') {
222
241
  return ((value * 180) / Math.PI);
223
242
  }
@@ -243,6 +262,8 @@ export const determinant = dualImpl({
243
262
  sideEffects: false,
244
263
  });
245
264
  function cpuDistance(a, b) {
265
+ assertKind([a, b], floatKind);
266
+ assertEqualKinds(a, b);
246
267
  if (typeof a === 'number' && typeof b === 'number') {
247
268
  return Math.abs(a - b);
248
269
  }
@@ -270,7 +291,11 @@ export const dot = dualImpl({
270
291
  argTypes: args,
271
292
  returnType: args[0].primitive,
272
293
  }),
273
- normalImpl: (lhs, rhs) => VectorOps.dot[lhs.kind](lhs, rhs),
294
+ normalImpl: (lhs, rhs) => {
295
+ assertKind([lhs, rhs], numericKind, true);
296
+ assertEqualKinds(lhs, rhs);
297
+ return VectorOps.dot[lhs.kind](lhs, rhs);
298
+ },
274
299
  codegenImpl: (_ctx, [lhs, rhs]) => stitch `dot(${lhs}, ${rhs})`,
275
300
  sideEffects: false,
276
301
  });
@@ -289,6 +314,7 @@ export const dot4I8Packed = dualImpl({
289
314
  sideEffects: false,
290
315
  });
291
316
  function cpuExp(value) {
317
+ assertKind(value, floatKind);
292
318
  return generalizeFn(Math.exp, [value]);
293
319
  }
294
320
  export const exp = dualImpl({
@@ -299,6 +325,7 @@ export const exp = dualImpl({
299
325
  sideEffects: false,
300
326
  });
301
327
  function cpuExp2(value) {
328
+ assertKind(value, floatKind);
302
329
  return generalizeFn((val) => 2 ** val, [value]);
303
330
  }
304
331
  export const exp2 = dualImpl({
@@ -355,6 +382,7 @@ export const firstTrailingBit = dualImpl({
355
382
  sideEffects: false,
356
383
  });
357
384
  function cpuFloor(value) {
385
+ assertKind(value, floatKind);
358
386
  return generalizeFn(Math.floor, [value]);
359
387
  }
360
388
  export const floor = dualImpl({
@@ -365,6 +393,8 @@ export const floor = dualImpl({
365
393
  sideEffects: false,
366
394
  });
367
395
  function cpuFma(e1, e2, e3) {
396
+ assertKind([e1, e2, e3], floatKind);
397
+ assertEqualKinds(e1, e2, e3);
368
398
  if (typeof e1 === 'number') {
369
399
  return (e1 * e2 + e3);
370
400
  }
@@ -378,6 +408,7 @@ export const fma = dualImpl({
378
408
  sideEffects: false,
379
409
  });
380
410
  function cpuFract(value) {
411
+ assertKind(value, floatKind);
381
412
  return generalizeFn((value) => value - Math.floor(value), [value]);
382
413
  }
383
414
  export const fract = dualImpl({
@@ -431,6 +462,7 @@ export const insertBits = dualImpl({
431
462
  sideEffects: false,
432
463
  });
433
464
  function cpuInverseSqrt(value) {
465
+ assertKind(value, floatKind);
434
466
  if (typeof value === 'number') {
435
467
  return (1 / Math.sqrt(value));
436
468
  }
@@ -440,7 +472,7 @@ export const inverseSqrt = dualImpl({
440
472
  name: 'inverseSqrt',
441
473
  signature: unifyRestrictedSignature(anyFloat),
442
474
  normalImpl: cpuInverseSqrt,
443
- codegenImpl: (_ctx, [value]) => stitch `inverseSqrt(${value})`,
475
+ codegenImpl: (ctx, [value]) => ctx.gen.emitCall('inverseSqrt', [], [value]),
444
476
  sideEffects: false,
445
477
  });
446
478
  function cpuLdexp(_e1, _e2) {
@@ -473,6 +505,7 @@ export const ldexp = dualImpl({
473
505
  sideEffects: false,
474
506
  });
475
507
  function cpuLength(value) {
508
+ assertKind(value, floatKind);
476
509
  if (typeof value === 'number') {
477
510
  return Math.abs(value);
478
511
  }
@@ -495,6 +528,7 @@ export const length = dualImpl({
495
528
  sideEffects: false,
496
529
  });
497
530
  function cpuLog(value) {
531
+ assertKind(value, floatKind);
498
532
  return generalizeFn(Math.log, [value]);
499
533
  }
500
534
  export const log = dualImpl({
@@ -505,6 +539,7 @@ export const log = dualImpl({
505
539
  sideEffects: false,
506
540
  });
507
541
  function cpuLog2(value) {
542
+ assertKind(value, floatKind);
508
543
  return generalizeFn(Math.log2, [value]);
509
544
  }
510
545
  export const log2 = dualImpl({
@@ -515,6 +550,8 @@ export const log2 = dualImpl({
515
550
  sideEffects: false,
516
551
  });
517
552
  function cpuMax(a, b) {
553
+ assertKind([a, b], numericKind);
554
+ assertEqualKinds(a, b);
518
555
  return generalizeFn(Math.max, [a, b]);
519
556
  }
520
557
  export const max = dualImpl({
@@ -525,6 +562,8 @@ export const max = dualImpl({
525
562
  sideEffects: false,
526
563
  });
527
564
  function cpuMin(a, b) {
565
+ assertKind([a, b], numericKind);
566
+ assertEqualKinds(a, b);
528
567
  return generalizeFn(Math.min, [a, b]);
529
568
  }
530
569
  export const min = dualImpl({
@@ -535,6 +574,13 @@ export const min = dualImpl({
535
574
  sideEffects: false,
536
575
  });
537
576
  function cpuMix(e1, e2, e3) {
577
+ assertKind([e1, e2, e3], floatKind);
578
+ if (typeof e3 === 'number') {
579
+ assertEqualKinds(e1, e2);
580
+ }
581
+ else {
582
+ assertEqualKinds(e1, e2, e3);
583
+ }
538
584
  return generalizeFn((e1, e2, e3) => e1 * (1 - e3) + e2 * e3, [e1, ...upCast([e2, e3])]);
539
585
  }
540
586
  export const mix = dualImpl({
@@ -588,6 +634,7 @@ export const normalize = dualImpl({
588
634
  name: 'normalize',
589
635
  signature: unifyRestrictedSignature(anyFloatVec),
590
636
  normalImpl: (v) => {
637
+ assertKind(v, floatKind);
591
638
  const len = length(v);
592
639
  return generalizeFn((e) => e / len, [v]);
593
640
  },
@@ -595,6 +642,8 @@ export const normalize = dualImpl({
595
642
  sideEffects: false,
596
643
  });
597
644
  function powCpu(base, exponent) {
645
+ assertKind([base, exponent], floatKind);
646
+ assertEqualKinds(base, exponent);
598
647
  return generalizeFn((a, b) => a ** b, [base, exponent]);
599
648
  }
600
649
  export const pow = dualImpl({
@@ -622,6 +671,7 @@ export const quantizeToF16 = dualImpl({
622
671
  sideEffects: false,
623
672
  });
624
673
  function cpuRadians(value) {
674
+ assertKind(value, floatKind);
625
675
  if (typeof value === 'number') {
626
676
  return ((value * Math.PI) / 180);
627
677
  }
@@ -646,7 +696,11 @@ export const reflect = dualImpl({
646
696
  returnType: uargs[0],
647
697
  };
648
698
  },
649
- normalImpl: (e1, e2) => sub(e1, mul(2 * dot(e2, e1), e2)),
699
+ normalImpl: (e1, e2) => {
700
+ assertKind([e1, e2], floatKind, true);
701
+ assertEqualKinds(e1, e2);
702
+ return sub(e1, mul(2 * dot(e2, e1), e2));
703
+ },
650
704
  codegenImpl: (_ctx, [e1, e2]) => stitch `reflect(${e1}, ${e2})`,
651
705
  sideEffects: false,
652
706
  });
@@ -671,6 +725,7 @@ export const reverseBits = dualImpl({
671
725
  sideEffects: false,
672
726
  });
673
727
  function cpuRound(value) {
728
+ assertKind(value, floatKind);
674
729
  if (typeof value === 'number') {
675
730
  const floor = Math.floor(value);
676
731
  if (value === floor + 0.5) {
@@ -691,6 +746,7 @@ export const round = dualImpl({
691
746
  sideEffects: false,
692
747
  });
693
748
  function cpuSaturate(value) {
749
+ assertKind(value, floatKind);
694
750
  if (typeof value === 'number') {
695
751
  return Math.max(0, Math.min(1, value));
696
752
  }
@@ -704,6 +760,7 @@ export const saturate = dualImpl({
704
760
  sideEffects: false,
705
761
  });
706
762
  function cpuSign(e) {
763
+ assertKind(e, signedKind);
707
764
  return generalizeFn(Math.sign, [e]);
708
765
  }
709
766
  export const sign = dualImpl({
@@ -721,6 +778,7 @@ export const sign = dualImpl({
721
778
  sideEffects: false,
722
779
  });
723
780
  function cpuSin(value) {
781
+ assertKind(value, floatKind);
724
782
  return generalizeFn(Math.sin, [value]);
725
783
  }
726
784
  export const sin = dualImpl({
@@ -731,6 +789,7 @@ export const sin = dualImpl({
731
789
  sideEffects: false,
732
790
  });
733
791
  function cpuSinh(value) {
792
+ assertKind(value, floatKind);
734
793
  return generalizeFn(Math.sinh, [value]);
735
794
  }
736
795
  export const sinh = dualImpl({
@@ -741,6 +800,8 @@ export const sinh = dualImpl({
741
800
  sideEffects: false,
742
801
  });
743
802
  function cpuSmoothstep(edge0, edge1, x) {
803
+ assertKind([edge0, edge1, x], floatKind);
804
+ assertEqualKinds(edge0, edge1, x);
744
805
  return generalizeFn(smoothstepScalar, [edge0, edge1, x]);
745
806
  }
746
807
  export const smoothstep = dualImpl({
@@ -751,6 +812,7 @@ export const smoothstep = dualImpl({
751
812
  sideEffects: false,
752
813
  });
753
814
  function cpuSqrt(value) {
815
+ assertKind(value, floatKind);
754
816
  return generalizeFn(Math.sqrt, [value]);
755
817
  }
756
818
  export const sqrt = dualImpl({
@@ -761,6 +823,8 @@ export const sqrt = dualImpl({
761
823
  sideEffects: false,
762
824
  });
763
825
  function cpuStep(edge, x) {
826
+ assertKind([edge, x], floatKind);
827
+ assertEqualKinds(edge, x);
764
828
  if (typeof edge === 'number') {
765
829
  return (edge <= x ? 1.0 : 0.0);
766
830
  }
@@ -774,6 +838,7 @@ export const step = dualImpl({
774
838
  sideEffects: false,
775
839
  });
776
840
  function cpuTan(value) {
841
+ assertKind(value, floatKind);
777
842
  if (typeof value === 'number') {
778
843
  return Math.tan(value);
779
844
  }
@@ -787,6 +852,7 @@ export const tan = dualImpl({
787
852
  sideEffects: false,
788
853
  });
789
854
  function cpuTanh(value) {
855
+ assertKind(value, floatKind);
790
856
  return generalizeFn(Math.tanh, [value]);
791
857
  }
792
858
  export const tanh = dualImpl({
@@ -797,6 +863,7 @@ export const tanh = dualImpl({
797
863
  sideEffects: false,
798
864
  });
799
865
  function cpuTranspose(value) {
866
+ assertKind(value, matrixKind);
800
867
  const schema = WORKAROUND_getSchema(value);
801
868
  // NOTE: This assumes all matrices are square
802
869
  const transposed = schema();
@@ -1,4 +1,4 @@
1
- import { type AnyIntegerVecInstance, type AnyMatInstance, type AnyNumericVecInstance, type mBaseForVec, type vBaseForMat, type vecIToVecU } from '../data/wgslTypes.ts';
1
+ import { type AnyIntegerVecInstance, type AnyMatInstance, type AnyNumericVecInstance, type AnySignedVecInstance, type mBaseForVec, type vBaseForMat, type vecIToVecU } from '../data/wgslTypes.ts';
2
2
  type NumVec = AnyNumericVecInstance;
3
3
  type Mat = AnyMatInstance;
4
4
  declare function cpuAdd(lhs: number, rhs: number): number;
@@ -39,7 +39,7 @@ type ModOverload = {
39
39
  */
40
40
  export declare const mod: import("../types.ts").DualFn<ModOverload>;
41
41
  declare function cpuNeg(value: number): number;
42
- declare function cpuNeg<T extends NumVec>(value: T): T;
42
+ declare function cpuNeg<T extends AnySignedVecInstance>(value: T): T;
43
43
  export declare const neg: import("../types.ts").DualFn<typeof cpuNeg>;
44
44
  declare function cpuBitShiftLeft<T extends AnyIntegerVecInstance>(lhs: T, rhs: number | vecIToVecU<T>): T;
45
45
  export declare const bitShiftLeft: import("../types.ts").DualFn<typeof cpuBitShiftLeft>;
package/std/operators.js CHANGED
@@ -3,9 +3,9 @@ import { stitch } from "../core/resolve/stitch.js";
3
3
  import { abstractFloat, f16, f32, u32 } from "../data/numeric.js";
4
4
  import { vec2i, vec2u, vec3i, vec3u, vec4i, vec4u } from "../data/vector.js";
5
5
  import { VectorOps } from "../data/vectorOps.js";
6
- import { generalizeFn, upCast } from "../data/generalizeFn.js";
7
- import { isFloat32VecInstance, isInteger32VecInstance, isMat, isMatInstance, isUint32VecInstance, isVec, isVecInstance, } from "../data/wgslTypes.js";
8
- import { SignatureNotSupportedError } from "../errors.js";
6
+ import { generalizeFn, kindOf, numericKind, numericOrMatrixKind, signedKind, upCast, assertEqualKinds, assertKind, } from "../data/generalizeFn.js";
7
+ import { isFloat32VecInstance, isMat, isMatInstance, isVec, isVecInstance, isInteger32VecInstance, isUint32VecInstance, } from "../data/wgslTypes.js";
8
+ import { SignatureNotSupportedError, WgslTypeError } from "../errors.js";
9
9
  import { unify } from "../tgsl/conversion.js";
10
10
  const getPrimitive = (t) => ('primitive' in t ? t.primitive : t);
11
11
  const makeBinarySignature = (opts) => (lhs, rhs) => {
@@ -56,19 +56,15 @@ const binaryDivSignature = makeBinarySignature({
56
56
  restrict: [f32, f16, abstractFloat],
57
57
  });
58
58
  function cpuAdd(lhs, rhs) {
59
- if (typeof lhs === 'number' && typeof rhs === 'number') {
60
- return lhs + rhs; // default addition
61
- }
62
- if (typeof lhs === 'number' && isVecInstance(rhs)) {
63
- return generalizeFn((e) => lhs + e, [rhs]); // mixed addition
64
- }
65
- if (isVecInstance(lhs) && typeof rhs === 'number') {
66
- return generalizeFn((e) => e + rhs, [lhs]); // mixed addition
59
+ assertKind([lhs, rhs], numericOrMatrixKind);
60
+ if (isMatInstance(lhs) !== isMatInstance(rhs)) {
61
+ throw new WgslTypeError('There is no matrix/non-matrix addition or subtraction in WGSL.');
67
62
  }
68
- if ((isVecInstance(lhs) && isVecInstance(rhs)) || (isMatInstance(lhs) && isMatInstance(rhs))) {
69
- return generalizeFn((a, b) => a + b, [lhs, rhs]); // component-wise addition
63
+ if ((typeof lhs === 'number') === (typeof rhs === 'number')) {
64
+ // If exactly one is a number, then it's fine, since we already know the other one is not a matrix.
65
+ assertEqualKinds(lhs, rhs);
70
66
  }
71
- throw new Error('Add/Sub called with invalid arguments.');
67
+ return generalizeFn((a, b) => a + b, upCast([lhs, rhs]));
72
68
  }
73
69
  export const add = dualImpl({
74
70
  name: 'add',
@@ -89,6 +85,7 @@ export const sub = dualImpl({
89
85
  sideEffects: false,
90
86
  });
91
87
  function cpuMul(lhs, rhs) {
88
+ assertKind([lhs, rhs], numericOrMatrixKind);
92
89
  if (typeof lhs === 'number' && typeof rhs === 'number') {
93
90
  return lhs * rhs; // default multiplication
94
91
  }
@@ -99,18 +96,26 @@ function cpuMul(lhs, rhs) {
99
96
  return generalizeFn((e) => e * rhs, [lhs]); // scale
100
97
  }
101
98
  if (isVecInstance(lhs) && isVecInstance(rhs)) {
99
+ assertEqualKinds(lhs, rhs);
102
100
  return generalizeFn((a, b) => a * b, [lhs, rhs]); // component-wise
103
101
  }
104
102
  if (isFloat32VecInstance(lhs) && isMatInstance(rhs)) {
103
+ if (lhs.length !== rhs.columns.length) {
104
+ throw new WgslTypeError(`Unsupported signature. Kind '${kindOf(lhs)}' cannot be multiplied by '${kindOf(rhs)}'.`);
105
+ }
105
106
  return VectorOps.mulVxM[rhs.kind](lhs, rhs); // row-vector-matrix
106
107
  }
107
108
  if (isMatInstance(lhs) && isFloat32VecInstance(rhs)) {
109
+ if (lhs.columns.length !== rhs.length) {
110
+ throw new WgslTypeError(`Unsupported signature. Kind '${kindOf(lhs)}' cannot be multiplied by '${kindOf(rhs)}'.`);
111
+ }
108
112
  return VectorOps.mulMxV[lhs.kind](lhs, rhs); // matrix-column-vector
109
113
  }
110
114
  if (isMatInstance(lhs) && isMatInstance(rhs)) {
115
+ assertEqualKinds(lhs, rhs);
111
116
  return VectorOps.mulMxM[lhs.kind](lhs, rhs); // matrix multiplication
112
117
  }
113
- throw new Error('Mul called with invalid arguments.');
118
+ throw new WgslTypeError(`Unsupported signature. Kind '${kindOf(lhs)}' cannot be multiplied by '${kindOf(rhs)}'.`);
114
119
  }
115
120
  export const mul = dualImpl({
116
121
  name: 'mul',
@@ -120,7 +125,10 @@ export const mul = dualImpl({
120
125
  sideEffects: false,
121
126
  });
122
127
  function cpuDiv(lhs, rhs) {
123
- return generalizeFn((a, b) => a / b, upCast([lhs, rhs]));
128
+ assertKind([lhs, rhs], numericKind);
129
+ const cast = upCast([lhs, rhs]);
130
+ assertEqualKinds(...cast);
131
+ return generalizeFn((a, b) => a / b, cast);
124
132
  }
125
133
  export const div = dualImpl({
126
134
  name: 'div',
@@ -138,12 +146,16 @@ export const mod = dualImpl({
138
146
  name: 'mod',
139
147
  signature: binaryDivSignature,
140
148
  normalImpl: ((a, b) => {
141
- return generalizeFn((a, b) => a % b, upCast([a, b]));
149
+ assertKind([a, b], numericKind);
150
+ const cast = upCast([a, b]);
151
+ assertEqualKinds(...cast);
152
+ return generalizeFn((a, b) => a % b, cast);
142
153
  }),
143
154
  codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '%', rhs),
144
155
  sideEffects: false,
145
156
  });
146
157
  function cpuNeg(value) {
158
+ assertKind(value, signedKind);
147
159
  return generalizeFn((value) => -value, [value]);
148
160
  }
149
161
  export const neg = dualImpl({