Bug 1837557 - Productionize webgpu pushErrorScope/popErrorScope. r=webgpu-reviewers,jimb

Differential Revision: https://phabricator.services.mozilla.com/D180456
This commit is contained in:
Kelsey Gilbert 2023-06-14 05:51:00 +00:00
parent 6b065a2607
commit 8ce110a8e9
10 changed files with 222 additions and 106 deletions

View File

@ -88,10 +88,12 @@ bool Device::IsLost() const { return !mBridge || !mBridge->CanSend(); }
// Generate an error on the Device timeline for this device.
//
// aMessage is interpreted as UTF-8.
void Device::GenerateError(const nsCString& aMessage) {
if (mBridge->CanSend()) {
mBridge->SendGenerateError(mId, aMessage);
void Device::GenerateValidationError(const nsCString& aMessage) {
if (IsLost()) {
return; // Just drop it?
}
mBridge->SendGenerateError(Some(mId), dom::GPUErrorFilter::Validation,
aMessage);
}
void Device::GetLabel(nsAString& aValue) const { aValue = mLabel; }
@ -322,19 +324,30 @@ void Device::Destroy() {
}
void Device::PushErrorScope(const dom::GPUErrorFilter& aFilter) {
if (mBridge->CanSend()) {
mBridge->SendDevicePushErrorScope(mId);
if (IsLost()) {
return;
}
mBridge->SendDevicePushErrorScope(mId, aFilter);
}
already_AddRefed<dom::Promise> Device::PopErrorScope(ErrorResult& aRv) {
/*
https://www.w3.org/TR/webgpu/#errors-and-debugging:
> After a device is lost (described below), errors are no longer surfaced.
> At this point, implementations do not need to run validation or error
tracking: > popErrorScope() and uncapturederror stop reporting errors, > and
the validity of objects on the device becomes unobservable.
*/
RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (!mBridge->CanSend()) {
promise->MaybeRejectWithOperationError("Internal communication error");
if (IsLost()) {
WebGPUChild::JsWarning(
GetOwnerGlobal(),
"popErrorScope resolving to null because device is already lost."_ns);
promise->MaybeResolve(JS::NullHandleValue);
return promise.forget();
}
@ -342,26 +355,50 @@ already_AddRefed<dom::Promise> Device::PopErrorScope(ErrorResult& aRv) {
errorPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}, promise](const MaybeScopedError& aMaybeError) {
if (aMaybeError) {
if (aMaybeError->operationError) {
promise->MaybeRejectWithOperationError("Stack is empty");
} else {
dom::OwningGPUOutOfMemoryErrorOrGPUValidationError error;
if (aMaybeError->validationMessage.IsEmpty()) {
error.SetAsGPUOutOfMemoryError();
} else {
error.SetAsGPUValidationError() = new ValidationError(
self->GetParentObject(), aMaybeError->validationMessage);
}
promise->MaybeResolve(std::move(error));
}
} else {
promise->MaybeResolveWithUndefined();
[self = RefPtr{this}, promise](const PopErrorScopeResult& aResult) {
dom::OwningGPUOutOfMemoryErrorOrGPUValidationError error;
switch (aResult.resultType) {
case PopErrorScopeResultType::NoError:
promise->MaybeResolve(JS::NullHandleValue);
return;
case PopErrorScopeResultType::DeviceLost:
WebGPUChild::JsWarning(
self->GetOwnerGlobal(),
"popErrorScope resolving to null because device was lost."_ns);
promise->MaybeResolve(JS::NullHandleValue);
return;
case PopErrorScopeResultType::ThrowOperationError:
promise->MaybeRejectWithOperationError(aResult.message);
return;
case PopErrorScopeResultType::OutOfMemory:
error.SetAsGPUOutOfMemoryError();
break;
case PopErrorScopeResultType::ValidationError:
error.SetAsGPUValidationError() =
new ValidationError(self->GetParentObject(), aResult.message);
break;
case PopErrorScopeResultType::InternalError:
MOZ_CRASH("TODO");
/*
error.SetAsGPUInternalError() = new InternalError(
self->GetParentObject(), aResult.message);
break;
*/
}
promise->MaybeResolve(std::move(error));
},
[promise](const ipc::ResponseRejectReason&) {
promise->MaybeRejectWithOperationError("Internal communication error");
[self = RefPtr{this}, promise](const ipc::ResponseRejectReason&) {
// Device was lost.
WebGPUChild::JsWarning(
self->GetOwnerGlobal(),
"popErrorScope resolving to null because device was just lost."_ns);
promise->MaybeResolve(JS::NullHandleValue);
});
return promise.forget();

View File

@ -100,7 +100,7 @@ class Device final : public DOMEventTargetHelper, public SupportsWeakPtr {
void CleanupUnregisteredInParent();
void GenerateError(const nsCString& aMessage);
void GenerateValidationError(const nsCString& aMessage);
bool IsLost() const;

View File

@ -131,7 +131,7 @@ ffi::WGPURenderPass* BeginRenderPass(
}
if (aDesc.mColorAttachments.Length() > WGPUMAX_COLOR_ATTACHMENTS) {
aParent->GetDevice()->GenerateError(nsLiteralCString(
aParent->GetDevice()->GenerateValidationError(nsLiteralCString(
"Too many color attachments in GPURenderPassDescriptor"));
return nullptr;
}

View File

@ -9,10 +9,11 @@ using mozilla::layers::RGBDescriptor from "mozilla/layers/LayersSurfaces.h";
using mozilla::layers::RemoteTextureId from "mozilla/layers/LayersTypes.h";
using mozilla::layers::RemoteTextureOwnerId from "mozilla/layers/LayersTypes.h";
using mozilla::webgpu::RawId from "mozilla/webgpu/WebGPUTypes.h";
using mozilla::dom::GPUErrorFilter from "mozilla/dom/WebGPUBinding.h";
using mozilla::dom::GPURequestAdapterOptions from "mozilla/dom/WebGPUBinding.h";
using mozilla::dom::GPUCommandBufferDescriptor from "mozilla/dom/WebGPUBinding.h";
using mozilla::dom::GPUBufferDescriptor from "mozilla/dom/WebGPUBinding.h";
using mozilla::webgpu::MaybeScopedError from "mozilla/webgpu/WebGPUTypes.h";
using mozilla::webgpu::PopErrorScopeResult from "mozilla/webgpu/WebGPUTypes.h";
using mozilla::webgpu::WebGPUCompilationMessage from "mozilla/webgpu/WebGPUTypes.h";
[MoveOnly] using class mozilla::ipc::UnsafeSharedMemoryHandle from "mozilla/ipc/RawShmem.h";
@ -76,15 +77,15 @@ parent:
async SwapChainPresent(RawId textureId, RawId commandEncoderId, RemoteTextureId remoteTextureId, RemoteTextureOwnerId remoteTextureOwnerId);
async SwapChainDestroy(RemoteTextureOwnerId ownerId);
async DevicePushErrorScope(RawId selfId);
async DevicePopErrorScope(RawId selfId) returns (MaybeScopedError maybeError);
async DevicePushErrorScope(RawId selfId, GPUErrorFilter aFilter);
async DevicePopErrorScope(RawId selfId) returns (PopErrorScopeResult result);
// Generate an error on the Device timeline for `deviceId`.
// The `message` parameter is interpreted as UTF-8.
async GenerateError(RawId deviceId, nsCString message);
async GenerateError(RawId? deviceId, GPUErrorFilter type, nsCString message);
child:
async DeviceUncapturedError(RawId aDeviceId, nsCString message);
async UncapturedError(RawId? aDeviceId, nsCString message);
async DropAction(ByteBuf buf);
async __delete__();
};

View File

@ -1064,25 +1064,30 @@ RefPtr<PipelinePromise> WebGPUChild::DeviceCreateRenderPipelineAsync(
});
}
ipc::IPCResult WebGPUChild::RecvDeviceUncapturedError(
RawId aDeviceId, const nsACString& aMessage) {
auto targetIter = mDeviceMap.find(aDeviceId);
if (!aDeviceId || targetIter == mDeviceMap.end()) {
ipc::IPCResult WebGPUChild::RecvUncapturedError(const Maybe<RawId> aDeviceId,
const nsACString& aMessage) {
RefPtr<Device> device;
if (aDeviceId) {
const auto itr = mDeviceMap.find(*aDeviceId);
if (itr != mDeviceMap.end()) {
device = itr->second.get();
MOZ_ASSERT(device);
}
}
if (!device) {
JsWarning(nullptr, aMessage);
} else {
auto* target = targetIter->second.get();
MOZ_ASSERT(target);
// We don't want to spam the errors to the console indefinitely
if (target->CheckNewWarning(aMessage)) {
JsWarning(target->GetOwnerGlobal(), aMessage);
if (device->CheckNewWarning(aMessage)) {
JsWarning(device->GetOwnerGlobal(), aMessage);
dom::GPUUncapturedErrorEventInit init;
init.mError.SetAsGPUValidationError() =
new ValidationError(target->GetParentObject(), aMessage);
new ValidationError(device->GetParentObject(), aMessage);
RefPtr<mozilla::dom::GPUUncapturedErrorEvent> event =
dom::GPUUncapturedErrorEvent::Constructor(
target, u"uncapturederror"_ns, init);
target->DispatchEvent(*event);
device, u"uncapturederror"_ns, init);
device->DispatchEvent(*event);
}
}
return IPC_OK();

View File

@ -116,11 +116,11 @@ class WebGPUChild final : public PWebGPUChild, public SupportsWeakPtr {
static void ConvertTextureFormatRef(const dom::GPUTextureFormat& aInput,
ffi::WGPUTextureFormat& aOutput);
static void JsWarning(nsIGlobalObject* aGlobal, const nsACString& aMessage);
private:
virtual ~WebGPUChild();
void JsWarning(nsIGlobalObject* aGlobal, const nsACString& aMessage);
RawId DeviceCreateComputePipelineImpl(
PipelineCreationContext* const aContext,
const dom::GPUComputePipelineDescriptor& aDesc,
@ -134,8 +134,8 @@ class WebGPUChild final : public PWebGPUChild, public SupportsWeakPtr {
std::unordered_map<RawId, WeakPtr<Device>> mDeviceMap;
public:
ipc::IPCResult RecvDeviceUncapturedError(RawId aDeviceId,
const nsACString& aMessage);
ipc::IPCResult RecvUncapturedError(Maybe<RawId> aDeviceId,
const nsACString& aMessage);
ipc::IPCResult RecvDropAction(const ipc::ByteBuf& aByteBuf);
void ActorDestroy(ActorDestroyReason) override;
};

View File

@ -233,34 +233,44 @@ void WebGPUParent::MaintainDevices() {
ffi::wgpu_server_poll_all_devices(mContext.get(), false);
}
bool WebGPUParent::ForwardError(RawId aDeviceId, ErrorBuffer& aError) {
bool WebGPUParent::ForwardError(const Maybe<RawId> aDeviceId,
ErrorBuffer& aError) {
// don't do anything if the error is empty
auto cString = aError.GetError();
if (!cString) {
return false;
}
ReportError(aDeviceId, cString.value());
// Risky: These are probably not all Validation errors.
ReportError(aDeviceId, dom::GPUErrorFilter::Validation, cString.value());
return true;
}
// Generate an error on the Device timeline of aDeviceId.
// aMessage is interpreted as UTF-8.
void WebGPUParent::ReportError(RawId aDeviceId, const nsCString& aMessage) {
void WebGPUParent::ReportError(const Maybe<RawId> aDeviceId,
const GPUErrorFilter aType,
const nsCString& aMessage) {
// find the appropriate error scope
const auto& lookup = mErrorScopeMap.find(aDeviceId);
if (lookup != mErrorScopeMap.end() && !lookup->second.mStack.IsEmpty()) {
auto& last = lookup->second.mStack.LastElement();
if (last.isNothing()) {
last.emplace(ScopedError{false, aMessage});
}
} else {
// fall back to the uncaptured error handler
if (!SendDeviceUncapturedError(aDeviceId, aMessage)) {
NS_ERROR("Unable to SendError");
if (aDeviceId) {
const auto& itr = mErrorScopeStackByDevice.find(*aDeviceId);
if (itr != mErrorScopeStackByDevice.end()) {
auto& stack = itr->second;
for (auto& scope : Reversed(stack)) {
if (scope.filter != aType) {
continue;
}
if (!scope.firstMessage) {
scope.firstMessage = Some(aMessage);
}
return;
}
}
}
// No error scope found, so fall back to the uncaptured error handler
if (!SendUncapturedError(aDeviceId, aMessage)) {
NS_ERROR("SendDeviceUncapturedError failed");
}
}
ipc::IPCResult WebGPUParent::RecvInstanceRequestAdapter(
@ -312,7 +322,7 @@ ipc::IPCResult WebGPUParent::RecvAdapterRequestDevice(
if (ForwardError(0, error)) {
resolver(false);
} else {
mErrorScopeMap.insert({aAdapterId, ErrorScopeStack()});
mErrorScopeStackByDevice.insert({aDeviceId, {}});
resolver(true);
}
return IPC_OK();
@ -325,7 +335,7 @@ ipc::IPCResult WebGPUParent::RecvAdapterDestroy(RawId aAdapterId) {
ipc::IPCResult WebGPUParent::RecvDeviceDestroy(RawId aDeviceId) {
ffi::wgpu_server_device_drop(mContext.get(), aDeviceId);
mErrorScopeMap.erase(aDeviceId);
mErrorScopeStackByDevice.erase(aDeviceId);
return IPC_OK();
}
@ -1083,45 +1093,78 @@ ipc::IPCResult WebGPUParent::RecvBumpImplicitBindGroupLayout(RawId aPipelineId,
return IPC_OK();
}
ipc::IPCResult WebGPUParent::RecvDevicePushErrorScope(RawId aDeviceId) {
const auto& lookup = mErrorScopeMap.find(aDeviceId);
if (lookup == mErrorScopeMap.end()) {
ipc::IPCResult WebGPUParent::RecvDevicePushErrorScope(
RawId aDeviceId, const dom::GPUErrorFilter aFilter) {
const auto& itr = mErrorScopeStackByDevice.find(aDeviceId);
if (itr == mErrorScopeStackByDevice.end()) {
// Content can cause this simply by destroying a device and then
// calling `pushErrorScope`.
return IPC_OK();
}
auto& stack = itr->second;
lookup->second.mStack.EmplaceBack();
// Let's prevent `while (true) { pushErrorScope(); }`.
constexpr size_t MAX_ERROR_SCOPE_STACK_SIZE = 1'000'000;
if (stack.size() >= MAX_ERROR_SCOPE_STACK_SIZE) {
nsPrintfCString m("pushErrorScope: Hit MAX_ERROR_SCOPE_STACK_SIZE of %zu",
MAX_ERROR_SCOPE_STACK_SIZE);
ReportError(Some(aDeviceId), dom::GPUErrorFilter::Out_of_memory, m);
return IPC_OK();
}
const auto newScope = ErrorScope{aFilter};
stack.push_back(newScope);
return IPC_OK();
}
ipc::IPCResult WebGPUParent::RecvDevicePopErrorScope(
RawId aDeviceId, DevicePopErrorScopeResolver&& aResolver) {
const auto& lookup = mErrorScopeMap.find(aDeviceId);
if (lookup == mErrorScopeMap.end()) {
// Content can cause this simply by destroying a device and then
// calling `popErrorScope`.
ScopedError error = {true};
aResolver(Some(error));
return IPC_OK();
}
const auto popResult = [&]() {
const auto& itr = mErrorScopeStackByDevice.find(aDeviceId);
if (itr == mErrorScopeStackByDevice.end()) {
// Content can cause this simply by destroying a device and then
// calling `popErrorScope`.
return PopErrorScopeResult{PopErrorScopeResultType::DeviceLost};
}
if (lookup->second.mStack.IsEmpty()) {
// Content can cause this simply by calling `popErrorScope` when
// there is no error scope pushed.
ScopedError error = {true};
aResolver(Some(error));
return IPC_OK();
}
auto& stack = itr->second;
if (!stack.size()) {
// Content can cause this simply by calling `popErrorScope` when
// there is no error scope pushed.
return PopErrorScopeResult{PopErrorScopeResultType::ThrowOperationError,
"popErrorScope on empty stack"_ns};
}
auto scope = lookup->second.mStack.PopLastElement();
aResolver(scope);
const auto& scope = stack.back();
const auto popLater = MakeScopeExit([&]() { stack.pop_back(); });
auto ret = PopErrorScopeResult{PopErrorScopeResultType::NoError};
if (scope.firstMessage) {
ret.message = *scope.firstMessage;
}
switch (scope.filter) {
case dom::GPUErrorFilter::Validation:
ret.resultType = PopErrorScopeResultType::ValidationError;
break;
case dom::GPUErrorFilter::Out_of_memory:
ret.resultType = PopErrorScopeResultType::OutOfMemory;
break;
// case dom::GPUErrorFilter::Internal:
// ret.resultType = PopErrorScopeResultType::InternalError;
// break;
case dom::GPUErrorFilter::EndGuard_:
MOZ_CRASH("Bad GPUErrorFilter");
}
return ret;
}();
aResolver(popResult);
return IPC_OK();
}
ipc::IPCResult WebGPUParent::RecvGenerateError(RawId aDeviceId,
ipc::IPCResult WebGPUParent::RecvGenerateError(const Maybe<RawId> aDeviceId,
const dom::GPUErrorFilter aType,
const nsCString& aMessage) {
ReportError(aDeviceId, aMessage);
ReportError(aDeviceId, aType, aMessage);
return IPC_OK();
}

View File

@ -24,10 +24,6 @@ namespace webgpu {
class ErrorBuffer;
class PresentationData;
struct ErrorScopeStack {
nsTArray<MaybeScopedError> mStack;
};
class WebGPUParent final : public PWebGPUParent {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGPUParent, override)
@ -103,10 +99,11 @@ class WebGPUParent final : public PWebGPUParent {
uint32_t aIndex,
RawId aAssignId);
ipc::IPCResult RecvDevicePushErrorScope(RawId aDeviceId);
ipc::IPCResult RecvDevicePushErrorScope(RawId aDeviceId, dom::GPUErrorFilter);
ipc::IPCResult RecvDevicePopErrorScope(
RawId aDeviceId, DevicePopErrorScopeResolver&& aResolver);
ipc::IPCResult RecvGenerateError(RawId aDeviceId, const nsCString& message);
ipc::IPCResult RecvGenerateError(Maybe<RawId> aDeviceId, dom::GPUErrorFilter,
const nsCString& message);
ipc::IPCResult GetFrontBufferSnapshot(
IProtocol* aProtocol, const layers::RemoteTextureOwnerId& aOwnerId,
@ -129,8 +126,14 @@ class WebGPUParent final : public PWebGPUParent {
virtual ~WebGPUParent();
void MaintainDevices();
bool ForwardError(RawId aDeviceId, ErrorBuffer& aError);
void ReportError(RawId aDeviceId, const nsCString& message);
bool ForwardError(const RawId aDeviceId, ErrorBuffer& aError) {
return ForwardError(Some(aDeviceId), aError);
}
bool ForwardError(Maybe<RawId> aDeviceId, ErrorBuffer& aError);
void ReportError(Maybe<RawId> aDeviceId, GPUErrorFilter,
const nsCString& message);
UniquePtr<ffi::WGPUGlobal> mContext;
base::RepeatingTimer<WebGPUParent> mTimer;
@ -147,7 +150,8 @@ class WebGPUParent final : public PWebGPUParent {
RefPtr<layers::RemoteTextureOwnerClient> mRemoteTextureOwner;
/// Associated stack of error scopes for each device.
std::unordered_map<uint64_t, ErrorScopeStack> mErrorScopeMap;
std::unordered_map<uint64_t, std::vector<ErrorScope>>
mErrorScopeStackByDevice;
};
} // namespace webgpu

View File

@ -24,6 +24,9 @@ namespace IPC {
#define DEFINE_IPC_SERIALIZER_FFI_ENUM(something) \
DEFINE_IPC_SERIALIZER_ENUM_GUARD(something, something##_Sentinel)
// -
DEFINE_IPC_SERIALIZER_DOM_ENUM(mozilla::dom::GPUErrorFilter);
DEFINE_IPC_SERIALIZER_DOM_ENUM(mozilla::dom::GPUPowerPreference);
DEFINE_IPC_SERIALIZER_FFI_ENUM(mozilla::webgpu::ffi::WGPUHostMap);
@ -36,8 +39,8 @@ DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::GPURequestAdapterOptions,
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::GPUBufferDescriptor, mSize,
mUsage, mMappedAtCreation);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::webgpu::ScopedError, operationError,
validationMessage);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::webgpu::PopErrorScopeResult,
resultType, message);
DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::webgpu::WebGPUCompilationMessage,
message, lineNum, linePos);
@ -46,5 +49,15 @@ DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::webgpu::WebGPUCompilationMessage,
#undef DEFINE_IPC_SERIALIZER_DOM_ENUM
#undef DEFINE_IPC_SERIALIZER_ENUM_GUARD
// -
template <>
struct ParamTraits<mozilla::webgpu::PopErrorScopeResultType>
: public ContiguousEnumSerializerInclusive<
mozilla::webgpu::PopErrorScopeResultType,
mozilla::webgpu::PopErrorScopeResultType{0},
mozilla::webgpu::PopErrorScopeResultType::_LAST> {};
} // namespace IPC
#endif // WEBGPU_SERIALIZE_H_

View File

@ -11,21 +11,34 @@
#include "nsString.h"
#include "mozilla/dom/BindingDeclarations.h"
namespace mozilla::dom {
enum class GPUErrorFilter : uint8_t;
} // namespace mozilla::dom
namespace mozilla::webgpu {
using RawId = uint64_t;
using BufferAddress = uint64_t;
struct ScopedError {
// Did an error occur as a result the attempt to retrieve an error
// (e.g. from a dead device, from an empty scope stack)?
bool operationError = false;
// If non-empty, the first error generated when this scope was on
// the top of the stack. This is interpreted as UTF-8.
nsCString validationMessage;
struct ErrorScope {
dom::GPUErrorFilter filter;
Maybe<nsCString> firstMessage;
};
enum class PopErrorScopeResultType : uint8_t {
NoError,
ThrowOperationError,
ValidationError,
OutOfMemory,
InternalError,
DeviceLost,
_LAST = DeviceLost,
};
struct PopErrorScopeResult {
PopErrorScopeResultType resultType;
nsCString message;
};
using MaybeScopedError = Maybe<ScopedError>;
enum class WebGPUCompilationMessageType { Error, Warning, Info };