llvm / llvm/llvm-project

Eliminate redundant vector movemask `zext`s

Open
#195,563 0 comments 0 reactions 0 assignees View on GitHub
llvm:instcombine missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Zig version: ([Godbolt](https://zig.godbo.lt/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:16,fontUsePx:'0',j:1,lang:zig,selection:(endColumn:2,endLineNumber:4,positionColumn:2,positionLineNumber:4,selectionStartColumn:2,selectionStartLineNumber:4,startColumn:2,startLineNumber:4),source:'const+V+%3D+@Vector(4,+u8)%3B%0Aexport+fn+foo(x:+*V)+u8+%7B%0A++++return+@as(u4,+@bitCast(x.*+%3D%3D+@as(V,+@splat(!'%3D!'))))%3B%0A%7D'),l:'5',n:'0',o:'Zig+source+%231',t:'0')),k:51.88636954553198,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:ztrunk,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:16,fontUsePx:'0',j:2,lang:zig,libs:!(),options:'-O+ReleaseFast+-target+x86_64-linux+-mcpu%3Dznver5+-fomit-frame-pointer',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+zig+trunk+(Editor+%231)',t:'0')),header:(),k:48.11363045446803,l:'4',m:100,n:'0',o:'',s:0,t:'0')),l:'2',m:100,n:'0',o:'',t:'0')),version:4))
```zig
const V = @Vector(4, u8);
export fn foo(x: *V) u8 {
return @as(u4, @bitCast(x.* == @as(V, @splat('='))));
}
```
LLVM equivalent: ([Godbolt](https://llvm.godbo.lt/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:llvm,selection:(endColumn:1,endLineNumber:9,positionColumn:1,positionLineNumber:9,selectionStartColumn:1,selectionStartLineNumber:9,startColumn:1,startLineNumber:9),source:'define+dso_local+zeroext+range(i8+0,+16)+i8+@foo(ptr+nocapture+nonnull+readonly+align+4+%250)+local_unnamed_addr+%7B%0AEntry:%0A++%251+%3D+load+%3C4+x+i8%3E,+ptr+%250,+align+4%0A++%252+%3D+icmp+eq+%3C4+x+i8%3E+%251,+splat+(i8+61)%0A++%253+%3D+bitcast+%3C4+x+i1%3E+%252+to+i4%0A++%254+%3D+zext+i4+%253+to+i8%0A++ret+i8+%254%0A%7D%0A'),l:'5',n:'0',o:'LLVM+IR+source+%231',t:'0')),k:41.40900195694716,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:llctrunk,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:llvm,libs:!(),options:'-O3+-mcpu%3Dznver5',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+llc+(trunk)+(Editor+%231)',t:'0')),k:58.590998043052835,l:'4',m:100,n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4))
```llvm
define dso_local zeroext range(i8 0, 16) i8 @foo(ptr nocapture nonnull readonly align 4 %0) local_unnamed_addr {
Entry:
%1 = load <4 x i8>, ptr %0, align 4
%2 = icmp eq <4 x i8> %1, splat (i8 61)
%3 = bitcast <4 x i1> %2 to i4
%4 = zext i4 %3 to i8
ret i8 %4
}
```

Compiles to:

```asm
.LCPI0_0:
.byte 61
.byte 61
.byte 61
.byte 61
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
foo:
vmovd xmm0, dword ptr [rdi]
vpcmpeqb k0, xmm0, xmmword ptr [rip + .LCPI0_0]
kmovd eax, k0
and al, 15
ret
```

In this case, the `and al, 15` is redundant because the `vmovd` is grabbing exactly 4 bytes and placing them into `xmm0`, and zeroing the upper 12 bytes. In our `vpcmpeqb`, we are comparing the upper 12 0's to 1's, which always results in 0's. Then we move the result to a general purpose register and zero out the upper 12 bits, even though they are already guaranteed to be 0.

This optimization is applicable to 2 and 4 byte scenarios. It also should impact the vector chosen to compare against. E.g., imagine another scenario where we inverted the conditional to be `!=` instead of `==`:

```zig
const V = @Vector(2, u8);
export fn foo(x: *V) u8 {
return @as(u2, @bitCast(x.* != @as(V, @splat('='))));
}
```

This currently compiles to:

```asm
.LCPI0_0:
.byte 61
.byte 61
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
.zero 1
foo:
movzx eax, word ptr [rdi]
vmovd xmm0, eax
vpcmpneqb k0, xmm0, xmmword ptr [rip + .LCPI0_0]
kmovd eax, k0
and al, 3
ret
```

Due to the constant that we are comparing against, the `and` is actually zeroing the upper bits because `0 != 1 => 1`. In that case, we could change the constant to have 0's in the upper 14 bytes, so that we do `0 != 0 => 0`, meaning no additional zeroing step is necessary.

This optimization is applicable to all ISA's.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.