Skip to content
This repository was archived by the owner on Oct 31, 2025. It is now read-only.
This repository was archived by the owner on Oct 31, 2025. It is now read-only.

min/max on ints results in quite branchy code #758

@expenses

Description

@expenses

This is perhaps more of a rust issue.

On 35172f7, this code results in the following spir-v:

#![cfg_attr(
    target_arch = "spirv",
    feature(register_attr),
    register_attr(spirv),
    no_std,
)]

// This needs to be here to provide `#[panic_handler]`.
extern crate spirv_std;

#[spirv(fragment)]
pub fn x() {
    let x = min_i32(1, 2);
    let y = min_u32(1, 2);
    let z = min_f32(1.0, 2.0);
}

#[inline(never)]
fn min_i32(a: i32, b: i32) -> i32 {
    a.min(b)
}

#[inline(never)]
fn min_u32(a: u32, b: u32) -> u32 {
    a.min(b)
}

#[inline(never)]
fn min_f32(a: f32, b: f32) -> f32 {
    a.min(b)
}

(With comments added to indicate the different functions)

; SPIR-V
; Version: 1.4
; Generator: Embark Studios Rust GPU Compiler Backend; 0
; Bound: 198
; Schema: 0
               OpCapability Int8
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical Simple
               OpEntryPoint Fragment %2 "x"
               OpExecutionMode %2 OriginUpperLeft
        %int = OpTypeInt 32 1
         %14 = OpTypeFunction %int %int %int
       %uint = OpTypeInt 32 0
         %16 = OpTypeFunction %uint %uint %uint
      %float = OpTypeFloat 32
         %18 = OpTypeFunction %float %float %float
       %char = OpTypeInt 8 1
       %void = OpTypeVoid
         %25 = OpTypeFunction %void
       %bool = OpTypeBool
      %false = OpConstantFalse %bool
       %true = OpConstantTrue %bool
     %uint_1 = OpConstant %uint 1
    %char_n1 = OpConstant %char -1
     %char_0 = OpConstant %char 0
     %char_1 = OpConstant %char 1
      %int_1 = OpConstant %int 1
      %int_2 = OpConstant %int 2
     %uint_2 = OpConstant %uint 2
    %float_1 = OpConstant %float 1
    %float_2 = OpConstant %float 2
         %41 = OpUndef %int
         %42 = OpUndef %uint
; min_i32:
          %9 = OpFunction %int DontInline %14
         %77 = OpFunctionParameter %int
         %78 = OpFunctionParameter %int
         %79 = OpLabel
        %166 = OpSLessThan %bool %77 %78
               OpSelectionMerge %176 None
               OpBranchConditional %166 %167 %168
        %167 = OpLabel
               OpBranch %176
        %168 = OpLabel
        %171 = OpIEqual %bool %77 %78
        %194 = OpSelect %char %171 %char_0 %char_1
               OpBranch %176
        %176 = OpLabel
        %177 = OpPhi %char %char_n1 %167 %194 %168
         %85 = OpSConvert %int %177
               OpSelectionMerge %86 None
               OpSwitch %85 %87 -1 %88 0 %89 1 %90
         %87 = OpLabel
               OpUnreachable
         %88 = OpLabel
               OpBranch %86
         %89 = OpLabel
               OpBranch %86
         %90 = OpLabel
               OpBranch %86
         %86 = OpLabel
         %94 = OpPhi %int %41 %88 %41 %89 %78 %90
         %95 = OpPhi %bool %true %88 %true %89 %false %90
        %195 = OpSelect %int %95 %77 %94
               OpReturnValue %195
               OpFunctionEnd
; min_u32:
         %10 = OpFunction %uint DontInline %16
        %112 = OpFunctionParameter %uint
        %113 = OpFunctionParameter %uint
        %114 = OpLabel
        %182 = OpULessThan %bool %112 %113
               OpSelectionMerge %192 None
               OpBranchConditional %182 %183 %184
        %183 = OpLabel
               OpBranch %192
        %184 = OpLabel
        %187 = OpIEqual %bool %112 %113
        %196 = OpSelect %char %187 %char_0 %char_1
               OpBranch %192
        %192 = OpLabel
        %193 = OpPhi %char %char_n1 %183 %196 %184
        %120 = OpSConvert %int %193
               OpSelectionMerge %121 None
               OpSwitch %120 %122 -1 %123 0 %124 1 %125
        %122 = OpLabel
               OpUnreachable
        %123 = OpLabel
               OpBranch %121
        %124 = OpLabel
               OpBranch %121
        %125 = OpLabel
               OpBranch %121
        %121 = OpLabel
        %129 = OpPhi %uint %42 %123 %42 %124 %113 %125
        %130 = OpPhi %bool %true %123 %true %124 %false %125
        %197 = OpSelect %uint %130 %112 %129
               OpReturnValue %197
               OpFunctionEnd
; min_f32:
         %11 = OpFunction %float DontInline %18
        %147 = OpFunctionParameter %float
        %148 = OpFunctionParameter %float
        %149 = OpLabel
        %150 = OpExtInst %float %1 FMin %147 %148
               OpReturnValue %150
               OpFunctionEnd
; main:
          %2 = OpFunction %void None %25
        %155 = OpLabel
        %158 = OpFunctionCall %int %9 %int_1 %int_2
        %159 = OpFunctionCall %uint %10 %uint_1 %uint_2
        %160 = OpFunctionCall %float %11 %float_1 %float_2
               OpReturn
               OpFunctionEnd

Expected Behaviour

While FMin is used correctly for floats, SMin and UMin are not used for integers. Same happens for max as well. Ideally they should be used as they're more-or-less guaranteed to generate the fastest code.

Alternatively, we could provide functions that call SMin/UMin via asm!.

  • Rustc version: nightly-2021-08-27-x86_64-pc-windows-msvc
  • SPIR-V version: SPIRV-Tools v2021.3 v2021.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    t: bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions