mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -05:00
fix(webgpu): align error scopes to spec (#9797)
This commit is contained in:
parent
7fc0e8ec8c
commit
b091b8fefb
2 changed files with 112 additions and 54 deletions
|
@ -17,6 +17,7 @@
|
||||||
ArrayBuffer,
|
ArrayBuffer,
|
||||||
ArrayBufferIsView,
|
ArrayBufferIsView,
|
||||||
ArrayIsArray,
|
ArrayIsArray,
|
||||||
|
ArrayPrototypeFilter,
|
||||||
ArrayPrototypeMap,
|
ArrayPrototypeMap,
|
||||||
ArrayPrototypePop,
|
ArrayPrototypePop,
|
||||||
ArrayPrototypePush,
|
ArrayPrototypePush,
|
||||||
|
@ -25,6 +26,10 @@
|
||||||
ObjectDefineProperty,
|
ObjectDefineProperty,
|
||||||
ObjectFreeze,
|
ObjectFreeze,
|
||||||
Promise,
|
Promise,
|
||||||
|
PromiseAll,
|
||||||
|
PromisePrototypeCatch,
|
||||||
|
PromisePrototypeThen,
|
||||||
|
PromiseReject,
|
||||||
PromiseResolve,
|
PromiseResolve,
|
||||||
Set,
|
Set,
|
||||||
SetPrototypeEntries,
|
SetPrototypeEntries,
|
||||||
|
@ -147,12 +152,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
class GPUOutOfMemoryError extends Error {
|
class GPUOutOfMemoryError extends Error {
|
||||||
|
name = "GPUOutOfMemoryError";
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super("device out of memory");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GPUValidationError extends Error {
|
class GPUValidationError extends Error {
|
||||||
|
name = "GPUValidationError";
|
||||||
/** @param {string} message */
|
/** @param {string} message */
|
||||||
constructor(message) {
|
constructor(message) {
|
||||||
const prefix = "Failed to construct 'GPUValidationError'";
|
const prefix = "Failed to construct 'GPUValidationError'";
|
||||||
|
@ -266,7 +273,7 @@
|
||||||
});
|
});
|
||||||
const nonGuaranteedFeatures = descriptor.nonGuaranteedFeatures ?? [];
|
const nonGuaranteedFeatures = descriptor.nonGuaranteedFeatures ?? [];
|
||||||
for (const feature of nonGuaranteedFeatures) {
|
for (const feature of nonGuaranteedFeatures) {
|
||||||
if (!SetPrototypeHas(this[_adapter].features, feature)) {
|
if (!SetPrototypeHas(this[_adapter].features[_features], feature)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`${prefix}: nonGuaranteedFeatures must be a subset of the adapter features.`,
|
`${prefix}: nonGuaranteedFeatures must be a subset of the adapter features.`,
|
||||||
);
|
);
|
||||||
|
@ -562,7 +569,7 @@
|
||||||
/**
|
/**
|
||||||
* @typedef ErrorScope
|
* @typedef ErrorScope
|
||||||
* @property {string} filter
|
* @property {string} filter
|
||||||
* @property {GPUError | undefined} error
|
* @property {Promise<void>[]} operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -617,42 +624,73 @@
|
||||||
|
|
||||||
/** @param {{ type: string, value: string | null } | undefined} err */
|
/** @param {{ type: string, value: string | null } | undefined} err */
|
||||||
pushError(err) {
|
pushError(err) {
|
||||||
if (err) {
|
this.pushErrorPromise(PromiseResolve(err));
|
||||||
switch (err.type) {
|
}
|
||||||
case "lost":
|
|
||||||
this.isLost = true;
|
/** @param {Promise<{ type: string, value: string | null } | undefined>} promise */
|
||||||
this.resolveLost(
|
pushErrorPromise(promise) {
|
||||||
createGPUDeviceLostInfo(undefined, "device was lost"),
|
const operation = PromisePrototypeThen(promise, (err) => {
|
||||||
);
|
if (err) {
|
||||||
break;
|
switch (err.type) {
|
||||||
case "validation":
|
case "lost":
|
||||||
case "out-of-memory":
|
this.isLost = true;
|
||||||
for (
|
this.resolveLost(
|
||||||
let i = this.errorScopeStack.length - 1;
|
createGPUDeviceLostInfo(undefined, "device was lost"),
|
||||||
i >= 0;
|
);
|
||||||
i--
|
break;
|
||||||
) {
|
case "validation":
|
||||||
const scope = this.errorScopeStack[i];
|
return PromiseReject(
|
||||||
if (scope.filter == err.type) {
|
new GPUValidationError(err.value ?? "validation error"),
|
||||||
if (!scope.error) {
|
);
|
||||||
switch (err.type) {
|
case "out-of-memory":
|
||||||
case "validation":
|
return PromiseReject(new GPUOutOfMemoryError());
|
||||||
scope.error = new GPUValidationError(
|
}
|
||||||
err.value ?? "validation error",
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "out-of-memory":
|
|
||||||
scope.error = new GPUOutOfMemoryError();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO(lucacasonato): emit a UncapturedErrorEvent
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const validationStack = ArrayPrototypeFilter(
|
||||||
|
this.errorScopeStack,
|
||||||
|
({ filter }) => filter == "validation",
|
||||||
|
);
|
||||||
|
const validationScope = validationStack[validationStack.length - 1];
|
||||||
|
const validationFilteredPromise = PromisePrototypeCatch(
|
||||||
|
operation,
|
||||||
|
(err) => {
|
||||||
|
if (err instanceof GPUValidationError) return PromiseReject(err);
|
||||||
|
return PromiseResolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (validationScope) {
|
||||||
|
ArrayPrototypePush(
|
||||||
|
validationScope.operations,
|
||||||
|
validationFilteredPromise,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
PromisePrototypeCatch(validationFilteredPromise, () => {
|
||||||
|
// TODO(lucacasonato): emit an UncapturedErrorEvent
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// prevent uncaptured promise rejections
|
||||||
|
PromisePrototypeCatch(validationFilteredPromise, (_err) => {});
|
||||||
|
|
||||||
|
const oomStack = ArrayPrototypeFilter(
|
||||||
|
this.errorScopeStack,
|
||||||
|
({ filter }) => filter == "out-of-memory",
|
||||||
|
);
|
||||||
|
const oomScope = oomStack[oomStack.length - 1];
|
||||||
|
const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => {
|
||||||
|
if (err instanceof GPUOutOfMemoryError) return PromiseReject(err);
|
||||||
|
return PromiseResolve();
|
||||||
|
});
|
||||||
|
if (oomScope) {
|
||||||
|
ArrayPrototypePush(oomScope.operations, oomFilteredPromise);
|
||||||
|
} else {
|
||||||
|
PromisePrototypeCatch(oomFilteredPromise, () => {
|
||||||
|
// TODO(lucacasonato): emit an UncapturedErrorEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// prevent uncaptured promise rejections
|
||||||
|
PromisePrototypeCatch(oomFilteredPromise, (_err) => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1296,7 +1334,7 @@
|
||||||
context: "Argument 1",
|
context: "Argument 1",
|
||||||
});
|
});
|
||||||
const device = assertDevice(this, { prefix, context: "this" });
|
const device = assertDevice(this, { prefix, context: "this" });
|
||||||
ArrayPrototypePush(device.errorScopeStack, { filter, error: undefined });
|
ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1305,7 +1343,7 @@
|
||||||
// deno-lint-ignore require-await
|
// deno-lint-ignore require-await
|
||||||
async popErrorScope() {
|
async popErrorScope() {
|
||||||
webidl.assertBranded(this, GPUDevice);
|
webidl.assertBranded(this, GPUDevice);
|
||||||
const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'";
|
const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'";
|
||||||
const device = assertDevice(this, { prefix, context: "this" });
|
const device = assertDevice(this, { prefix, context: "this" });
|
||||||
if (device.isLost) {
|
if (device.isLost) {
|
||||||
throw new DOMException("Device has been lost.", "OperationError");
|
throw new DOMException("Device has been lost.", "OperationError");
|
||||||
|
@ -1313,11 +1351,16 @@
|
||||||
const scope = ArrayPrototypePop(device.errorScopeStack);
|
const scope = ArrayPrototypePop(device.errorScopeStack);
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
throw new DOMException(
|
throw new DOMException(
|
||||||
"There are no error scopes on that stack.",
|
"There are no error scopes on the error scope stack.",
|
||||||
"OperationError",
|
"OperationError",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return scope.error ?? null;
|
const operations = PromiseAll(scope.operations);
|
||||||
|
return PromisePrototypeThen(
|
||||||
|
operations,
|
||||||
|
() => PromiseResolve(null),
|
||||||
|
(err) => PromiseResolve(err),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SymbolFor("Deno.privateCustomInspect")](inspect) {
|
[SymbolFor("Deno.privateCustomInspect")](inspect) {
|
||||||
|
@ -1686,17 +1729,24 @@
|
||||||
|
|
||||||
this[_mapMode] = mode;
|
this[_mapMode] = mode;
|
||||||
this[_state] = "mapping pending";
|
this[_state] = "mapping pending";
|
||||||
const { err } = await core.opAsync(
|
const promise = PromisePrototypeThen(
|
||||||
"op_webgpu_buffer_get_map_async",
|
core.opAsync(
|
||||||
{
|
"op_webgpu_buffer_get_map_async",
|
||||||
bufferRid,
|
{
|
||||||
deviceRid: device.rid,
|
bufferRid,
|
||||||
mode,
|
deviceRid: device.rid,
|
||||||
offset,
|
mode,
|
||||||
size: rangeSize,
|
offset,
|
||||||
},
|
size: rangeSize,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
({ err }) => err,
|
||||||
);
|
);
|
||||||
device.pushError(err);
|
device.pushErrorPromise(promise);
|
||||||
|
const err = await promise;
|
||||||
|
if (err) {
|
||||||
|
throw new DOMException("validation error occured", "OperationError");
|
||||||
|
}
|
||||||
this[_state] = "mapped";
|
this[_state] = "mapped";
|
||||||
this[_mappingRange] = [offset, offset + rangeSize];
|
this[_mappingRange] = [offset, offset + rangeSize];
|
||||||
/** @type {[ArrayBuffer, number, number][] | null} */
|
/** @type {[ArrayBuffer, number, number][] | null} */
|
||||||
|
@ -1729,6 +1779,7 @@
|
||||||
} else {
|
} else {
|
||||||
rangeSize = size;
|
rangeSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedRanges = this[_mappedRanges];
|
const mappedRanges = this[_mappedRanges];
|
||||||
if (!mappedRanges) {
|
if (!mappedRanges) {
|
||||||
throw new DOMException(`${prefix}: invalid state.`, "OperationError");
|
throw new DOMException(`${prefix}: invalid state.`, "OperationError");
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use deno_core::error::bad_resource_id;
|
use deno_core::error::bad_resource_id;
|
||||||
use deno_core::error::null_opbuf;
|
use deno_core::error::null_opbuf;
|
||||||
|
use deno_core::error::type_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::futures::channel::oneshot;
|
use deno_core::futures::channel::oneshot;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
@ -56,7 +57,8 @@ pub fn op_webgpu_create_buffer(
|
||||||
let descriptor = wgpu_core::resource::BufferDescriptor {
|
let descriptor = wgpu_core::resource::BufferDescriptor {
|
||||||
label: args.label.map(Cow::from),
|
label: args.label.map(Cow::from),
|
||||||
size: args.size,
|
size: args.size,
|
||||||
usage: wgpu_types::BufferUsage::from_bits(args.usage).unwrap(),
|
usage: wgpu_types::BufferUsage::from_bits(args.usage)
|
||||||
|
.ok_or_else(|| type_error("usage is not valid"))?,
|
||||||
mapped_at_creation: args.mapped_at_creation.unwrap_or(false),
|
mapped_at_creation: args.mapped_at_creation.unwrap_or(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,7 +119,7 @@ pub async fn op_webgpu_buffer_get_map_async(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(lucacasonato): error handling
|
// TODO(lucacasonato): error handling
|
||||||
gfx_select!(buffer => instance.buffer_map_async(
|
let maybe_err = gfx_select!(buffer => instance.buffer_map_async(
|
||||||
buffer,
|
buffer,
|
||||||
args.offset..(args.offset + args.size),
|
args.offset..(args.offset + args.size),
|
||||||
wgpu_core::resource::BufferMapOperation {
|
wgpu_core::resource::BufferMapOperation {
|
||||||
|
@ -129,7 +131,12 @@ pub async fn op_webgpu_buffer_get_map_async(
|
||||||
callback: buffer_map_future_wrapper,
|
callback: buffer_map_future_wrapper,
|
||||||
user_data: sender_ptr,
|
user_data: sender_ptr,
|
||||||
}
|
}
|
||||||
))?;
|
))
|
||||||
|
.err();
|
||||||
|
|
||||||
|
if maybe_err.is_some() {
|
||||||
|
return Ok(WebGpuResult::maybe_err(maybe_err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let done = Rc::new(RefCell::new(false));
|
let done = Rc::new(RefCell::new(false));
|
||||||
|
|
Loading…
Add table
Reference in a new issue