Eliminate redundant vector movemask `zext`s
- 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
Assessment
This issue has not been assessed yet.