[dawn][common] Require (d)checked_cast for unsafe TypedInteger

Restrict conversions to/from TypedInteger so that they're only allowed
to widen, OR they're consteval and the value is known to be valid.
Require checked_cast/dchecked_cast to be used in all other cases.

In order to avoid changing tons of code, this allows narrowing casts in
consteval, where we can just check the actual value and know that the
cast is safe. However, this has one bad consequence: if a consteval
function (such as this cast) is used inside a lambda, the lambda is
inferred to be consteval. Then an error is produced when it's called.
But there's no pointer back to why the lambda became consteval in the
first place, so it's completely incomprehensible.

See the change in `src/dawn/native/Queue.cpp` for the (one) place where
I saw this issue: those static_casts now select the consteval overload,
causing the lambda to be consteval, causing a confusing compilation
error inside `WithUploadReservation` when it tries to call the lambda.

A few places I had to do change literals to e.g. `Val(2u)` to deal with
an MSVC deficiency where for some reason it thinks the constructor is
not constexpr in some contexts even though it always is.

Bug: 515794394
Change-Id: I49a59a2721b306287edecdd11601b5ea3e910f24
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/310677
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/dawn/common/Numeric.h b/src/dawn/common/Numeric.h
index e7dcdb1..9f97edb 100644
--- a/src/dawn/common/Numeric.h
+++ b/src/dawn/common/Numeric.h
@@ -58,8 +58,7 @@
 inline constexpr uint32_t u32_alignof = detail::u32_alignof<T>();
 
 // Returns if two inclusive integral ranges [x0, x1] and [y0, y1] have overlap.
-template <typename T>
-    requires std::integral<T>
+template <std::integral T>
 bool RangesOverlap(T x0, T x1, T y0, T y1) {
     DAWN_ASSERT(x0 <= x1 && y0 <= y1);
     // Two ranges DON'T have overlap if and only if:
diff --git a/src/dawn/common/Time.h b/src/dawn/common/Time.h
index f0a85cc..7a3b095 100644
--- a/src/dawn/common/Time.h
+++ b/src/dawn/common/Time.h
@@ -35,7 +35,8 @@
 namespace dawn {
 
 using Nanoseconds = TypedInteger<struct NanosecondsT, uint64_t>;
-constexpr Nanoseconds kMaxDurationNanos = Nanoseconds(std::chrono::nanoseconds::max().count());
+constexpr Nanoseconds kMaxDurationNanos =
+    checked_cast<Nanoseconds>(std::chrono::nanoseconds::max().count());
 
 }  // namespace dawn
 
diff --git a/src/dawn/native/BlitBufferToDepthStencil.cpp b/src/dawn/native/BlitBufferToDepthStencil.cpp
index 69adf2e..074607f 100644
--- a/src/dawn/native/BlitBufferToDepthStencil.cpp
+++ b/src/dawn/native/BlitBufferToDepthStencil.cpp
@@ -284,7 +284,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = wgpu::TextureViewDimension::e2D;
-            viewDesc.baseArrayLayer = static_cast<uint32_t>(z);
+            viewDesc.baseArrayLayer = dchecked_cast<uint32_t>(z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.mipLevelCount = 1;
             DAWN_TRY_ASSIGN(srcView, dataTexture->CreateView(&viewDesc));
@@ -294,7 +294,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = wgpu::TextureViewDimension::e2D;
-            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z + z);
+            viewDesc.baseArrayLayer = dchecked_cast<uint32_t>(dst.origin.z + z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
@@ -311,8 +311,8 @@
 
             uint32_t* params =
                 static_cast<uint32_t*>(paramsBuffer->GetMappedRange(0, bufferDesc.size));
-            params[0] = static_cast<uint32_t>(dst.origin.x);
-            DAWN_UNSAFE_TODO(params[1]) = static_cast<uint32_t>(dst.origin.y);
+            params[0] = dchecked_cast<uint32_t>(dst.origin.x);
+            DAWN_UNSAFE_TODO(params[1]) = dchecked_cast<uint32_t>(dst.origin.y);
             DAWN_TRY(paramsBuffer->Unmap());
         }
 
@@ -345,8 +345,8 @@
         pass->APISetBindGroup(0, bindGroup.Get());
         // Discard all fragments outside the copy region.
         pass->APISetScissorRect(
-            static_cast<uint32_t>(dst.origin.x), static_cast<uint32_t>(dst.origin.y),
-            static_cast<uint32_t>(copyExtent.width), static_cast<uint32_t>(copyExtent.height));
+            dchecked_cast<uint32_t>(dst.origin.x), dchecked_cast<uint32_t>(dst.origin.y),
+            dchecked_cast<uint32_t>(copyExtent.width), dchecked_cast<uint32_t>(copyExtent.height));
 
         // Draw to perform the blit.
         pass->APISetPipeline(pipeline.Get());
@@ -416,8 +416,8 @@
         DAWN_TRY_ASSIGN(paramsBuffer, device->CreateBuffer(&bufferDesc));
 
         uint32_t* params = static_cast<uint32_t*>(paramsBuffer->GetMappedRange(0, bufferDesc.size));
-        params[0] = static_cast<uint32_t>(dst.origin.x);
-        DAWN_UNSAFE_TODO(params[1]) = static_cast<uint32_t>(dst.origin.y);
+        params[0] = dchecked_cast<uint32_t>(dst.origin.x);
+        DAWN_UNSAFE_TODO(params[1]) = dchecked_cast<uint32_t>(dst.origin.y);
         DAWN_UNSAFE_TODO(params[2]) = 0;
         DAWN_TRY(paramsBuffer->Unmap());
     }
@@ -445,7 +445,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = textureViewDimension;
-            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z) + z;
+            viewDesc.baseArrayLayer = dchecked_cast<uint32_t>(dst.origin.z) + z;
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
@@ -485,8 +485,8 @@
         // Bind the resources.
         pass->APISetBindGroup(0, bindGroup.Get());
         // Discard all fragments outside the copy region.
-        pass->APISetScissorRect(static_cast<uint32_t>(dst.origin.x),
-                                static_cast<uint32_t>(dst.origin.y), copyExtent.width,
+        pass->APISetScissorRect(dchecked_cast<uint32_t>(dst.origin.x),
+                                dchecked_cast<uint32_t>(dst.origin.y), copyExtent.width,
                                 copyExtent.height);
 
         // Clear the copy region to 0.
diff --git a/src/dawn/native/BlitBufferToTexture.cpp b/src/dawn/native/BlitBufferToTexture.cpp
index 3e975f4..b0c41ab 100644
--- a/src/dawn/native/BlitBufferToTexture.cpp
+++ b/src/dawn/native/BlitBufferToTexture.cpp
@@ -437,12 +437,12 @@
             break;
         case wgpu::TextureDimension::e3D:
             viewDimension = wgpu::TextureViewDimension::e3D;
-            baseDepth = static_cast<uint32_t>(dst.origin.z);
+            baseDepth = dchecked_cast<uint32_t>(dst.origin.z);
             depthStep = 1;
             break;
         default:
             viewDimension = wgpu::TextureViewDimension::e2D;
-            baseArray = static_cast<uint32_t>(dst.origin.z);
+            baseArray = dchecked_cast<uint32_t>(dst.origin.z);
             arrayStep = 1;
             break;
     }
@@ -452,7 +452,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = viewDimension;
-            viewDesc.baseArrayLayer = baseArray + arrayStep * static_cast<uint32_t>(z);
+            viewDesc.baseArrayLayer = baseArray + arrayStep * dchecked_cast<uint32_t>(z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
@@ -460,7 +460,7 @@
         }
 
         const uint64_t srcOffset =
-            src.offset + static_cast<uint32_t>(z) * src.rowsPerImage * src.bytesPerRow;
+            src.offset + dchecked_cast<uint32_t>(z) * src.rowsPerImage * src.bytesPerRow;
         const uint64_t srcBufferBindingOffset = AlignDown(srcOffset, ssboAlignment);
         const uint32_t shaderReadOffset = static_cast<uint32_t>(srcOffset & (ssboAlignment - 1));
         Ref<BufferBase> paramsBuffer;
@@ -471,8 +471,8 @@
             uint32_t params[4];
             params[0] = shaderReadOffset;
             params[1] = src.bytesPerRow;
-            params[2] = static_cast<uint32_t>(dst.origin.x);
-            params[3] = static_cast<uint32_t>(dst.origin.y);
+            params[2] = dchecked_cast<uint32_t>(dst.origin.x);
+            params[3] = dchecked_cast<uint32_t>(dst.origin.y);
             commandEncoder->APIWriteBuffer(paramsBuffer.Get(), 0,
                                            reinterpret_cast<const uint8_t*>(&params[0]),
                                            sizeof(params));
@@ -489,7 +489,7 @@
         RenderPassColorAttachment colorAttachment;
         colorAttachment.view = dstView.Get();
         if (depthStep) {
-            colorAttachment.depthSlice = baseDepth + depthStep * static_cast<uint32_t>(z);
+            colorAttachment.depthSlice = baseDepth + depthStep * dchecked_cast<uint32_t>(z);
         }
         colorAttachment.loadOp = wgpu::LoadOp::Load;
         colorAttachment.storeOp = wgpu::StoreOp::Store;
@@ -501,10 +501,10 @@
         Ref<RenderPassEncoder> pass = commandEncoder->BeginRenderPass(&rpDesc);
         // Bind the resources.
         pass->APISetBindGroup(0, bindGroup.Get());
-        pass->APISetViewport(static_cast<float>(static_cast<uint32_t>(dst.origin.x)),
-                             static_cast<float>(static_cast<uint32_t>(dst.origin.y)),
-                             static_cast<float>(static_cast<uint32_t>(copyExtent.width)),
-                             static_cast<float>(static_cast<uint32_t>(copyExtent.height)), 0.f,
+        pass->APISetViewport(static_cast<float>(dchecked_cast<uint32_t>(dst.origin.x)),
+                             static_cast<float>(dchecked_cast<uint32_t>(dst.origin.y)),
+                             static_cast<float>(dchecked_cast<uint32_t>(copyExtent.width)),
+                             static_cast<float>(dchecked_cast<uint32_t>(copyExtent.height)), 0.f,
                              1.f);
 
         // Draw to perform the blit.
diff --git a/src/dawn/native/BlitDepthToDepth.cpp b/src/dawn/native/BlitDepthToDepth.cpp
index d02379a..84cda9c 100644
--- a/src/dawn/native/BlitDepthToDepth.cpp
+++ b/src/dawn/native/BlitDepthToDepth.cpp
@@ -156,8 +156,8 @@
             intermediateTexDesc.format = src.texture->GetFormat().format;
             intermediateTexDesc.usage =
                 wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
-            intermediateTexDesc.size = {static_cast<uint32_t>(copyExtent.width),
-                                        static_cast<uint32_t>(copyExtent.height)};
+            intermediateTexDesc.size = {dchecked_cast<uint32_t>(copyExtent.width),
+                                        dchecked_cast<uint32_t>(copyExtent.height)};
 
             Ref<TextureBase> intermediateTexture;
             DAWN_TRY_ASSIGN(intermediateTexture, device->CreateTexture(&intermediateTexDesc));
@@ -167,7 +167,7 @@
                 TexelCopyTextureInfo intermediateSrc;
                 intermediateSrc.texture = src.texture.Get();
                 intermediateSrc.mipLevel = src.mipLevel;
-                intermediateSrc.origin = {0, 0, static_cast<uint32_t>(layer)};
+                intermediateSrc.origin = {0, 0, dchecked_cast<uint32_t>(layer)};
                 intermediateSrc.aspect = wgpu::TextureAspect::All;
 
                 TexelCopyTextureInfo intermediateDst;
@@ -211,7 +211,7 @@
         {
             TextureViewDescriptor viewDesc = {};
             viewDesc.dimension = wgpu::TextureViewDimension::e2D;
-            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z + z);
+            viewDesc.baseArrayLayer = dchecked_cast<uint32_t>(dst.origin.z + z);
             viewDesc.arrayLayerCount = 1;
             viewDesc.baseMipLevel = dst.mipLevel;
             viewDesc.mipLevelCount = 1;
diff --git a/src/dawn/native/BlitTextureToBuffer.cpp b/src/dawn/native/BlitTextureToBuffer.cpp
index e114e91..3029138 100644
--- a/src/dawn/native/BlitTextureToBuffer.cpp
+++ b/src/dawn/native/BlitTextureToBuffer.cpp
@@ -1098,9 +1098,9 @@
     // As the texture is uncompressed, texel and block space extents are the same, but we still use
     // texel space here because the compute shader works on texels.
     const TexelExtent3D texCopyExtent = blockInfo.ToTexel(copyExtent);
-    const uint32_t texelCopyWidth = static_cast<uint32_t>(texCopyExtent.width);
-    const uint32_t texelCopyHeight = static_cast<uint32_t>(texCopyExtent.height);
-    const uint32_t texelCopyDepth = static_cast<uint32_t>(texCopyExtent.depthOrArrayLayers);
+    const uint32_t texelCopyWidth = dchecked_cast<uint32_t>(texCopyExtent.width);
+    const uint32_t texelCopyHeight = dchecked_cast<uint32_t>(texCopyExtent.height);
+    const uint32_t texelCopyDepth = dchecked_cast<uint32_t>(texCopyExtent.depthOrArrayLayers);
 
     const uint32_t bytesPerTexel = blockInfo.byteSize;
     uint32_t workgroupCountX = 1;
@@ -1255,22 +1255,22 @@
         uint32_t* params =
             static_cast<uint32_t*>(uniformBuffer->GetMappedRange(0, bufferDesc.size));
         // srcOrigin: vec3u
-        params[0] = static_cast<uint32_t>(src.origin.x);
-        DAWN_UNSAFE_TODO(params[1]) = static_cast<uint32_t>(src.origin.y);
-        DAWN_UNSAFE_TODO(params[2]) = static_cast<uint32_t>(src.origin.z);
+        params[0] = dchecked_cast<uint32_t>(src.origin.x);
+        DAWN_UNSAFE_TODO(params[1]) = dchecked_cast<uint32_t>(src.origin.y);
+        DAWN_UNSAFE_TODO(params[2]) = dchecked_cast<uint32_t>(src.origin.z);
 
         // packTexelCount: number of texel values (1, 2, or 4) one thread packs into the dst
         // buffer
         DAWN_UNSAFE_TODO(params[3]) = std::max(1u, 4 / bytesPerTexel);
         // srcExtent: vec3u
-        DAWN_UNSAFE_TODO(params[4]) = static_cast<uint32_t>(copyExtent.width);
-        DAWN_UNSAFE_TODO(params[5]) = static_cast<uint32_t>(copyExtent.height);
-        DAWN_UNSAFE_TODO(params[6]) = static_cast<uint32_t>(copyExtent.depthOrArrayLayers);
+        DAWN_UNSAFE_TODO(params[4]) = dchecked_cast<uint32_t>(copyExtent.width);
+        DAWN_UNSAFE_TODO(params[5]) = dchecked_cast<uint32_t>(copyExtent.height);
+        DAWN_UNSAFE_TODO(params[6]) = dchecked_cast<uint32_t>(copyExtent.depthOrArrayLayers);
 
         DAWN_UNSAFE_TODO(params[7]) = src.mipLevel;
 
         DAWN_UNSAFE_TODO(params[8]) = static_cast<uint32_t>(blockInfo.ToBytes(dst.blocksPerRow));
-        DAWN_UNSAFE_TODO(params[9]) = static_cast<uint32_t>(dst.rowsPerImage);
+        DAWN_UNSAFE_TODO(params[9]) = dchecked_cast<uint32_t>(dst.rowsPerImage);
         DAWN_UNSAFE_TODO(params[10]) = static_cast<uint32_t>(shaderStartOffset);
 
         // These params are only used for formats smaller than 4 bytes
diff --git a/src/dawn/native/BlockInfo.h b/src/dawn/native/BlockInfo.h
index 90cb46a..2e5cb17 100644
--- a/src/dawn/native/BlockInfo.h
+++ b/src/dawn/native/BlockInfo.h
@@ -66,7 +66,7 @@
     // Convert to Origin3D
     // TODO(crbug.com/424536624): Remove once strong types are used everywhere.
     constexpr Origin3D ToOrigin3D() const {
-        return {static_cast<uint32_t>(x), static_cast<uint32_t>(y), static_cast<uint32_t>(z)};
+        return {dchecked_cast<uint32_t>(x), dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(z)};
     }
 
     // Comparison operator
@@ -114,8 +114,8 @@
     // Convert to Extent3D
     // TODO(crbug.com/424536624): Remove once strong types are used everywhere.
     constexpr Extent3D ToExtent3D() const {
-        return {static_cast<uint32_t>(width), static_cast<uint32_t>(height),
-                static_cast<uint32_t>(depthOrArrayLayers)};
+        return {dchecked_cast<uint32_t>(width), dchecked_cast<uint32_t>(height),
+                dchecked_cast<uint32_t>(depthOrArrayLayers)};
     }
 
     // Comparison operator
@@ -177,7 +177,7 @@
     // Convert to TexelBlockInfo
     // TODO(crbug.com/424536624): Remove once strong types are used everywhere.
     constexpr TexelBlockInfo ToTexelBlockInfo() const {
-        return {byteSize, static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+        return {byteSize, dchecked_cast<uint32_t>(width), dchecked_cast<uint32_t>(height)};
     }
 
     // Convert blocks to bytes
diff --git a/src/dawn/native/CommandBuffer.cpp b/src/dawn/native/CommandBuffer.cpp
index fab2167..24bd421 100644
--- a/src/dawn/native/CommandBuffer.cpp
+++ b/src/dawn/native/CommandBuffer.cpp
@@ -149,8 +149,8 @@
             return {copy.aspect, {0, 1}, {0, 1}};
         case wgpu::TextureDimension::e2D:
             return {copy.aspect,
-                    {static_cast<uint32_t>(copy.origin.z),
-                     static_cast<uint32_t>(copySize.depthOrArrayLayers)},
+                    {dchecked_cast<uint32_t>(copy.origin.z),
+                     dchecked_cast<uint32_t>(copySize.depthOrArrayLayers)},
                     {copy.mipLevel, 1}};
         case wgpu::TextureDimension::e3D:
             return {copy.aspect, {0, 1}, {copy.mipLevel, 1}};
diff --git a/src/dawn/native/Queue.cpp b/src/dawn/native/Queue.cpp
index 7777d91..c03a802 100644
--- a/src/dawn/native/Queue.cpp
+++ b/src/dawn/native/Queue.cpp
@@ -417,13 +417,13 @@
                 DAWN_UNSAFE_TODO(reinterpret_cast<const uint8_t*>(data) + dataLayout.offset);
             uint8_t* dstPointer = reinterpret_cast<uint8_t*>(reservation.mappedPointer.get());
             CopyTextureData(dstPointer, srcPointer, writeSizePixel.depthOrArrayLayers,
-                            static_cast<uint32_t>(rowsPerImage), dataLayout.rowsPerImage,
+                            dchecked_cast<uint32_t>(rowsPerImage), dataLayout.rowsPerImage,
                             bytesPerRow, alignedBytesPerRow, dataLayout.bytesPerRow);
 
             TexelCopyBufferLayout passDataLayout = dataLayout;
             passDataLayout.offset = reservation.offsetInBuffer;
             passDataLayout.bytesPerRow = alignedBytesPerRow;
-            passDataLayout.rowsPerImage = static_cast<uint32_t>(rowsPerImage);
+            passDataLayout.rowsPerImage = dchecked_cast<uint32_t>(rowsPerImage);
 
             TextureCopy textureCopy;
             textureCopy.texture = destination.texture;
diff --git a/src/dawn/native/d3d11/CommandBufferD3D11.cpp b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
index fe04d90..8ef993b 100644
--- a/src/dawn/native/d3d11/CommandBufferD3D11.cpp
+++ b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
@@ -438,8 +438,8 @@
                 uint64_t bytesPerRow = blockInfo.ToBytes(src.blocksPerRow);
                 DAWN_TRY(texture->Write(commandContext, subresources, dst.origin.ToOrigin3D(),
                                         copy->copySize.ToExtent3D(), data,
-                                        static_cast<uint32_t>(bytesPerRow),
-                                        static_cast<uint32_t>(src.rowsPerImage)));
+                                        dchecked_cast<uint32_t>(bytesPerRow),
+                                        dchecked_cast<uint32_t>(src.rowsPerImage)));
                 break;
             }
 
@@ -480,8 +480,9 @@
 
                 DAWN_TRY(ToBackend(src.texture)
                              ->Read(commandContext, subresources, src.origin.ToOrigin3D(),
-                                    copy->copySize.ToExtent3D(), static_cast<uint32_t>(bytesPerRow),
-                                    static_cast<uint32_t>(dst.rowsPerImage), callback));
+                                    copy->copySize.ToExtent3D(),
+                                    dchecked_cast<uint32_t>(bytesPerRow),
+                                    dchecked_cast<uint32_t>(dst.rowsPerImage), callback));
                 break;
             }
 
diff --git a/src/dawn/native/d3d11/TextureD3D11.cpp b/src/dawn/native/d3d11/TextureD3D11.cpp
index 45a5222..689dcdf 100644
--- a/src/dawn/native/d3d11/TextureD3D11.cpp
+++ b/src/dawn/native/d3d11/TextureD3D11.cpp
@@ -1048,10 +1048,10 @@
     SubresourceRange dstSubresources = GetSubresourcesAffectedByCopy(dst, copy->copySize);
 
     D3D11_BOX srcBox;
-    srcBox.left = static_cast<uint32_t>(src.origin.x);
-    srcBox.right = static_cast<uint32_t>(src.origin.x + copy->copySize.width);
-    srcBox.top = static_cast<uint32_t>(src.origin.y);
-    srcBox.bottom = static_cast<uint32_t>(src.origin.y + copy->copySize.height);
+    srcBox.left = dchecked_cast<uint32_t>(src.origin.x);
+    srcBox.right = dchecked_cast<uint32_t>(src.origin.x + copy->copySize.width);
+    srcBox.top = dchecked_cast<uint32_t>(src.origin.y);
+    srcBox.bottom = dchecked_cast<uint32_t>(src.origin.y + copy->copySize.height);
     switch (src.texture->GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
@@ -1061,8 +1061,8 @@
             srcBox.back = 1;
             break;
         case wgpu::TextureDimension::e3D:
-            srcBox.front = static_cast<uint32_t>(src.origin.z);
-            srcBox.back = static_cast<uint32_t>(src.origin.z + copy->copySize.depthOrArrayLayers);
+            srcBox.front = dchecked_cast<uint32_t>(src.origin.z);
+            srcBox.back = dchecked_cast<uint32_t>(src.origin.z + copy->copySize.depthOrArrayLayers);
             break;
     }
 
