Skip to content

LLVM miscompiles passing/returning half on several backends by using lossy conversions #97981

Description

@beetrees

Consider the following IR:

define half @to_half(i16 %bits) {
    %f = bitcast i16 %bits to half
    ret half %f
}

define i16 @from_half(half %f) {
    %bits = bitcast half %f to i16
    ret i16 %bits
}

As the only operation involved is a bitcast (in particular, there are no floating point type conversions in the LLVM IR), the values returned from to_half and from_half should be bit-for-bit identical to the value passed to them as their only argument (just a different type). However, several targets pass/return half as a float. On these targets, LLVM will use the default float conversion builtins (such as __gnu_h2f_ieee and __gnu_f2h_ieee) to convert between half and float. The issue is that these builtins silence signalling NaNs which changes the NaN payload, meaning that the roundtrip of half -> float -> half is not lossless and causes the generated ASM to violate the semantics of LLVM IR. This miscompilation is similar to #66803.

By inspecting the assembly LLVM emits, I've discovered that at least the following backends appear to be affected (in brackets are a specific target triple that I've checked):

As none of these target's ABI specifications (that I've been able to find) specify how half should be passed (nor does Clang support _Float16 on any of these targets), and given that these targets are a subset of those affected by #97975, I'm filing this as a single issue as the ABI has probably been selected as an automatic default by LLVM rather than a deliberate choice by the backends. Ultimately there are two possible solutions: either fix LLVM to codegen lossless conversions between half and float when needed for the ABI (one way to do this would be with a new pair of builtins that don't silence signalling NaNs), or change the ABIs to pass/return half without converting it to float (probably using the same ABI as i16, but some targets might have better options).

Related to #97975.

Activity

  1. llvmbot commented on Jul 8, 2024

    @llvmbot
    Member

    @llvm/issue-subscribers-backend-powerpc

    Author: None (beetrees)

    Details Consider the following IR: ```llvm define half @to_half(i16 %bits) { %f = bitcast i16 %bits to half ret half %f }

    define i16 @from_half(half %f) {
    %bits = bitcast half %f to i16
    ret i16 %bits
    }

    As the only operation involved is a bitcast (in particular, there are no floating point type conversions in the LLVM IR), the values returned from `to_half` and `from_half` should be bit-for-bit identical to the value passed to them as their only argument (just a different type). However, several targets pass/return `half` as a `float`. On these targets, LLVM will use the default float conversion builtins (such as `__gnu_h2f_ieee` and `__gnu_f2h_ieee`) to convert between `half` and `float`. The issue is that these builtins silence signalling NaNs which changes the NaN payload, meaning that the roundtrip of `half` -&gt; `float` -&gt; `half` is not lossless and causes the generated ASM to violate the semantics of LLVM IR. This miscompilation is similar to #<!-- -->66803.
    
    By inspecting the assembly LLVM emits, I've discovered that at least the following backends appear to be affected (in brackets are a specific target triple that I've checked):
    
    - Hexagon (`hexagon-unknown-linux-musl`)
    - MIPS (`mips64el-unknown-linux-gnuabi64`)
    - PowerPC (`powerpc64le-unknown-linux-gnu`)
    - SPARC (`sparc64-unknown-linux-gnu`)
    - WASM (`wasm32-unknown-wasi`): Already reported in #<!-- -->96438
    
    As none of these target's ABI specifications (that I've been able to find) specify how `half` should be passed (nor does Clang support `_Float16` on any of these targets), and given that these targets are a subset of those affected by #<!-- -->97975, I'm filing this as a single issue as the ABI has probably been selected as an automatic default by LLVM rather than a deliberate choice by the backends. Ultimately there are two possible solutions: either fix LLVM to codegen lossless conversions between `half` and `float` when needed for the ABI (one way to do this would be with a new pair of builtins that don't silence signalling NaNs), or change the ABIs to pass/return `half` without converting it to `float` (probably using the same ABI as `i16`, but some targets might have better options).
    </details>
    
  2. llvmbot commented on Jul 8, 2024

    @llvmbot
    Member

    @llvm/issue-subscribers-backend-mips

    Author: None (beetrees)

    Details Consider the following IR: ```llvm define half @to_half(i16 %bits) { %f = bitcast i16 %bits to half ret half %f }

    define i16 @from_half(half %f) {
    %bits = bitcast half %f to i16
    ret i16 %bits
    }

    As the only operation involved is a bitcast (in particular, there are no floating point type conversions in the LLVM IR), the values returned from `to_half` and `from_half` should be bit-for-bit identical to the value passed to them as their only argument (just a different type). However, several targets pass/return `half` as a `float`. On these targets, LLVM will use the default float conversion builtins (such as `__gnu_h2f_ieee` and `__gnu_f2h_ieee`) to convert between `half` and `float`. The issue is that these builtins silence signalling NaNs which changes the NaN payload, meaning that the roundtrip of `half` -&gt; `float` -&gt; `half` is not lossless and causes the generated ASM to violate the semantics of LLVM IR. This miscompilation is similar to #<!-- -->66803.
    
    By inspecting the assembly LLVM emits, I've discovered that at least the following backends appear to be affected (in brackets are a specific target triple that I've checked):
    
    - Hexagon (`hexagon-unknown-linux-musl`)
    - MIPS (`mips64el-unknown-linux-gnuabi64`)
    - PowerPC (`powerpc64le-unknown-linux-gnu`)
    - SPARC (`sparc64-unknown-linux-gnu`)
    - WASM (`wasm32-unknown-wasi`): Already reported in #<!-- -->96438
    
    As none of these target's ABI specifications (that I've been able to find) specify how `half` should be passed (nor does Clang support `_Float16` on any of these targets), and given that these targets are a subset of those affected by #<!-- -->97975, I'm filing this as a single issue as the ABI has probably been selected as an automatic default by LLVM rather than a deliberate choice by the backends. Ultimately there are two possible solutions: either fix LLVM to codegen lossless conversions between `half` and `float` when needed for the ABI (one way to do this would be with a new pair of builtins that don't silence signalling NaNs), or change the ABIs to pass/return `half` without converting it to `float` (probably using the same ABI as `i16`, but some targets might have better options).
    </details>
    
  3. llvmbot commented on Jul 8, 2024

    @llvmbot
    Member

    @llvm/issue-subscribers-backend-hexagon

    Author: None (beetrees)

    Details Consider the following IR: ```llvm define half @to_half(i16 %bits) { %f = bitcast i16 %bits to half ret half %f }

    define i16 @from_half(half %f) {
    %bits = bitcast half %f to i16
    ret i16 %bits
    }

    As the only operation involved is a bitcast (in particular, there are no floating point type conversions in the LLVM IR), the values returned from `to_half` and `from_half` should be bit-for-bit identical to the value passed to them as their only argument (just a different type). However, several targets pass/return `half` as a `float`. On these targets, LLVM will use the default float conversion builtins (such as `__gnu_h2f_ieee` and `__gnu_f2h_ieee`) to convert between `half` and `float`. The issue is that these builtins silence signalling NaNs which changes the NaN payload, meaning that the roundtrip of `half` -&gt; `float` -&gt; `half` is not lossless and causes the generated ASM to violate the semantics of LLVM IR. This miscompilation is similar to #<!-- -->66803.
    
    By inspecting the assembly LLVM emits, I've discovered that at least the following backends appear to be affected (in brackets are a specific target triple that I've checked):
    
    - Hexagon (`hexagon-unknown-linux-musl`)
    - MIPS (`mips64el-unknown-linux-gnuabi64`)
    - PowerPC (`powerpc64le-unknown-linux-gnu`)
    - SPARC (`sparc64-unknown-linux-gnu`)
    - WASM (`wasm32-unknown-wasi`): Already reported in #<!-- -->96438
    
    As none of these target's ABI specifications (that I've been able to find) specify how `half` should be passed (nor does Clang support `_Float16` on any of these targets), and given that these targets are a subset of those affected by #<!-- -->97975, I'm filing this as a single issue as the ABI has probably been selected as an automatic default by LLVM rather than a deliberate choice by the backends. Ultimately there are two possible solutions: either fix LLVM to codegen lossless conversions between `half` and `float` when needed for the ABI (one way to do this would be with a new pair of builtins that don't silence signalling NaNs), or change the ABIs to pass/return `half` without converting it to `float` (probably using the same ABI as `i16`, but some targets might have better options).
    </details>
    
  4. llvmbot commented on Jul 8, 2024

    @llvmbot
    Member

    @llvm/issue-subscribers-backend-webassembly

    Author: None (beetrees)

    Details Consider the following IR: ```llvm define half @to_half(i16 %bits) { %f = bitcast i16 %bits to half ret half %f }

    define i16 @from_half(half %f) {
    %bits = bitcast half %f to i16
    ret i16 %bits
    }

    As the only operation involved is a bitcast (in particular, there are no floating point type conversions in the LLVM IR), the values returned from `to_half` and `from_half` should be bit-for-bit identical to the value passed to them as their only argument (just a different type). However, several targets pass/return `half` as a `float`. On these targets, LLVM will use the default float conversion builtins (such as `__gnu_h2f_ieee` and `__gnu_f2h_ieee`) to convert between `half` and `float`. The issue is that these builtins silence signalling NaNs which changes the NaN payload, meaning that the roundtrip of `half` -&gt; `float` -&gt; `half` is not lossless and causes the generated ASM to violate the semantics of LLVM IR. This miscompilation is similar to #<!-- -->66803.
    
    By inspecting the assembly LLVM emits, I've discovered that at least the following backends appear to be affected (in brackets are a specific target triple that I've checked):
    
    - Hexagon (`hexagon-unknown-linux-musl`)
    - MIPS (`mips64el-unknown-linux-gnuabi64`)
    - PowerPC (`powerpc64le-unknown-linux-gnu`)
    - SPARC (`sparc64-unknown-linux-gnu`)
    - WASM (`wasm32-unknown-wasi`): Already reported in #<!-- -->96438
    
    As none of these target's ABI specifications (that I've been able to find) specify how `half` should be passed (nor does Clang support `_Float16` on any of these targets), and given that these targets are a subset of those affected by #<!-- -->97975, I'm filing this as a single issue as the ABI has probably been selected as an automatic default by LLVM rather than a deliberate choice by the backends. Ultimately there are two possible solutions: either fix LLVM to codegen lossless conversions between `half` and `float` when needed for the ABI (one way to do this would be with a new pair of builtins that don't silence signalling NaNs), or change the ABIs to pass/return `half` without converting it to `float` (probably using the same ABI as `i16`, but some targets might have better options).
    </details>
    
  5. vchuravy commented on Jul 8, 2024

    @vchuravy
    Contributor

    FYI: On PPC atleast you need to add zext I think. (On PPC i16 is being passed as i32 and that can cause issue)

    define half @to_half(zext i16 %bits) {
        %f = bitcast i16 %bits to half
        ret half %f
    }
    
  6. programmerjake commented on Jul 8, 2024

    @programmerjake
    Contributor

    FYI: On PPC atleast you need to add zext I think. (On PPC i16 is being passed as i32 and that can cause issue)

    I remember seeing somewhere that s390x requires either zext or sext for all integer arguments and returns, since they have different ABIs and the generated code assumes they've been sign/zero extended properly.

  7. 65 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions