1
0
Fork 0
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:
Luca Casonato 2021-07-07 14:38:24 +02:00 committed by GitHub
parent 7fc0e8ec8c
commit b091b8fefb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 54 deletions

View file

@ -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");

View file

@ -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));