@@ -1081,9 +1081,9 @@
                                              D3D11Aspect(dstSubresources.aspects));
         commandContext->CopySubresourceRegion(
             ToBackend(dst.texture)->GetD3D11Resource(), dstSubresource,
-            static_cast<uint32_t>(dst.origin.x), static_cast<uint32_t>(dst.origin.y),
+            dchecked_cast<uint32_t>(dst.origin.x), dchecked_cast<uint32_t>(dst.origin.y),
             dst.texture->GetDimension() == wgpu::TextureDimension::e3D
-                ? static_cast<uint32_t>(dst.origin.z)
+                ? dchecked_cast<uint32_t>(dst.origin.z)
                 : 0,
             ToBackend(src.texture)->GetD3D11Resource(), srcSubresource,
             isWholeSubresource ? nullptr : &srcBox);
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
index 9a28679..04d1f43 100644
--- a/src/dawn/native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -1205,9 +1205,9 @@
                             ComputeD3D12BoxFromOffsetAndSize(copy->source.origin, copy->copySize);
 
                         commandList->CopyTextureRegion(
-                            &dstLocation, static_cast<uint32_t>(copy->destination.origin.x),
-                            static_cast<uint32_t>(copy->destination.origin.y),
-                            static_cast<uint32_t>(copy->destination.origin.z), &srcLocation,
+                            &dstLocation, dchecked_cast<uint32_t>(copy->destination.origin.x),
+                            dchecked_cast<uint32_t>(copy->destination.origin.y),
+                            dchecked_cast<uint32_t>(copy->destination.origin.z), &srcLocation,
                             &sourceRegion);
                     }
                 } else {
@@ -1225,7 +1225,8 @@
                                     DAWN_ASSERT(copy->source.origin.z == TexelCount{0});
                                     break;
                                 case wgpu::TextureDimension::e2D:
-                                    sourceLayer = static_cast<uint32_t>(copy->source.origin.z + z);
+                                    sourceLayer =
+                                        dchecked_cast<uint32_t>(copy->source.origin.z + z);
                                     break;
                                 case wgpu::TextureDimension::e3D:
                                     sourceZ = copy->source.origin.z + z;
@@ -1242,7 +1243,7 @@
                                     break;
                                 case wgpu::TextureDimension::e2D:
                                     destinationLayer =
-                                        static_cast<uint32_t>(copy->destination.origin.z + z);
+                                        dchecked_cast<uint32_t>(copy->destination.origin.z + z);
                                     break;
                                 case wgpu::TextureDimension::e3D:
                                     destinationZ = copy->destination.origin.z + z;
@@ -1263,9 +1264,9 @@
                                 sourceOriginInSubresource, copyExtentOneSlice);
 
                             commandList->CopyTextureRegion(
-                                &dstLocation, static_cast<uint32_t>(copy->destination.origin.x),
-                                static_cast<uint32_t>(copy->destination.origin.y),
-                                static_cast<uint32_t>(destinationZ), &srcLocation, &sourceRegion);
+                                &dstLocation, dchecked_cast<uint32_t>(copy->destination.origin.x),
+                                dchecked_cast<uint32_t>(copy->destination.origin.y),
+                                dchecked_cast<uint32_t>(destinationZ), &srcLocation, &sourceRegion);
                         }
                     }
                 }
diff --git a/src/dawn/native/d3d12/TextureCopySplitter.cpp b/src/dawn/native/d3d12/TextureCopySplitter.cpp
index 9b70d1d..2f033b8 100644
--- a/src/dawn/native/d3d12/TextureCopySplitter.cpp
+++ b/src/dawn/native/d3d12/TextureCopySplitter.cpp
@@ -497,7 +497,7 @@
             DAWN_ASSERT(copySize.height == rowsPerImage);
 
             const BlockCount copyHeight = copySize.height;
-            if (static_cast<uint32_t>(copyHeight) % 2 == 0) {
+            if (dchecked_cast<uint32_t>(copyHeight) % 2 == 0) {
                 // If copyHeight is even and there is an empty row at the beginning of the
                 // first slice of the copy region, the offset of all depth slices will never be
                 // aligned to D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT (512) and there is always
diff --git a/src/dawn/native/d3d12/UtilsD3D12.cpp b/src/dawn/native/d3d12/UtilsD3D12.cpp
index 471a342..d3fa24f 100644
--- a/src/dawn/native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn/native/d3d12/UtilsD3D12.cpp
@@ -112,10 +112,10 @@
     bufferLocation.PlacedFootprint.Offset = offset;
     bufferLocation.PlacedFootprint.Footprint.Format =
         texture->GetD3D12CopyableSubresourceFormat(aspect);
-    bufferLocation.PlacedFootprint.Footprint.Width = static_cast<uint32_t>(bufferSize.width);
-    bufferLocation.PlacedFootprint.Footprint.Height = static_cast<uint32_t>(bufferSize.height);
+    bufferLocation.PlacedFootprint.Footprint.Width = dchecked_cast<uint32_t>(bufferSize.width);
+    bufferLocation.PlacedFootprint.Footprint.Height = dchecked_cast<uint32_t>(bufferSize.height);
     bufferLocation.PlacedFootprint.Footprint.Depth =
-        static_cast<uint32_t>(bufferSize.depthOrArrayLayers);
+        dchecked_cast<uint32_t>(bufferSize.depthOrArrayLayers);
     bufferLocation.PlacedFootprint.Footprint.RowPitch = rowPitch;
     return bufferLocation;
 }
@@ -177,12 +177,12 @@
 D3D12_BOX ComputeD3D12BoxFromOffsetAndSize(const TexelOrigin3D& offset,
                                            const TexelExtent3D& copySize) {
     D3D12_BOX sourceRegion;
-    sourceRegion.left = static_cast<UINT>(offset.x);
-    sourceRegion.top = static_cast<UINT>(offset.y);
-    sourceRegion.front = static_cast<UINT>(offset.z);
-    sourceRegion.right = static_cast<UINT>(offset.x + copySize.width);
-    sourceRegion.bottom = static_cast<UINT>(offset.y + copySize.height);
-    sourceRegion.back = static_cast<UINT>(offset.z + copySize.depthOrArrayLayers);
+    sourceRegion.left = dchecked_cast<UINT>(offset.x);
+    sourceRegion.top = dchecked_cast<UINT>(offset.y);
+    sourceRegion.front = dchecked_cast<UINT>(offset.z);
+    sourceRegion.right = dchecked_cast<UINT>(offset.x + copySize.width);
+    sourceRegion.bottom = dchecked_cast<UINT>(offset.y + copySize.height);
+    sourceRegion.back = dchecked_cast<UINT>(offset.z + copySize.depthOrArrayLayers);
     return sourceRegion;
 }
 
@@ -199,7 +199,7 @@
                                        Aspect aspect) {
     Texture* texture = ToBackend(textureBase);
     const D3D12_TEXTURE_COPY_LOCATION textureLocation = ComputeTextureCopyLocationForTexture(
-        texture, textureMiplevel, static_cast<uint32_t>(textureLayer), aspect);
+        texture, textureMiplevel, dchecked_cast<uint32_t>(textureLayer), aspect);
     uint64_t bufferBytesPerRow = blockInfo.ToBytes(bufferBlocksPerRow);
 
     for (uint32_t i = 0; i < baseCopySplit.count; ++i) {
@@ -214,7 +214,7 @@
         const D3D12_TEXTURE_COPY_LOCATION bufferLocation =
             ComputeBufferLocationForCopyTextureRegion(
                 texture, bufferResource, bufferSize, offsetBytes,
-                static_cast<uint32_t>(bufferBytesPerRow), aspect);
+                dchecked_cast<uint32_t>(bufferBytesPerRow), aspect);
 
         if (direction == BufferTextureCopyDirection::B2T) {
             const D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize(bufferOffset, copySize);
@@ -260,7 +260,7 @@
 
     for (BlockCount copyLayer : Range(copySize.depthOrArrayLayers)) {
         const uint32_t splitIndex =
-            static_cast<uint32_t>(copyLayer) % copySplits.copySubresources.size();
+            dchecked_cast<uint32_t>(copyLayer) % copySplits.copySubresources.size();
 
         const TextureCopySubresource& copyResourcePerLayer =
             copySplits.copySubresources[splitIndex];
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index c8cdc9e..cb0fed4 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -1055,7 +1055,7 @@
                                              sourceBytesPerImage:copyInfo.bytesPerImage
                                                       sourceSize:copyExtent
                                                        toTexture:texture->GetMTLTexture(aspect)
-                                                destinationSlice:uint32_t(z)
+                                                destinationSlice:dchecked_cast<uint32_t>(z)
                                                 destinationLevel:mipLevel
                                                destinationOrigin:textureOrigin
                                                          options:blitOption];
@@ -1332,7 +1332,7 @@
                                  ++z) {
                                 [commandContext->EnsureBlit()
                                              copyFromTexture:texture->GetMTLTexture(src.aspect)
-                                                 sourceSlice:uint32_t(z)
+                                                 sourceSlice:dchecked_cast<uint32_t>(z)
                                                  sourceLevel:src.mipLevel
                                                 sourceOrigin:textureOrigin
                                                   sourceSize:copyExtent
@@ -1381,8 +1381,8 @@
                     commandContext, dstTexture, copy->destination, copy->copySize.ToExtent3D()));
 
                 const MTLSize sizeOneSlice =
-                    MTLSizeMake(static_cast<uint32_t>(copy->copySize.width),
-                                static_cast<uint32_t>(copy->copySize.height), 1);
+                    MTLSizeMake(dchecked_cast<uint32_t>(copy->copySize.width),
+                                dchecked_cast<uint32_t>(copy->copySize.height), 1);
 
                 uint32_t sourceLayer = 0;
                 uint32_t sourceOriginZ = 0;
@@ -1406,8 +1406,8 @@
 
                 // TODO(crbug.com/dawn/782): Do a single T2T copy if both are 1D or 3D.
                 for (TexelCount z{0}; z < copy->copySize.depthOrArrayLayers; ++z) {
-                    *sourceZPtr = static_cast<uint32_t>(copy->source.origin.z + z);
-                    *destinationZPtr = static_cast<uint32_t>(copy->destination.origin.z + z);
+                    *sourceZPtr = dchecked_cast<uint32_t>(copy->source.origin.z + z);
+                    *destinationZPtr = dchecked_cast<uint32_t>(copy->destination.origin.z + z);
 
                     // Hold the ref until out of scope
                     NSPRef<id<MTLTexture>> dstTextureView =
@@ -1418,16 +1418,16 @@
                               sourceSlice:sourceLayer
                               sourceLevel:copy->source.mipLevel
                              sourceOrigin:MTLOriginMake(
-                                              static_cast<uint32_t>(copy->source.origin.x),
-                                              static_cast<uint32_t>(copy->source.origin.y),
+                                              dchecked_cast<uint32_t>(copy->source.origin.x),
+                                              dchecked_cast<uint32_t>(copy->source.origin.y),
                                               sourceOriginZ)
                                sourceSize:sizeOneSlice
                                 toTexture:dstTextureView.Get()
                          destinationSlice:destinationLayer
                          destinationLevel:copy->destination.mipLevel
                         destinationOrigin:MTLOriginMake(
-                                              static_cast<uint32_t>(copy->destination.origin.x),
-                                              static_cast<uint32_t>(copy->destination.origin.y),
+                                              dchecked_cast<uint32_t>(copy->destination.origin.x),
+                                              dchecked_cast<uint32_t>(copy->destination.origin.y),
                                               destinationOriginZ)];
                 }
                 break;
