Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "enforceDefaultLimits" flag #4124

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/common/framework/test_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export type TestConfig = {
*/
forceFallbackAdapter: boolean;

/**
* Enforce the default limits on the adapter
*/
enforceDefaultLimits: boolean;

/**
* Whether to enable the `logToWebSocket` function used for out-of-band test logging.
*/
Expand All @@ -59,5 +64,6 @@ export const globalTestConfig: TestConfig = {
unrollConstEvalLoops: false,
compatibility: false,
forceFallbackAdapter: false,
enforceDefaultLimits: false,
logToWebSocket: false,
};
2 changes: 2 additions & 0 deletions src/common/runtime/cmdline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ for (let i = 0; i < sys.args.length; ++i) {
globalTestConfig.compatibility = true;
} else if (a === '--force-fallback-adapter') {
globalTestConfig.forceFallbackAdapter = true;
} else if (a === '--enforce-default-limits') {
globalTestConfig.enforceDefaultLimits = true;
} else if (a === '--log-to-websocket') {
globalTestConfig.logToWebSocket = true;
} else {
Expand Down
6 changes: 6 additions & 0 deletions src/common/runtime/helper/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface CTSOptions {
debug: boolean;
compatibility: boolean;
forceFallbackAdapter: boolean;
enforceDefaultLimits: boolean;
unrollConstEvalLoops: boolean;
powerPreference: GPUPowerPreference | null;
logToWebSocket: boolean;
Expand All @@ -63,6 +64,7 @@ export const kDefaultCTSOptions: CTSOptions = {
debug: true,
compatibility: false,
forceFallbackAdapter: false,
enforceDefaultLimits: false,
unrollConstEvalLoops: false,
powerPreference: null,
logToWebSocket: false,
Expand Down Expand Up @@ -100,6 +102,10 @@ export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
debug: { description: 'show more info' },
compatibility: { description: 'run in compatibility mode' },
forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' },
enforceDefaultLimits: {
description: `force the adapter limits to the default limits.
Note: May fail on tests for low-power/high-performance`,
},
unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' },
powerPreference: {
description: 'set default powerPreference for some tests',
Expand Down
1 change: 1 addition & 0 deletions src/common/runtime/helper/utils_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function setupWorkerEnvironment(ctsOptions: CTSOptions): Logger {
globalTestConfig.enableDebugLogs = ctsOptions.debug;
globalTestConfig.unrollConstEvalLoops = ctsOptions.unrollConstEvalLoops;
globalTestConfig.compatibility = compatibility;
globalTestConfig.enforceDefaultLimits = ctsOptions.enforceDefaultLimits;
globalTestConfig.logToWebSocket = ctsOptions.logToWebSocket;

const log = new Logger();
Expand Down
2 changes: 2 additions & 0 deletions src/common/runtime/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ for (let i = 0; i < sys.args.length; ++i) {
emitCoverage = true;
} else if (a === '--force-fallback-adapter') {
globalTestConfig.forceFallbackAdapter = true;
} else if (a === '--enforce-default-limits') {
globalTestConfig.enforceDefaultLimits = true;
} else if (a === '--log-to-websocket') {
globalTestConfig.logToWebSocket = true;
} else if (a === '--gpu-provider') {
Expand Down
1 change: 1 addition & 0 deletions src/common/runtime/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const { runnow, powerPreference, compatibility, forceFallbackAdapter } = options
globalTestConfig.enableDebugLogs = options.debug;
globalTestConfig.unrollConstEvalLoops = options.unrollConstEvalLoops;
globalTestConfig.compatibility = compatibility;
globalTestConfig.enforceDefaultLimits = options.enforceDefaultLimits;
globalTestConfig.logToWebSocket = options.logToWebSocket;

const logger = new Logger();
Expand Down
84 changes: 84 additions & 0 deletions src/common/util/navigator_gpu.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// eslint-disable-next-line import/no-restricted-paths
import { TestCaseRecorder } from '../framework/fixture.js';
import { globalTestConfig } from '../framework/test_config.js';

import { ErrorWithExtra, assert, objectEquals } from './util.js';

Expand Down Expand Up @@ -31,6 +33,7 @@ export function setGPUProvider(provider: GPUProvider) {
}

let impl: GPU | undefined = undefined;
let s_defaultLimits: Record<string, GPUSize64> | undefined = undefined;

let defaultRequestAdapterOptions: GPURequestAdapterOptions | undefined;

Expand All @@ -49,6 +52,14 @@ export function getDefaultRequestAdapterOptions() {
return defaultRequestAdapterOptions;
}

function copyLimits(objLike: GPUSupportedLimits) {
const obj: Record<string, number> = {};
for (const key in objLike) {
obj[key] = (objLike as unknown as Record<string, number>)[key];
}
return obj;
}

/**
* Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations).
* Throws an exception if not found.
Expand All @@ -60,6 +71,79 @@ export function getGPU(recorder: TestCaseRecorder | null): GPU {

impl = gpuProvider();

if (globalTestConfig.enforceDefaultLimits) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const origRequestAdapterFn = impl.requestAdapter;
// eslint-disable-next-line @typescript-eslint/unbound-method
const origRequestDeviceFn = GPUAdapter.prototype.requestDevice;

impl.requestAdapter = async function (options?: GPURequestAdapterOptions) {
if (!s_defaultLimits) {
const tempAdapter = await origRequestAdapterFn.call(this, {
...defaultRequestAdapterOptions,
...options,
});
// eslint-disable-next-line no-restricted-syntax
const tempDevice = await tempAdapter?.requestDevice();
s_defaultLimits = copyLimits(tempDevice!.limits);
tempDevice?.destroy();
}
const adapter = await origRequestAdapterFn.call(this, {
...defaultRequestAdapterOptions,
...options,
});
if (adapter) {
const limits = Object.fromEntries(
Object.entries(s_defaultLimits).map(([key, v]) => [key, v])
);

Object.defineProperty(adapter, 'limits', {
get() {
return limits;
},
});
}
return adapter;
};

const enforceDefaultLimits = (adapter: GPUAdapter, desc: GPUDeviceDescriptor | undefined) => {
if (desc?.requiredLimits) {
for (const [key, value] of Object.entries(desc.requiredLimits)) {
const limit = s_defaultLimits![key];
if (limit !== undefined && value !== undefined) {
const [beyondLimit, condition] = key.startsWith('max')
? [value > limit, 'greater']
: [value < limit, 'less'];
if (beyondLimit) {
throw new DOMException(
`requestedLimit ${value} for ${key} is ${condition} than adapter limit ${limit}`,
'OperationError'
);
}
}
}
}
};

GPUAdapter.prototype.requestDevice = async function (
this: GPUAdapter,
desc?: GPUDeviceDescriptor | undefined
) {
// We need to enforce the default limits because even though we patched the adapter to
// show defaults for adapter.limits, there are tests that test we throw when we request more than the max.
// In other words.
//
// adapter.requestDevice({ requiredLimits: {
// maxXXX: addapter.limits.maxXXX + 1, // should throw
// });
//
// But unless we enforce this manually, it won't actual through if the adapter's
// true limits are higher than we patched above.
enforceDefaultLimits(this, desc);
return await origRequestDeviceFn.call(this, desc);
};
}

if (defaultRequestAdapterOptions) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const oldFn = impl.requestAdapter;
Expand Down
Loading
Loading