Name Strings
SPV_KHR_poison_freeze
Contact
To report problems with this extension, please open a new issue at:
Contributors
-
Tobias Hector, AMD
-
Jakub Kuderski, AMD
-
Alyssa Rosensweig, Valve
-
Alan Baker, Google
-
Jeff Bolz, Nvidia
-
Ben Ashbaugh, Intel
-
Victor Lomuller, Codeplay
-
Nicolai Hähnle, AMD
-
Graeme Leese, Broadcom
-
David Neto, Google
-
Raph Levien, Google
-
John Kessenich, Google
Notice
Copyright (c) 2025-2026 The Khronos Group Inc. Copyright terms at http://www.khronos.org/registry/speccopyright.html
Status
Experimental
-
Approved by the SPIR-V Working Group: 2026-01-07
-
Approved by the Khronos Board of Promoters: 2026-02-20
Version
Last Modified Date |
2026-03-12 |
Revision |
1 |
Dependencies
This extension is written against the SPIR-V Specification, Version 1.6 Revision 7.
This extension requires SPIR-V 1.0.
Overview
This extension adds the OpFreezeKHR instructions that enables compilers targeting SPIR-V to force a poison value to take an unspecified but stable value, while leaving existing stable values alone. In addition, a new execution mode is added that modifies several existing operations to now produce poison rather than directly resulting in undefined behavior.
Problem Statement
There has been a long standing clarity issue around the handling of undefined values in SPIR-V; some developers have assumed "undefined values" to imply that a single stable value is returned, while others took the meaning of OpUndef and assumed this applied. In practice, the edges of this have never been tested in any client APIs, so the details remained fuzzy until the core specification was updated to define the concept of poison, bringing it in line with LLVM’s solution to this problem.
However, just defining poison acknowledges that a suite of things that people have expected to work actually don’t work - and there is no clear fallback. This makes generating sound SPIR-V that avoids undefined behavior challenging, which is a particular problem for languages like WGSL where avoiding undefined behavior is critically important.
Solution Space
One solution to this problem could have been to just start testing if implementations are returning stable values or undefined values already. Unfortunately, without testing the likelihood is that many existing implementations would retroactively be considered buggy. Any implementations that updated to the newer definition would also likely have a negative performance impact on execution existing code, even if that code did not need such strong guarantees. It also means that client APIs and implementations would expend a lot of effort testing and fixing compilers to do something that we don’t necessarily want for the long term anyway, which brings us to the other option.
Looking to LLVM, they solved this issue by introducing the "freeze" instruction. This instruction can be used to turn a poison or undef value into a static valid value, with the expectation that higher level language compilers can use this instruction to make tighter guarantees about handling of undefined values. SPIR-V defining a similar instruction with a 1:1 meaning would also give a clean and straightforward translation between LLVM IR and SPIR-V, which is important in an ecosystem where translation both ways is relied upon by a huge number of tools and implementations.
This document thus proposes introducing a freeze instruction, as well as changing some instances of undefined behavior into generating poison.
Extension Name
To use this extension within a SPIR-V module, the following OpExtension must be present in the module:
OpExtension "SPV_KHR_poison_freeze"
Modifications to the SPIR-V Specification, Version 1.6
Capabilities
Modify Section 3.2.30, "Capability", adding rows to the Capability table:
Execution Modes
Modify Section 3.2.5, "Execution Mode", adding rows to the Capability table:
Decoration
Modify section 3.2.19, "Decoration", modifying entries in the table as follows:
Change the last sentence in the descriptions of NoSignedWrap and NoUnsignedWrap from:
If an instruction decorated with NoSignedWrap/NoUnsignedWrap does overflow or underflow, behavior is undefined.
to:
If an instruction decorated with NoSignedWrap/NoUnsignedWrap does overflow or underflow, its result is poison if ArithmeticPoisonKHR is declared, otherwise behavior is undefined.
Instructions
Modify section 3.3.1, "Miscellaneous Instructions":
Add the following text to the start of OpUndef:
Deprecated (use OpPoisonKHR and OpFreezeKHR instead)
|
The same behavior can be guaranteed by using OpPoisonKHR and freezing it via OpFreezeKHR from the perspective of a SPIR-V producer. For a SPIR-V consumer this is more conservative than OpUndef, but any impact on compilation output is expected to be negligible. |
Add the following instructions immediately after OpUndef:
|
This instruction generates a poison value. |
Capability: PoisonFreezeKHR |
||
3 |
5158 |
<id> Result Type |
Result <id> |
This instruction generates a stable value of the result type. The value of Result depends on Value:
Result is always a stable value. |
Capability: PoisonFreezeKHR |
|||
4 |
5159 |
<id> Result Type |
Result <id> |
<id> Value |
Modify section 3.3.11, "Conversion Instructions", modifying the following instructions:
Change the following sentence in the descriptions of OpConvertFToU and OpConvertFToS from:
Behavior is undefined if Result Type is not wide enough to hold the converted value.
to:
If Result Type is not wide enough to hold the converted value Result is poison if ArithmeticPoisonKHR is declared; otherwise behavior is undefined.
Change the following sentence in the description of OpConvertUToPtr from:
Behavior is undefined if the storage class of Result Type does not match the one used by the operation that produced the value of Integer Value.
to:
If the storage class of Result Type does not match the one used by the operation that produced the value of Integer Value, Result is poison if ArithmeticPoisonKHR is declared; otherwise behavior is undefined.
Change the following sentence in the description of OpBitcast from:
Behavior is undefined if the storage class of Result Type does not match the one used by the operation that produced the value of Integer Value.
to:
If the storage class of Result Type does not match the one used by the operation that produced the value of Operand, Result is poison if ArithmeticPoisonKHR is declared; otherwise behavior is undefined.
Modify section 3.3.11, "Conversion Instructions", modifying the following instructions:
Change the following sentence in the description of OpUDiv, OpSDiv, OpUMod, OpSRem, and OpSMod from:
Behavior is undefined if Operand 2 is 0.
to:
If Operand 2 is 0, Result is poison if ArithmeticPoisonKHR is declared; otherwise behavior is undefined.
Change the following sentence in the description of OpSDiv, OpSRem, and OpSMod from:
Behavior is undefined if Operand 2 is -1 and Operand 1 is the minimum representable value for the operands' type, causing signed overflow.
to:
If Operand 2 is -1 and Operand 1 is the minimum representable value for the operands' type, causing signed overflow, Result is poison if ArithmeticPoisonKHR is declared; otherwise behavior is undefined.
Issues
.1. Should future extensions adding data transforms result in poison instead?
Yes. Unless explicitly called out why poison is not returned, it should be considered a spec bug to not, even when this functionality is not present.
We should work to ensure that UB can never be the direct result of a data transformation.
.2. What about extensions not listed here that already exist?
This should also be considered a bug - missing interactions should be added.
Revision History
| Rev | Date | Author | Changes |
|---|---|---|---|
1 |
2026-03-12 |
Tobias Hector |
Initial KHR extension. |