diff --git a/src/dawn/native/metal/UtilsMetal.mm b/src/dawn/native/metal/UtilsMetal.mm
index 637f41d..371a99f 100644
--- a/src/dawn/native/metal/UtilsMetal.mm
+++ b/src/dawn/native/metal/UtilsMetal.mm
@@ -635,12 +635,14 @@
 }
 
 MTLSize ToMTLSize(const TexelExtent3D& extent) {
-    return MTLSizeMake(uint32_t(extent.width), uint32_t(extent.height),
-                       uint32_t(extent.depthOrArrayLayers));
+    return MTLSizeMake(dchecked_cast<uint32_t>(extent.width),
+                       dchecked_cast<uint32_t>(extent.height),
+                       dchecked_cast<uint32_t>(extent.depthOrArrayLayers));
 }
 
 MTLOrigin ToMTLOrigin(const TexelOrigin3D& origin) {
-    return MTLOriginMake(uint32_t(origin.x), uint32_t(origin.y), uint32_t(origin.z));
+    return MTLOriginMake(dchecked_cast<uint32_t>(origin.x), dchecked_cast<uint32_t>(origin.y),
+                         dchecked_cast<uint32_t>(origin.z));
 }
 
 TextureBufferCopySplit ComputeTextureBufferCopySplit(const Texture* texture,
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index ce88933..dcc5532 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -911,7 +911,7 @@
                 TexelCopyBufferLayout dataLayout;
                 dataLayout.offset = 0;
                 dataLayout.bytesPerRow = blockInfo.ToBytes(src.blocksPerRow);
-                dataLayout.rowsPerImage = static_cast<uint32_t>(src.rowsPerImage);
+                dataLayout.rowsPerImage = dchecked_cast<uint32_t>(src.rowsPerImage);
 
                 DAWN_TRY(DoTexSubImage(gl, dst, reinterpret_cast<void*>(src.offset), dataLayout,
                                        copy->copySize.ToExtent3D()));
@@ -959,7 +959,7 @@
                 DAWN_GL_TRY(gl, BindBuffer(GL_PIXEL_PACK_BUFFER, buffer->GetHandle()));
                 DAWN_GL_TRY(gl, PixelStorei(GL_PACK_ALIGNMENT, std::min(8u, blockInfo.byteSize)));
                 DAWN_GL_TRY(
-                    gl, PixelStorei(GL_PACK_ROW_LENGTH, static_cast<uint32_t>(dst.blocksPerRow)));
+                    gl, PixelStorei(GL_PACK_ROW_LENGTH, dchecked_cast<uint32_t>(dst.blocksPerRow)));
 
                 GLenum glAttachment;
                 GLenum glFormat;
@@ -1000,10 +1000,10 @@
                             DAWN_GL_TRY(
                                 gl, FramebufferTexture2D(GL_READ_FRAMEBUFFER, glAttachment, target,
                                                          texture->GetHandle(), src.mipLevel));
-                            DAWN_GL_TRY(gl, ReadPixels(static_cast<uint32_t>(src.origin.x),
-                                                       static_cast<uint32_t>(src.origin.y),
-                                                       static_cast<uint32_t>(copySize.width),
-                                                       static_cast<uint32_t>(copySize.height),
+                            DAWN_GL_TRY(gl, ReadPixels(dchecked_cast<uint32_t>(src.origin.x),
+                                                       dchecked_cast<uint32_t>(src.origin.y),
+                                                       dchecked_cast<uint32_t>(copySize.width),
+                                                       dchecked_cast<uint32_t>(copySize.height),
                                                        glFormat, glType, offset));
                             break;
                         } else if (target == GL_TEXTURE_CUBE_MAP) {
@@ -1012,15 +1012,15 @@
                                 blockInfo.ToBytes(dst.blocksPerRow * dst.rowsPerImage);
                             for (TexelCount z{0}; z < copySize.depthOrArrayLayers; ++z) {
                                 GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X +
-                                                       static_cast<uint32_t>(z + src.origin.z);
+                                                       dchecked_cast<uint32_t>(z + src.origin.z);
                                 DAWN_GL_TRY(
                                     gl, FramebufferTexture2D(GL_READ_FRAMEBUFFER, glAttachment,
                                                              cubeMapTarget, texture->GetHandle(),
                                                              src.mipLevel));
-                                DAWN_GL_TRY(gl, ReadPixels(static_cast<uint32_t>(src.origin.x),
-                                                           static_cast<uint32_t>(src.origin.y),
-                                                           static_cast<uint32_t>(copySize.width),
-                                                           static_cast<uint32_t>(copySize.height),
+                                DAWN_GL_TRY(gl, ReadPixels(dchecked_cast<uint32_t>(src.origin.x),
+                                                           dchecked_cast<uint32_t>(src.origin.y),
+                                                           dchecked_cast<uint32_t>(copySize.width),
+                                                           dchecked_cast<uint32_t>(copySize.height),
                                                            glFormat, glType, offset));
                                 DAWN_UNSAFE_TODO(offset += bytesPerImage);
                             }
@@ -1034,14 +1034,14 @@
                         const uint64_t bytesPerImage =
                             blockInfo.ToBytes(dst.blocksPerRow * dst.rowsPerImage);
                         for (TexelCount z{0}; z < copySize.depthOrArrayLayers; ++z) {
-                            DAWN_GL_TRY(gl,
-                                        FramebufferTextureLayer(
-                                            GL_READ_FRAMEBUFFER, glAttachment, texture->GetHandle(),
-                                            src.mipLevel, static_cast<uint32_t>(src.origin.z + z)));
-                            DAWN_GL_TRY(gl, ReadPixels(static_cast<uint32_t>(src.origin.x),
-                                                       static_cast<uint32_t>(src.origin.y),
-                                                       static_cast<uint32_t>(copySize.width),
-                                                       static_cast<uint32_t>(copySize.height),
+                            DAWN_GL_TRY(
+                                gl, FramebufferTextureLayer(
+                                        GL_READ_FRAMEBUFFER, glAttachment, texture->GetHandle(),
+                                        src.mipLevel, dchecked_cast<uint32_t>(src.origin.z + z)));
+                            DAWN_GL_TRY(gl, ReadPixels(dchecked_cast<uint32_t>(src.origin.x),
+                                                       dchecked_cast<uint32_t>(src.origin.y),
+                                                       dchecked_cast<uint32_t>(copySize.width),
+                                                       dchecked_cast<uint32_t>(copySize.height),
                                                        glFormat, glType, offset));
 
                             DAWN_UNSAFE_TODO(offset += bytesPerImage);
@@ -1733,43 +1733,45 @@
 
             DAWN_GL_TRY(gl,
                         PixelStorei(GL_UNPACK_ROW_LENGTH,
-                                    static_cast<uint32_t>(blockInfo.ToTexelWidth(blocksPerRow))));
+                                    dchecked_cast<uint32_t>(blockInfo.ToTexelWidth(blocksPerRow))));
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_SIZE, blockInfo.byteSize));
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH,
-                                        static_cast<uint32_t>(blockInfo.width)));
+                                        dchecked_cast<uint32_t>(blockInfo.width)));
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT,
-                                        static_cast<uint32_t>(blockInfo.height)));
+                                        dchecked_cast<uint32_t>(blockInfo.height)));
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 1));
 
             if (target == GL_TEXTURE_2D) {
-                DAWN_GL_TRY(
-                    gl, CompressedTexSubImage2D(
-                            target, destination.mipLevel, static_cast<uint32_t>(x),
-                            static_cast<uint32_t>(y), static_cast<uint32_t>(width),
-                            static_cast<uint32_t>(height), format.internalFormat, imageSize, data));
+                DAWN_GL_TRY(gl, CompressedTexSubImage2D(
+                                    target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                    dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(width),
+                                    dchecked_cast<uint32_t>(height), format.internalFormat,
+                                    imageSize, data));
             } else if (target == GL_TEXTURE_CUBE_MAP) {
                 DAWN_ASSERT(texture->GetArrayLayers() == 6);
                 const uint8_t* pointer = static_cast<const uint8_t*>(data);
                 TexelCount baseLayer = destination.origin.z;
                 for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
                     GLenum cubeMapTarget =
-                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
-                    DAWN_GL_TRY(gl, CompressedTexSubImage2D(
-                                        cubeMapTarget, destination.mipLevel,
-                                        static_cast<uint32_t>(x), static_cast<uint32_t>(y),
-                                        static_cast<uint32_t>(width), static_cast<uint32_t>(height),
-                                        format.internalFormat, imageSize, pointer));
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + dchecked_cast<uint32_t>(baseLayer + l);
+                    DAWN_GL_TRY(
+                        gl, CompressedTexSubImage2D(
+                                cubeMapTarget, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(width),
+                                dchecked_cast<uint32_t>(height), format.internalFormat, imageSize,
+                                pointer));
                     DAWN_UNSAFE_TODO(pointer += bytesPerImage);
                 }
             } else {
                 DAWN_GL_TRY(
-                    gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
-                                    static_cast<uint32_t>(blockInfo.ToTexelHeight(rowsPerImage))));
+                    gl,
+                    PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
+                                dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(rowsPerImage))));
                 DAWN_GL_TRY(gl, CompressedTexSubImage3D(
-                                    target, destination.mipLevel, static_cast<uint32_t>(x),
-                                    static_cast<uint32_t>(y), static_cast<uint32_t>(z),
-                                    static_cast<uint32_t>(width), static_cast<uint32_t>(height),
-                                    static_cast<uint32_t>(copySize.depthOrArrayLayers),
+                                    target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                    dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(z),
+                                    dchecked_cast<uint32_t>(width), dchecked_cast<uint32_t>(height),
+                                    dchecked_cast<uint32_t>(copySize.depthOrArrayLayers),
                                     format.internalFormat, imageSize, data));
                 DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0));
             }
@@ -1785,11 +1787,11 @@
 
                 for (; y < destination.origin.y + copySize.height; y += blockInfo.height) {
                     TexelCount height = std::min(blockInfo.height, virtSize.height - y);
-                    DAWN_GL_TRY(
-                        gl, CompressedTexSubImage2D(
-                                target, destination.mipLevel, static_cast<uint32_t>(x),
-                                static_cast<uint32_t>(y), static_cast<uint32_t>(width),
-                                static_cast<uint32_t>(height), format.internalFormat, rowSize, d));
+                    DAWN_GL_TRY(gl, CompressedTexSubImage2D(
+                                        target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                        dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(width),
+                                        dchecked_cast<uint32_t>(height), format.internalFormat,
+                                        rowSize, d));
                     DAWN_UNSAFE_TODO(d += bytesPerRow);
                 }
             } else if (target == GL_TEXTURE_CUBE_MAP) {
@@ -1798,17 +1800,17 @@
                 TexelCount baseLayer = destination.origin.z;
                 for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
                     const uint8_t* d =
-                        DAWN_UNSAFE_TODO(pointer + static_cast<uint32_t>(l)) * bytesPerImage;
+                        DAWN_UNSAFE_TODO(pointer + dchecked_cast<uint32_t>(l)) * bytesPerImage;
                     GLenum cubeMapTarget =
-                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + dchecked_cast<uint32_t>(baseLayer + l);
                     for (y = destination.origin.y; y < destination.origin.y + copySize.height;
                          y += blockInfo.height) {
                         TexelCount height = std::min(blockInfo.height, virtSize.height - y);
                         DAWN_GL_TRY(gl, CompressedTexSubImage2D(cubeMapTarget, destination.mipLevel,
-                                                                static_cast<uint32_t>(x),
-                                                                static_cast<uint32_t>(y),
-                                                                static_cast<uint32_t>(width),
-                                                                static_cast<uint32_t>(height),
+                                                                dchecked_cast<uint32_t>(x),
+                                                                dchecked_cast<uint32_t>(y),
+                                                                dchecked_cast<uint32_t>(width),
+                                                                dchecked_cast<uint32_t>(height),
                                                                 format.internalFormat, rowSize, d));
                         DAWN_UNSAFE_TODO(d += bytesPerRow);
                     }
@@ -1826,10 +1828,10 @@
                         TexelCount height = std::min(blockInfo.height, virtSize.height - y);
                         DAWN_GL_TRY(
                             gl, CompressedTexSubImage3D(
-                                    target, destination.mipLevel, static_cast<uint32_t>(x),
-                                    static_cast<uint32_t>(y), static_cast<uint32_t>(z),
-                                    static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1,
-                                    format.internalFormat, rowSize, d));
+                                    target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                    dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(z),
+                                    dchecked_cast<uint32_t>(width), dchecked_cast<uint32_t>(height),
+                                    1, format.internalFormat, rowSize, d));
                         DAWN_UNSAFE_TODO(d += bytesPerRow);
                     }
 
@@ -1853,12 +1855,12 @@
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_ALIGNMENT, std::min(8u, blockInfo.byteSize)));
             DAWN_GL_TRY(gl,
                         PixelStorei(GL_UNPACK_ROW_LENGTH,
-                                    static_cast<uint32_t>(blockInfo.ToTexelWidth(blocksPerRow))));
+                                    dchecked_cast<uint32_t>(blockInfo.ToTexelWidth(blocksPerRow))));
             if (target == GL_TEXTURE_2D) {
                 DAWN_GL_TRY(
-                    gl, TexSubImage2D(target, destination.mipLevel, static_cast<uint32_t>(x),
-                                      static_cast<uint32_t>(y), static_cast<uint32_t>(width),
-                                      static_cast<uint32_t>(height), adjustedFormat, format.type,
+                    gl, TexSubImage2D(target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                      dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(width),
+                                      dchecked_cast<uint32_t>(height), adjustedFormat, format.type,
                                       data));
             } else if (target == GL_TEXTURE_CUBE_MAP) {
                 DAWN_ASSERT(texture->GetArrayLayers() == 6);
@@ -1866,26 +1868,28 @@
                 TexelCount baseLayer = destination.origin.z;
                 for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
                     GLenum cubeMapTarget =
-                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
-                    DAWN_GL_TRY(gl, TexSubImage2D(
-                                        cubeMapTarget, destination.mipLevel,
-                                        static_cast<uint32_t>(x), static_cast<uint32_t>(y),
-                                        static_cast<uint32_t>(width), static_cast<uint32_t>(height),
-                                        adjustedFormat, format.type, pointer));
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + dchecked_cast<uint32_t>(baseLayer + l);
+                    DAWN_GL_TRY(
+                        gl, TexSubImage2D(cubeMapTarget, destination.mipLevel,
+                                          dchecked_cast<uint32_t>(x), dchecked_cast<uint32_t>(y),
+                                          dchecked_cast<uint32_t>(width),
+                                          dchecked_cast<uint32_t>(height), adjustedFormat,
+                                          format.type, pointer));
                     DAWN_UNSAFE_TODO(pointer += bytesPerImage);
                 }
             } else {
                 DAWN_ASSERT(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY ||
                             target == GL_TEXTURE_CUBE_MAP_ARRAY);
                 DAWN_GL_TRY(
-                    gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
-                                    static_cast<uint32_t>(blockInfo.ToTexelHeight(rowsPerImage))));
-                DAWN_GL_TRY(
-                    gl, TexSubImage3D(target, destination.mipLevel, static_cast<uint32_t>(x),
-                                      static_cast<uint32_t>(y), static_cast<uint32_t>(z),
-                                      static_cast<uint32_t>(width), static_cast<uint32_t>(height),
-                                      static_cast<uint32_t>(copySize.depthOrArrayLayers),
-                                      adjustedFormat, format.type, data));
+                    gl,
+                    PixelStorei(GL_UNPACK_IMAGE_HEIGHT,
+                                dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(rowsPerImage))));
+                DAWN_GL_TRY(gl, TexSubImage3D(
+                                    target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                    dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(z),
+                                    dchecked_cast<uint32_t>(width), dchecked_cast<uint32_t>(height),
+                                    dchecked_cast<uint32_t>(copySize.depthOrArrayLayers),
+                                    adjustedFormat, format.type, data));
                 DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0));
             }
             DAWN_GL_TRY(gl, PixelStorei(GL_UNPACK_ROW_LENGTH, 0));
@@ -1894,10 +1898,10 @@
             if (target == GL_TEXTURE_2D) {
                 const uint8_t* d = static_cast<const uint8_t*>(data);
                 for (; y < destination.origin.y + height; ++y) {
-                    DAWN_GL_TRY(
-                        gl, TexSubImage2D(target, destination.mipLevel, static_cast<uint32_t>(x),
-                                          static_cast<uint32_t>(y), static_cast<uint32_t>(width), 1,
-                                          adjustedFormat, format.type, d));
+                    DAWN_GL_TRY(gl, TexSubImage2D(
+                                        target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                        dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(width),
+                                        1, adjustedFormat, format.type, d));
                     DAWN_UNSAFE_TODO(d += bytesPerRow);
                 }
             } else if (target == GL_TEXTURE_CUBE_MAP) {
@@ -1907,13 +1911,13 @@
                 for (TexelCount l{0}; l < copySize.depthOrArrayLayers; ++l) {
                     const uint8_t* d = pointer;
                     GLenum cubeMapTarget =
-                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<uint32_t>(baseLayer + l);
+                        GL_TEXTURE_CUBE_MAP_POSITIVE_X + dchecked_cast<uint32_t>(baseLayer + l);
                     for (y = destination.origin.y; y < destination.origin.y + height; ++y) {
-                        DAWN_GL_TRY(
-                            gl, TexSubImage2D(cubeMapTarget, destination.mipLevel,
-                                              static_cast<uint32_t>(x), static_cast<uint32_t>(y),
-                                              static_cast<uint32_t>(width), 1, adjustedFormat,
-                                              format.type, d));
+                        DAWN_GL_TRY(gl, TexSubImage2D(cubeMapTarget, destination.mipLevel,
+                                                      dchecked_cast<uint32_t>(x),
+                                                      dchecked_cast<uint32_t>(y),
+                                                      dchecked_cast<uint32_t>(width), 1,
+                                                      adjustedFormat, format.type, d));
                         DAWN_UNSAFE_TODO(d += bytesPerRow);
                     }
                     DAWN_UNSAFE_TODO(pointer += bytesPerImage);
@@ -1925,11 +1929,12 @@
                 for (; z < destination.origin.z + copySize.depthOrArrayLayers; ++z) {
                     const uint8_t* d = slice;
                     for (y = destination.origin.y; y < destination.origin.y + height; ++y) {
-                        DAWN_GL_TRY(gl, TexSubImage3D(
-                                            target, destination.mipLevel, static_cast<uint32_t>(x),
-                                            static_cast<uint32_t>(y), static_cast<uint32_t>(z),
-                                            static_cast<uint32_t>(width), 1, 1, adjustedFormat,
-                                            format.type, d));
+                        DAWN_GL_TRY(
+                            gl,
+                            TexSubImage3D(target, destination.mipLevel, dchecked_cast<uint32_t>(x),
+                                          dchecked_cast<uint32_t>(y), dchecked_cast<uint32_t>(z),
+                                          dchecked_cast<uint32_t>(width), 1, 1, adjustedFormat,
+                                          format.type, d));
                         DAWN_UNSAFE_TODO(d += bytesPerRow);
                     }
                     DAWN_UNSAFE_TODO(slice += bytesPerImage);
diff --git a/src/dawn/native/opengl/PipelineGL.cpp b/src/dawn/native/opengl/PipelineGL.cpp
index 687963a..225efd5 100644
--- a/src/dawn/native/opengl/PipelineGL.cpp
+++ b/src/dawn/native/opengl/PipelineGL.cpp
@@ -145,7 +145,7 @@
                 mUnitsForSamplers[samplerGLIndex].push_back(textureUnit);
             }
 
-            uniformsToSet.push_back(GLint(textureUnit));
+            uniformsToSet.push_back(dchecked_cast<GLint>(textureUnit));
             textureUnit++;
         }
 
diff --git a/src/dawn/native/vulkan/CommandBufferVk.cpp b/src/dawn/native/vulkan/CommandBufferVk.cpp
index aa6bce5..037fbab 100644
--- a/src/dawn/native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn/native/vulkan/CommandBufferVk.cpp
@@ -110,8 +110,8 @@
 
     bool has3DTextureInCopy = false;
 
-    region.srcOffset.x = static_cast<uint32_t>(srcCopy.origin.x);
-    region.srcOffset.y = static_cast<uint32_t>(srcCopy.origin.y);
+    region.srcOffset.x = dchecked_cast<uint32_t>(srcCopy.origin.x);
+    region.srcOffset.y = dchecked_cast<uint32_t>(srcCopy.origin.y);
     switch (srcTexture->GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
@@ -121,20 +121,20 @@
             region.srcOffset.z = 0;
             break;
         case wgpu::TextureDimension::e2D:
-            region.srcSubresource.baseArrayLayer = static_cast<uint32_t>(srcCopy.origin.z);
-            region.srcSubresource.layerCount = static_cast<uint32_t>(copySize.depthOrArrayLayers);
+            region.srcSubresource.baseArrayLayer = dchecked_cast<uint32_t>(srcCopy.origin.z);
+            region.srcSubresource.layerCount = dchecked_cast<uint32_t>(copySize.depthOrArrayLayers);
             region.srcOffset.z = 0;
             break;
         case wgpu::TextureDimension::e3D:
             has3DTextureInCopy = true;
             region.srcSubresource.baseArrayLayer = 0;
             region.srcSubresource.layerCount = 1;
-            region.srcOffset.z = static_cast<uint32_t>(srcCopy.origin.z);
+            region.srcOffset.z = dchecked_cast<uint32_t>(srcCopy.origin.z);
             break;
     }
 
-    region.dstOffset.x = static_cast<uint32_t>(dstCopy.origin.x);
-    region.dstOffset.y = static_cast<uint32_t>(dstCopy.origin.y);
+    region.dstOffset.x = dchecked_cast<uint32_t>(dstCopy.origin.x);
+    region.dstOffset.y = dchecked_cast<uint32_t>(dstCopy.origin.y);
     switch (dstTexture->GetDimension()) {
         case wgpu::TextureDimension::Undefined:
             DAWN_UNREACHABLE();
@@ -144,24 +144,24 @@
             region.dstOffset.z = 0;
             break;
         case wgpu::TextureDimension::e2D:
-            region.dstSubresource.baseArrayLayer = static_cast<uint32_t>(dstCopy.origin.z);
-            region.dstSubresource.layerCount = static_cast<uint32_t>(copySize.depthOrArrayLayers);
+            region.dstSubresource.baseArrayLayer = dchecked_cast<uint32_t>(dstCopy.origin.z);
+            region.dstSubresource.layerCount = dchecked_cast<uint32_t>(copySize.depthOrArrayLayers);
             region.dstOffset.z = 0;
             break;
         case wgpu::TextureDimension::e3D:
             has3DTextureInCopy = true;
             region.dstSubresource.baseArrayLayer = 0;
             region.dstSubresource.layerCount = 1;
-            region.dstOffset.z = static_cast<uint32_t>(dstCopy.origin.z);
+            region.dstOffset.z = dchecked_cast<uint32_t>(dstCopy.origin.z);
             break;
     }
 
     DAWN_ASSERT(HasSameTextureCopyExtent(srcCopy, dstCopy, copySize));
     TexelExtent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize);
-    region.extent.width = static_cast<uint32_t>(imageExtent.width);
-    region.extent.height = static_cast<uint32_t>(imageExtent.height);
+    region.extent.width = dchecked_cast<uint32_t>(imageExtent.width);
+    region.extent.height = dchecked_cast<uint32_t>(imageExtent.height);
     region.extent.depth =
-        has3DTextureInCopy ? static_cast<uint32_t>(copySize.depthOrArrayLayers) : 1;
+        has3DTextureInCopy ? dchecked_cast<uint32_t>(copySize.depthOrArrayLayers) : 1;
 
     return region;
 }
diff --git a/src/dawn/native/vulkan/UtilsVulkan.cpp b/src/dawn/native/vulkan/UtilsVulkan.cpp
index 4829cb8..a9f6b90 100644
--- a/src/dawn/native/vulkan/UtilsVulkan.cpp
+++ b/src/dawn/native/vulkan/UtilsVulkan.cpp
@@ -243,9 +243,10 @@
     TexelExtent3D copySizeTexels = blockInfo.ToTexel(copySize);
 
     // In Vulkan the row length is in texels while it is in blocks for Dawn
-    region.bufferRowLength = static_cast<uint32_t>(blockInfo.ToTexelWidth(bufferCopy.blocksPerRow));
+    region.bufferRowLength =
+        dchecked_cast<uint32_t>(blockInfo.ToTexelWidth(bufferCopy.blocksPerRow));
     region.bufferImageHeight =
-        static_cast<uint32_t>(blockInfo.ToTexelHeight(bufferCopy.rowsPerImage));
+        dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(bufferCopy.rowsPerImage));
 
     region.imageSubresource.aspectMask = VulkanAspectMask(textureCopy.aspect);
     region.imageSubresource.mipLevel = textureCopy.mipLevel;
@@ -256,46 +257,46 @@
         case wgpu::TextureDimension::e1D:
             DAWN_ASSERT(textureCopy.origin.z == TexelCount{0} &&
                         copySizeTexels.depthOrArrayLayers == TexelCount{1});
-            region.imageOffset.x = static_cast<uint32_t>(textureCopy.origin.x);
+            region.imageOffset.x = dchecked_cast<uint32_t>(textureCopy.origin.x);
             region.imageOffset.y = 0;
             region.imageOffset.z = 0;
             region.imageSubresource.baseArrayLayer = 0;
             region.imageSubresource.layerCount = 1;
 
             DAWN_ASSERT(!textureCopy.texture->GetFormat().isCompressed);
-            region.imageExtent.width = static_cast<uint32_t>(copySizeTexels.width);
+            region.imageExtent.width = dchecked_cast<uint32_t>(copySizeTexels.width);
             region.imageExtent.height = 1;
             region.imageExtent.depth = 1;
             break;
 
         case wgpu::TextureDimension::e2D: {
-            region.imageOffset.x = static_cast<uint32_t>(textureCopy.origin.x);
-            region.imageOffset.y = static_cast<uint32_t>(textureCopy.origin.y);
+            region.imageOffset.x = dchecked_cast<uint32_t>(textureCopy.origin.x);
+            region.imageOffset.y = dchecked_cast<uint32_t>(textureCopy.origin.y);
             region.imageOffset.z = 0;
-            region.imageSubresource.baseArrayLayer = static_cast<uint32_t>(textureCopy.origin.z);
+            region.imageSubresource.baseArrayLayer = dchecked_cast<uint32_t>(textureCopy.origin.z);
             region.imageSubresource.layerCount =
-                static_cast<uint32_t>(copySizeTexels.depthOrArrayLayers);
+                dchecked_cast<uint32_t>(copySizeTexels.depthOrArrayLayers);
 
             TexelExtent3D imageExtent =
                 ComputeTextureCopyExtent(textureCopy, copySizeTexels.ToExtent3D());
-            region.imageExtent.width = static_cast<uint32_t>(imageExtent.width);
-            region.imageExtent.height = static_cast<uint32_t>(imageExtent.height);
+            region.imageExtent.width = dchecked_cast<uint32_t>(imageExtent.width);
+            region.imageExtent.height = dchecked_cast<uint32_t>(imageExtent.height);
             region.imageExtent.depth = 1;
             break;
         }
 
         case wgpu::TextureDimension::e3D: {
-            region.imageOffset.x = static_cast<uint32_t>(textureCopy.origin.x);
-            region.imageOffset.y = static_cast<uint32_t>(textureCopy.origin.y);
-            region.imageOffset.z = static_cast<uint32_t>(textureCopy.origin.z);
+            region.imageOffset.x = dchecked_cast<uint32_t>(textureCopy.origin.x);
+            region.imageOffset.y = dchecked_cast<uint32_t>(textureCopy.origin.y);
+            region.imageOffset.z = dchecked_cast<uint32_t>(textureCopy.origin.z);
             region.imageSubresource.baseArrayLayer = 0;
             region.imageSubresource.layerCount = 1;
 
             TexelExtent3D imageExtent =
                 ComputeTextureCopyExtent(textureCopy, copySizeTexels.ToExtent3D());
-            region.imageExtent.width = static_cast<uint32_t>(imageExtent.width);
-            region.imageExtent.height = static_cast<uint32_t>(imageExtent.height);
-            region.imageExtent.depth = static_cast<uint32_t>(copySizeTexels.depthOrArrayLayers);
+            region.imageExtent.width = dchecked_cast<uint32_t>(imageExtent.width);
+            region.imageExtent.height = dchecked_cast<uint32_t>(imageExtent.height);
+            region.imageExtent.depth = dchecked_cast<uint32_t>(copySizeTexels.depthOrArrayLayers);
             break;
         }
     }
diff --git a/src/dawn/native/webgpu/CaptureContext.cpp b/src/dawn/native/webgpu/CaptureContext.cpp
index 80964cb..72a35d4 100644
--- a/src/dawn/native/webgpu/CaptureContext.cpp
+++ b/src/dawn/native/webgpu/CaptureContext.cpp
@@ -311,9 +311,9 @@
 
 schema::Origin3D ToSchema(const TexelOrigin3D& origin) {
     return {{
-        .x = static_cast<uint32_t>(origin.x),
-        .y = static_cast<uint32_t>(origin.y),
-        .z = static_cast<uint32_t>(origin.z),
+        .x = dchecked_cast<uint32_t>(origin.x),
+        .y = dchecked_cast<uint32_t>(origin.y),
+        .z = dchecked_cast<uint32_t>(origin.z),
     }};
 }
 
@@ -326,9 +326,9 @@
 
 schema::Extent3D ToSchema(const TexelExtent3D& extent) {
     return {{
-        .width = static_cast<uint32_t>(extent.width),
-        .height = static_cast<uint32_t>(extent.height),
-        .depthOrArrayLayers = static_cast<uint32_t>(extent.depthOrArrayLayers),
+        .width = dchecked_cast<uint32_t>(extent.width),
+        .height = dchecked_cast<uint32_t>(extent.height),
+        .depthOrArrayLayers = dchecked_cast<uint32_t>(extent.depthOrArrayLayers),
     }};
 }
 
@@ -353,7 +353,7 @@
     return {{
         .offset = bufferCopy.offset,
         .bytesPerRow = static_cast<uint32_t>(blockInfo.ToBytes(bufferCopy.blocksPerRow)),
-        .rowsPerImage = static_cast<uint32_t>(bufferCopy.rowsPerImage),
+        .rowsPerImage = dchecked_cast<uint32_t>(bufferCopy.rowsPerImage),
     }};
 }
 
diff --git a/src/dawn/native/webgpu/TextureWGPU.cpp b/src/dawn/native/webgpu/TextureWGPU.cpp
index cb24f29..5ef2265 100644
--- a/src/dawn/native/webgpu/TextureWGPU.cpp
+++ b/src/dawn/native/webgpu/TextureWGPU.cpp
@@ -315,7 +315,8 @@
     // We only write out the beginning of each row, the rest is padding.
     for (BlockCount blockRow{0}; blockRow < blockRows; ++blockRow) {
         const void* data = wgpu->bufferGetConstMappedRange(
-            copyBuffer, uint32_t(blockRow) * alignedBytesPerRow, mappableBytesPerRow);
+            copyBuffer, dchecked_cast<uint32_t>(blockRow) * alignedBytesPerRow,
+            mappableBytesPerRow);
         writer.WriteContentBytes(data, usedBytesPerRow);
     }
     wgpu->bufferUnmap(copyBuffer);
@@ -386,12 +387,12 @@
                     .layout = {{
                         .offset = 0,
                         .bytesPerRow = usedBytesPerRow,
-                        .rowsPerImage = uint32_t(blockSize.height),
+                        .rowsPerImage = dchecked_cast<uint32_t>(blockSize.height),
                     }},
                     .size = {{
-                        .width = uint32_t(size.width),
-                        .height = uint32_t(size.height),
-                        .depthOrArrayLayers = uint32_t(size.depthOrArrayLayers),
+                        .width = dchecked_cast<uint32_t>(size.width),
+                        .height = dchecked_cast<uint32_t>(size.height),
+                        .depthOrArrayLayers = dchecked_cast<uint32_t>(size.depthOrArrayLayers),
                     }},
                     .dataSize = blockInfo.ToBytes(blockSize.width * blockSize.height *
                                                   blockSize.depthOrArrayLayers),
@@ -416,8 +417,8 @@
                         .origin =
                             {
                                 .x = 0,
-                                .y = uint32_t(blockInfo.ToTexelHeight(y)),
-                                .z = uint32_t(blockInfo.ToTexelHeight(z)),
+                                .y = dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(y)),
+                                .z = dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(z)),
                             },
                         .aspect = ToWGPU(aspect),
                     };
@@ -426,13 +427,13 @@
                             {
                                 .offset = 0,
                                 .bytesPerRow = alignedBytesPerRow,
-                                .rowsPerImage = uint32_t(blockRows),
+                                .rowsPerImage = dchecked_cast<uint32_t>(blockRows),
                             },
                         .buffer = copyBuffer,
                     };
                     WGPUExtent3D copySize{
-                        .width = uint32_t(blockInfo.ToTexelWidth(blockSize.width)),
-                        .height = uint32_t(blockInfo.ToTexelHeight(blockRows)),
+                        .width = dchecked_cast<uint32_t>(blockInfo.ToTexelWidth(blockSize.width)),
+                        .height = dchecked_cast<uint32_t>(blockInfo.ToTexelHeight(blockRows)),
                         .depthOrArrayLayers = 1,
                     };
 
diff --git a/src/dawn/native/webgpu/ToWGPU.cpp b/src/dawn/native/webgpu/ToWGPU.cpp
index 567a13d..1830d06 100644
--- a/src/dawn/native/webgpu/ToWGPU.cpp
+++ b/src/dawn/native/webgpu/ToWGPU.cpp
@@ -41,17 +41,17 @@
 
 WGPUExtent3D ToWGPU(const TexelExtent3D& extent) {
     return {
-        .width = static_cast<uint32_t>(extent.width),
-        .height = static_cast<uint32_t>(extent.height),
-        .depthOrArrayLayers = static_cast<uint32_t>(extent.depthOrArrayLayers),
+        .width = dchecked_cast<uint32_t>(extent.width),
+        .height = dchecked_cast<uint32_t>(extent.height),
+        .depthOrArrayLayers = dchecked_cast<uint32_t>(extent.depthOrArrayLayers),
     };
 }
 
 WGPUOrigin3D ToWGPU(const TexelOrigin3D& origin) {
     return {
-        .x = static_cast<uint32_t>(origin.x),
-        .y = static_cast<uint32_t>(origin.y),
-        .z = static_cast<uint32_t>(origin.z),
+        .x = dchecked_cast<uint32_t>(origin.x),
+        .y = dchecked_cast<uint32_t>(origin.y),
+        .z = dchecked_cast<uint32_t>(origin.z),
     };
 }
 
@@ -92,7 +92,7 @@
             {
                 .offset = copy.offset,
                 .bytesPerRow = static_cast<uint32_t>(blockInfo.ToBytes(copy.blocksPerRow)),
-                .rowsPerImage = static_cast<uint32_t>(copy.rowsPerImage),
+                .rowsPerImage = dchecked_cast<uint32_t>(copy.rowsPerImage),
             },
         .buffer = ToBackend(copy.buffer)->GetInnerHandle(),
     };
diff --git a/src/dawn/tests/unittests/ITypVectorTests.cpp b/src/dawn/tests/unittests/ITypVectorTests.cpp
index c89030e..145f830 100644
--- a/src/dawn/tests/unittests/ITypVectorTests.cpp
+++ b/src/dawn/tests/unittests/ITypVectorTests.cpp
@@ -72,7 +72,7 @@
 
     // Initializer list constructor
     {
-        Vector vec = {Val(2), Val(8), Val(1)};
+        Vector vec = {Val(2u), Val(8u), Val(1u)};
         ASSERT_EQ(vec.size(), Key(3));
         ASSERT_EQ(vec[Key(0)], Val(2));
         ASSERT_EQ(vec[Key(1)], Val(8));
@@ -84,7 +84,7 @@
 TEST_F(ITypVectorTest, CopyConstructAssign) {
     // Test the copy constructor
     {
-        Vector rhs = {Val(2), Val(8), Val(1)};
+        Vector rhs = {Val(2u), Val(8u), Val(1u)};
 
         Vector vec(rhs);
         ASSERT_EQ(vec.size(), Key(3));
@@ -100,7 +100,7 @@
 
     // Test the copy assignment
     {
-        Vector rhs = {Val(2), Val(8), Val(1)};
+        Vector rhs = {Val(2u), Val(8u), Val(1u)};
 
         Vector vec = rhs;
         ASSERT_EQ(vec.size(), Key(3));
@@ -119,7 +119,7 @@
 TEST_F(ITypVectorTest, MoveConstructAssign) {
     // Test the move constructor
     {
-        Vector rhs = {Val(2), Val(8), Val(1)};
+        Vector rhs = {Val(2u), Val(8u), Val(1u)};
 
         Vector vec(std::move(rhs));
         ASSERT_EQ(vec.size(), Key(3));
@@ -130,7 +130,7 @@
 
     // Test the move assignment
     {
-        Vector rhs = {Val(2), Val(8), Val(1)};
+        Vector rhs = {Val(2u), Val(8u), Val(1u)};
 
         Vector vec = std::move(rhs);
         ASSERT_EQ(vec.size(), Key(3));
diff --git a/src/dawn/tests/unittests/d3d12/CopySplitTests.cpp b/src/dawn/tests/unittests/d3d12/CopySplitTests.cpp
index 723dfdc..4d48fc8 100644
--- a/src/dawn/tests/unittests/d3d12/CopySplitTests.cpp
+++ b/src/dawn/tests/unittests/d3d12/CopySplitTests.cpp
@@ -85,9 +85,10 @@
             bufferSpec.offset +
             // TOOD(425944899): add overload of RequiredBytesInCopy that accepts strong types
             utils::RequiredBytesInCopy(
-                bufferSpec.bytesPerRow, static_cast<uint32_t>(bufferSpec.rowsPerImage),
-                static_cast<uint32_t>(widthInBlocks), static_cast<uint32_t>(heightInBlocks),
-                static_cast<uint32_t>(textureSpec.copySize.depthOrArrayLayers), blockInfo.byteSize);
+                bufferSpec.bytesPerRow, dchecked_cast<uint32_t>(bufferSpec.rowsPerImage),
+                dchecked_cast<uint32_t>(widthInBlocks), dchecked_cast<uint32_t>(heightInBlocks),
+                dchecked_cast<uint32_t>(textureSpec.copySize.depthOrArrayLayers),
+                blockInfo.byteSize);
 
         // The last pixel (buffer footprint) of each copy region depends on its
         // bufferOffset and copySize. It is not the last pixel where the bufferSize
@@ -108,10 +109,10 @@
             copy.alignedOffset +
             utils::RequiredBytesInCopy(
                 bufferSpec.bytesPerRow,
-                static_cast<uint32_t>(blockInfo.ToBlockHeight(bufferSize.height)),
-                static_cast<uint32_t>(footprintWidthInBlocks),
-                static_cast<uint32_t>(footprintHeightInBlocks),
-                static_cast<uint32_t>(blockInfo.ToBlockDepth(bufferSize.depthOrArrayLayers)),
+                dchecked_cast<uint32_t>(blockInfo.ToBlockHeight(bufferSize.height)),
+                dchecked_cast<uint32_t>(footprintWidthInBlocks),
+                dchecked_cast<uint32_t>(footprintHeightInBlocks),
+                dchecked_cast<uint32_t>(blockInfo.ToBlockDepth(bufferSize.depthOrArrayLayers)),
                 blockInfo.byteSize);
 
         // The buffer footprint of each copy region should not exceed the minimum
@@ -493,7 +494,7 @@
                     const uint64_t bytesPerLayer = blockInfo.ToBytes(blocksPerRow * rowsPerImage);
                     for (BlockCount copyLayer : Range(copySize.depthOrArrayLayers)) {
                         const uint32_t splitIndex =
-                            static_cast<uint32_t>(copyLayer) % copySplits.copySubresources.size();
+                            dchecked_cast<uint32_t>(copyLayer) % copySplits.copySubresources.size();
                         const TextureCopySubresource& copySubresourcePerLayer =
                             copySplits.copySubresources[splitIndex];
 
@@ -508,7 +509,7 @@
                         // computed assuming an offset of bytesPerLayer from the previous (1st)
                         // copy, and ValidateFootprints will check for that.
                         const uint64_t bufferOffsetForNextLayer =
-                            bytesPerLayer * static_cast<uint32_t>(splitIndex);
+                            bytesPerLayer * dchecked_cast<uint32_t>(splitIndex);
                         bufferSpecCopy.offset += bufferOffsetForNextLayer;
 
                         // Modify texture spec for a single layer
diff --git a/src/dawn/tests/unittests/native/StreamTests.cpp b/src/dawn/tests/unittests/native/StreamTests.cpp
index d98f393..4b5dff8 100644
--- a/src/dawn/tests/unittests/native/StreamTests.cpp
+++ b/src/dawn/tests/unittests/native/StreamTests.cpp
@@ -657,7 +657,7 @@
     // Test pairs.
     std::vector<std::pair<int, float>>{{1, 3.}, {6, 4.}},
     // Test TypedIntegers
-    std::vector<TypedIntegerForTest>{TypedIntegerForTest(42), TypedIntegerForTest(13)},
+    std::vector<TypedIntegerForTest>{TypedIntegerForTest(42u), TypedIntegerForTest(13u)},
     // Test enums
     std::vector<wgpu::TextureUsage>{wgpu::TextureUsage::CopyDst,
                                     wgpu::TextureUsage::RenderAttachment},
diff --git a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
index 05f034d..0cb2ae0 100644
--- a/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
+++ b/src/dawn/tests/white_box/D3D12DescriptorHeapTests.cpp
@@ -1684,7 +1684,7 @@
     const HeapVersionID heapSerial = allocator->GetShaderVisibleHeapSerialForTesting();
 
     uint32_t tableSize = heapSize;
-    for (int i = 0;; ++i) {
+    for (HeapVersionID i{0u};; ++i) {
         wgpu::ResourceTable table = MakeResourceTable(tableSize - mImplicitDescriptorCount);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
@@ -1696,7 +1696,7 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + HeapVersionID(i));
+        EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + i);
 
         // Make the table grow so that each iteration is double the previous heap size
         // to force the allocator to grow by double its current size.
@@ -1728,7 +1728,7 @@
     const HeapVersionID heapSerial = allocator->GetShaderVisibleHeapSerialForTesting();
 
     uint32_t tableSize = heapSize;
-    for (int i = 0;; ++i) {
+    for (HeapVersionID i{0u};; ++i) {
         wgpu::ResourceTable table = MakeResourceTable(tableSize - mImplicitDescriptorCount);
 
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
@@ -1740,7 +1740,7 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + HeapVersionID(i));
+        EXPECT_EQ(allocator->GetShaderVisibleHeapSerialForTesting(), heapSerial + i);
 
         // Make the table grow so that each iteration is quadruple the previous heap size
         // to force the allocator to grow by quadruple its current size.
diff --git a/src/utils/numeric.h b/src/utils/numeric.h
index 55e543f..bfe9696 100644
--- a/src/utils/numeric.h
+++ b/src/utils/numeric.h
@@ -56,6 +56,20 @@
     return Dst{static_cast<IDst>(valueISrc)};
 }
 
+template <HasUnderlyingType Dst, HasUnderlyingType Src>
+constexpr inline Dst dchecked_cast(const Src& value) {
+    using ISrc = UnderlyingType<Src>;
+    using IDst = UnderlyingType<Dst>;
+    ISrc valueISrc = static_cast<ISrc>(value);
+    DAWN_ASSERT(std::in_range<IDst>(valueISrc));
+    return Dst{static_cast<IDst>(valueISrc)};
+}
+
+template <std::integral From, std::integral To>
+constexpr inline bool kIsCastAlwaysInRange =
+    std::in_range<To>(std::numeric_limits<From>::lowest()) &&
+    std::in_range<To>(std::numeric_limits<From>::max());
+
 template <typename T>
 bool inline IsDoubleValueRepresentable(double value) {
     if constexpr (std::is_same_v<T, float> || std::is_integral_v<T>) {
diff --git a/src/utils/numeric_test.cc b/src/utils/numeric_test.cc
index 72becaa..c5ce073 100644
--- a/src/utils/numeric_test.cc
+++ b/src/utils/numeric_test.cc
@@ -34,9 +34,22 @@
 namespace dawn {
 namespace {
 
+TEST(NumericTest, IsCastAlwaysInRange) {
+    static_assert(kIsCastAlwaysInRange<uint32_t, uint64_t>);
+    static_assert(!kIsCastAlwaysInRange<int32_t, uint32_t>);
+    static_assert(!kIsCastAlwaysInRange<uint32_t, int32_t>);
+    static_assert(!kIsCastAlwaysInRange<uint64_t, uint32_t>);
+}
+
 // Death tests
 // Name "*DeathTest" per https://google.github.io/googletest/advanced.html#death-test-naming
 
+#ifdef DAWN_ENABLE_ASSERTS
+#define EXPECT_DCHECK(statement, matcher) EXPECT_DEATH(statement, matcher)
+#else
+#define EXPECT_DCHECK(statement, matcher) statement
+#endif
+
 // Test for checked cast between various types.
 template <typename T32, typename T64>
 void CheckedCastTestPair() {
@@ -91,6 +104,11 @@
     // TypedInteger <-> enum class
     CheckedCastTest<TypedU32, TypedI32, EnumU64, EnumI64>();
     CheckedCastTest<EnumU32, EnumI32, EnumU64, EnumI64>();
+
+    // Basic test that dchecked_cast is checked in debug but not release.
+    // Not exhaustive like the cases above because those are very slow.
+    EXPECT_DCHECK(dchecked_cast<uint32_t>(uint64_t{std::numeric_limits<uint32_t>::max()} + 1), "");
+    EXPECT_DCHECK(dchecked_cast<int32_t>(uint32_t{std::numeric_limits<int32_t>::max()} + 1), "");
 }
 
 }  // anonymous namespace
diff --git a/src/utils/typed_integer.h b/src/utils/typed_integer.h
index 29aea58..bc3df3b 100644
--- a/src/utils/typed_integer.h
+++ b/src/utils/typed_integer.h
@@ -37,6 +37,7 @@
 #include <utility>
 
 #include "src/utils/assert.h"
+#include "src/utils/numeric.h"
 #include "src/utils/underlying_type.h"
 
 namespace dawn {
@@ -74,8 +75,7 @@
 class TypedIntegerImpl;
 }  // namespace detail
 
-template <typename Tag, typename T>
-    requires std::integral<T>
+template <typename Tag, std::integral T>
 #if defined(DAWN_ENABLE_ASSERTS)
 using TypedInteger = detail::TypedIntegerImpl<Tag, T>;
 #else
@@ -85,7 +85,7 @@
 namespace detail {
 template <typename Tag, typename T>
 class alignas(T) TypedIntegerImpl {
-    static_assert(std::is_integral<T>::value, "TypedInteger must be integral");
+    static_assert(std::is_integral_v<T>, "TypedInteger must be integral");
     T mValue;
 
   public:
@@ -94,33 +94,32 @@
         static_assert(sizeof(TypedIntegerImpl) == sizeof(T));
     }
 
-    // Construction from non-narrowing integral types.
-    // TODO(425911085): Consider allowing construction from narrowing types, but assert
-    // that it doesn't truncate.
-    template <typename I>
-        requires std::integral<I> && std::unsigned_integral<T>
-    explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast<T>(rhs)) {
-        DAWN_ASSERT(rhs >= 0);
-        static_assert(static_cast<uint64_t>(std::numeric_limits<I>::max()) <=
-                      static_cast<uint64_t>(std::numeric_limits<T>::max()));
-    }
-    template <typename I>
-        requires std::integral<I> && std::signed_integral<T>
-    explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast<T>(rhs)) {
-        static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<T>::max());
-        static_assert(std::numeric_limits<I>::min() >= std::numeric_limits<T>::min());
+    // Lossless conversion: primitive -> TypedInteger (constructor).
+    // If you need a lossy (narrowing) conversion, use (d)checked_cast.
+    template <std::integral Src>
+        requires kIsCastAlwaysInRange<Src, T>
+    explicit constexpr TypedIntegerImpl(Src src) : mValue(static_cast<T>(src)) {}
+
+    // In consteval, we can allow narrowing casts because we can check the actual value at compile.
+    // TODO(crbug.com/515794394): Remove this due to issues with lambda type inference.
+    template <std::integral Src>
+        requires(!kIsCastAlwaysInRange<Src, T>)
+    explicit consteval TypedIntegerImpl(Src src) : mValue(checked_cast<T>(src)) {}
+
+    // Lossless conversion: TypedInteger -> primitive (cast)
+    // If you need a lossy (narrowing) conversion, use (d)checked_cast.
+    template <std::integral Dst>
+        requires kIsCastAlwaysInRange<T, Dst>
+    explicit constexpr operator Dst() const {
+        return static_cast<Dst>(this->mValue);
     }
 
-    // Allow explicit casts to any integral type. If the type is smaller than T,
-    // we assert that no truncation occurs.
-    template <typename I>
-        requires std::integral<I>
-    explicit constexpr operator I() const {
-        if constexpr (sizeof(I) < sizeof(T)) {
-            // If downcasting, assert that the value is not truncated
-            DAWN_ASSERT(static_cast<I>(this->mValue) == this->mValue);
-        }
-        return static_cast<I>(this->mValue);
+    // In consteval, we can allow narrowing casts because we can check the actual value at compile.
+    // TODO(crbug.com/515794394): Remove this due to issues with lambda type inference.
+    template <std::integral Dst>
+        requires(!kIsCastAlwaysInRange<T, Dst>)
+    explicit consteval operator Dst() const {
+        return checked_cast<Dst>(this->mValue);
     }
 
     // Same-tag TypedInteger comparison operators
diff --git a/src/utils/typed_integer_tests.cc b/src/utils/typed_integer_tests.cc
index 140fec7..d66e59c 100644
--- a/src/utils/typed_integer_tests.cc
+++ b/src/utils/typed_integer_tests.cc
@@ -53,6 +53,7 @@
 }
 
 // Test that typed integers can be explicitly cast to other integral types
+// (either using a static_cast or a checked_cast depending on the type pair).
 TEST_F(TypedIntegerTest, CastToOther) {
     using Unsigned64 = TypedInteger<struct Unsigned64T, uint64_t>;
     using Signed64 = TypedInteger<struct Signed64T, int64_t>;
@@ -70,35 +71,35 @@
 
     {
         Signed64 svalue64(maxI32);
-        EXPECT_EQ(static_cast<int32_t>(svalue64), maxI32);
+        EXPECT_EQ(checked_cast<int32_t>(svalue64), maxI32);
 
         Signed32 svalue32(maxI16);
         EXPECT_EQ(static_cast<int16_t>(svalue32), maxI16);
 
         Signed16 svalue16(maxI8);
-        EXPECT_EQ(static_cast<int8_t>(svalue16), maxI8);
+        EXPECT_EQ(checked_cast<int8_t>(svalue16), maxI8);
     }
     {
         Unsigned64 uvalue64(maxU32);
-        EXPECT_EQ(static_cast<uint32_t>(uvalue64), maxU32);
+        EXPECT_EQ(checked_cast<uint32_t>(uvalue64), maxU32);
 
         Unsigned32 uvalue32(maxU16);
         EXPECT_EQ(static_cast<uint16_t>(uvalue32), maxU16);
 
         Unsigned16 uvalue16(maxU8);
-        EXPECT_EQ(static_cast<uint8_t>(uvalue16), maxU8);
+        EXPECT_EQ(checked_cast<uint8_t>(uvalue16), maxU8);
     }
     {
         Signed64 svalue64(maxI8);
-        EXPECT_EQ(static_cast<int32_t>(svalue64), maxI8);
-        EXPECT_EQ(static_cast<int16_t>(svalue64), maxI8);
-        EXPECT_EQ(static_cast<int8_t>(svalue64), maxI8);
+        EXPECT_EQ(checked_cast<int32_t>(svalue64), maxI8);
+        EXPECT_EQ(checked_cast<int16_t>(svalue64), maxI8);
+        EXPECT_EQ(checked_cast<int8_t>(svalue64), maxI8);
     }
     {
         Unsigned64 uvalue64(maxU8);
-        EXPECT_EQ(static_cast<uint32_t>(uvalue64), maxU8);
-        EXPECT_EQ(static_cast<uint16_t>(uvalue64), maxU8);
-        EXPECT_EQ(static_cast<uint8_t>(uvalue64), maxU8);
+        EXPECT_EQ(checked_cast<uint32_t>(uvalue64), maxU8);
+        EXPECT_EQ(checked_cast<uint16_t>(uvalue64), maxU8);
+        EXPECT_EQ(checked_cast<uint8_t>(uvalue64), maxU8);
     }
 }
 
@@ -380,28 +381,12 @@
     EXPECT_EQ(Signed(9), ityp::PlusOne(ityp::PlusOne(seven)));
 }
 
-// Tests for bounds assertions on arithmetic overflow and underflow.
-#if defined(DAWN_ENABLE_ASSERTS)
-
 // Name "*DeathTest" per https://google.github.io/googletest/advanced.html#death-test-naming
 using TypedIntegerDeathTest = TypedIntegerTest;
 
-TEST_F(TypedIntegerDeathTest, CastToOtherTruncation) {
-    using Unsigned64 = TypedInteger<struct Unsigned64T, uint64_t>;
-    using Signed64 = TypedInteger<struct Signed64T, int64_t>;
-
-    constexpr int32_t maxI32 = std::numeric_limits<int32_t>::max();
-    constexpr uint32_t maxU32 = std::numeric_limits<uint32_t>::max();
-    constexpr int32_t minI32 = std::numeric_limits<int32_t>::min();
-
-    Signed64 too_large_for_i32(static_cast<int64_t>(maxI32) + 1);
-    Signed64 too_small_for_i32(static_cast<int64_t>(minI32) - 1);
-    Unsigned64 too_large_for_u32(static_cast<uint64_t>(maxU32) + 1);
-
-    EXPECT_DEATH({ [[maybe_unused]] auto result = static_cast<int32_t>(too_large_for_i32); }, "");
-    EXPECT_DEATH({ [[maybe_unused]] auto result = static_cast<int32_t>(too_small_for_i32); }, "");
-    EXPECT_DEATH({ [[maybe_unused]] auto result = static_cast<uint32_t>(too_large_for_u32); }, "");
-}
+// Tests for bounds assertions on arithmetic overflow and underflow.
+// Note (d)checked_cast tests are in NumericDeathTest.
+#if defined(DAWN_ENABLE_ASSERTS)
 
 TEST_F(TypedIntegerDeathTest, IncrementUnsignedOverflow) {
     Unsigned value(std::numeric_limits<uint32_t>::max() - 1);