In a release build with .NET 8 RC1 the compiler overwrites memory it shouldn't touch.
Input is a Span<byte> of pixels with layout RGBA, where every fourth byte is an alpha byte set to 255 (index 3, 7, 11, etc).
The code only overwrites the RGB bytes (indexes 0, 1, 2, 4, 5, 6, 8, etc), but the compiler overwrites 3, 7, 11, etc too (with 0).
This overwrite happens when a ref byte (3 bytes earlier) is overwritten with the first element of a Vector256
Below is this line in more context. I have tried to shorten the example code but the bug is very specific and is Vector256 related.
usingSystem;usingSystem.Numerics;usingSystem.Runtime.Intrinsics;usingSystem.Runtime.Intrinsics.X86;usingSystem.Runtime.CompilerServices;usingSystem.Runtime.InteropServices;usingSystem.Diagnostics;namespaceApplication{classProgram{staticvoidMain(string[]args){Console.WriteLine(Environment.Version);constintWidth=1920;constintHeight=1080;Span<byte>pixels=newbyte[Width*Height*sizeof(uint)];for(inti=3;i<pixels.Length;i+=4){pixels[i]=255;// fill alpha channel}pixels.Fill(255);vararea=Vector128.Create(0,0,384,384);StackBlur(pixels,Width,Height,Width*sizeof(uint),radius:100,area);for(inti=3;i<pixels.Length;i+=4){if(pixels[i]!=255){Console.WriteLine("FAILED");return;}}}staticushort[]stackblur_mul=newushort[255]{512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259};staticushort[]stackblur_shr=newushort[255]{9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24};//[MethodImpl(MethodImplOptions.NoOptimization)] // also 'fixes' the issuestaticvoidStackblurPass(Span<byte>src,///< input image dataintwidth,///< image widthintheight,///< image heightintrowStride,uintradius,///< blur intensity (should be in 2..254 range)boolvertical,Vector128<int>area){uintmul_sum=stackblur_mul[radius];intshr_sum=stackblur_shr[radius];varareaX=area[0];varareaY=area[1];varareaWidth=area[2];varareaHeight=area[3];intstrideX=4;intstrideY=rowStride;intareaMinX=areaX>=0?areaX:0;intareaMinY=areaY>=0?areaY:0;intmaxX=width-1;intmaxY=height-1;intareaMaxX=areaX+areaWidth-1;intareaMaxY=areaY+areaHeight-1;if(areaMaxX>maxX)areaMaxX=maxX;if(areaMaxY>maxY)areaMaxY=maxY;areaMaxX-=areaMinX;areaMaxY-=areaMinY;if(vertical){// transpose (swap X and Y)(maxX,maxY)=(maxY,maxX);(strideX,strideY)=(strideY,strideX);(areaMinX,areaMinY)=(areaMinY,areaMinX);(areaMaxX,areaMaxY)=(areaMaxY,areaMaxX);}uintinitialSum=(uint)SumNumbers((int)radius+1);refvarsourcePtr=refsrc[areaMinX*strideX+areaMinY*strideY];if(Avx2.IsSupported&&Vector256.IsHardwareAccelerated){varidx0=Vector256.Create(0,1,2,1*strideY,1*strideY+1,1*strideY+2,2*strideY,2*strideY+1);varidx1=Vector256.Create(2*strideY+2,3*strideY,3*strideY+1,3*strideY+2,4*strideY,4*strideY+1,4*strideY+2,5*strideY);varidx2=Vector256.Create(5*strideY+1,5*strideY+2,6*strideY,6*strideY+1,6*strideY+2,7*strideY,7*strideY+1,7*strideY+2);varlowerByte=Vector256.Create(0xFFU);[MethodImpl(MethodImplOptions.AggressiveInlining)]unsafevoidFillVectorsFromPixels(refbytesrc,refVector256<uint>data0,refVector256<uint>data1,refVector256<uint>data2){varsrc_ptr=(uint*)Unsafe.AsPointer(refsrc);data0=Avx2.GatherVector256(src_ptr,idx0,1);data1=Avx2.GatherVector256(src_ptr,idx1,1);data2=Avx2.GatherVector256(src_ptr,idx2,1);data0=Avx2.And(data0,lowerByte);data1=Avx2.And(data1,lowerByte);data2=Avx2.And(data2,lowerByte);}Span<Vector256<uint>>stack0=stackallocVector256<uint>[(int)radius*2+1];Span<Vector256<uint>>stack1=stackallocVector256<uint>[(int)radius*2+1];Span<Vector256<uint>>stack2=stackallocVector256<uint>[(int)radius*2+1];for(inty=0;y<=areaMaxY;y+=8){varsum_in0=Vector256<uint>.Zero;varsum_in1=Vector256<uint>.Zero;varsum_in2=Vector256<uint>.Zero;vardata0=Vector256<uint>.Zero;vardata1=Vector256<uint>.Zero;vardata2=Vector256<uint>.Zero;refvarsrc_ptr=refUnsafe.Add(refsourcePtr,y*strideY);// start of line (0,y)FillVectorsFromPixels(refsrc_ptr,refdata0,refdata1,refdata2);varsum0=Vector256.Multiply(data0,initialSum);varsum1=Vector256.Multiply(data1,initialSum);varsum2=Vector256.Multiply(data2,initialSum);varsum_out0=Vector256.Multiply(data0,radius+1);varsum_out1=Vector256.Multiply(data1,radius+1);varsum_out2=Vector256.Multiply(data2,radius+1);foreach(refvarstack_ptrinstack0.Slice(0,(int)radius+1)){stack_ptr=data0;}foreach(refvarstack_ptrinstack1.Slice(0,(int)radius+1)){stack_ptr=data1;}foreach(refvarstack_ptrinstack2.Slice(0,(int)radius+1)){stack_ptr=data2;}for(inti=1;i<=radius;i++){if(i<areaMaxX){src_ptr=refUnsafe.Add(refsrc_ptr,strideX);FillVectorsFromPixels(refsrc_ptr,refdata0,refdata1,refdata2);}refvarstack_ptr=refstack0[(int)(i+radius)];stack_ptr=data0;stack_ptr=refstack1[(int)(i+radius)];stack_ptr=data1;stack_ptr=refstack2[(int)(i+radius)];stack_ptr=data2;sum0+=data0*(uint)(radius+1-i);sum1+=data1*(uint)(radius+1-i);sum2+=data2*(uint)(radius+1-i);sum_in0+=data0;sum_in1+=data1;sum_in2+=data2;}intsp=(int)radius;intxp=(int)radius;if(xp>areaMaxX)xp=areaMaxX;src_ptr=refUnsafe.Add(refsourcePtr,xp*strideX+y*strideY);refvardst_ptr=refUnsafe.Add(refsourcePtr,y*strideY);for(intx=0;x<=areaMaxX;x++){varblurredPixel0=Vector256.ShiftRightLogical(Vector256.Multiply(sum0,mul_sum),shr_sum);varblurredPixel1=Vector256.ShiftRightLogical(Vector256.Multiply(sum1,mul_sum),shr_sum);varblurredPixel2=Vector256.ShiftRightLogical(Vector256.Multiply(sum2,mul_sum),shr_sum);
#if NET8_0_OR_GREATERConsole.WriteLine(Unsafe.Add(refdst_ptr,3).ToString("X"));// should print FF
#endif
Unsafe.Add(refdst_ptr,0)=(byte)blurredPixel0[0];// bug, should only write a single byte
#if NET8_0_OR_GREATERConsole.WriteLine(Unsafe.Add(refdst_ptr,3).ToString("X"));// should print FF, not 00
#endif
Unsafe.Add(refdst_ptr,1)=(byte)blurredPixel0[1];Unsafe.Add(refdst_ptr,2)=(byte)blurredPixel0[2];Unsafe.Add(refdst_ptr,0+strideY)=(byte)blurredPixel0[3];Unsafe.Add(refdst_ptr,1+strideY)=(byte)blurredPixel0[4];Unsafe.Add(refdst_ptr,2+strideY)=(byte)blurredPixel0[5];Unsafe.Add(refdst_ptr,0+strideY*2)=(byte)blurredPixel0[6];Unsafe.Add(refdst_ptr,1+strideY*2)=(byte)blurredPixel0[7];Unsafe.Add(refdst_ptr,2+strideY*2)=(byte)blurredPixel1[0];Unsafe.Add(refdst_ptr,0+strideY*3)=(byte)blurredPixel1[1];Unsafe.Add(refdst_ptr,1+strideY*3)=(byte)blurredPixel1[2];Unsafe.Add(refdst_ptr,2+strideY*3)=(byte)blurredPixel1[3];Unsafe.Add(refdst_ptr,0+strideY*4)=(byte)blurredPixel1[4];Unsafe.Add(refdst_ptr,1+strideY*4)=(byte)blurredPixel1[5];Unsafe.Add(refdst_ptr,2+strideY*4)=(byte)blurredPixel1[6];Unsafe.Add(refdst_ptr,0+strideY*5)=(byte)blurredPixel1[7];Unsafe.Add(refdst_ptr,1+strideY*5)=(byte)blurredPixel2[0];Unsafe.Add(refdst_ptr,2+strideY*5)=(byte)blurredPixel2[1];Unsafe.Add(refdst_ptr,0+strideY*6)=(byte)blurredPixel2[2];Unsafe.Add(refdst_ptr,1+strideY*6)=(byte)blurredPixel2[3];Unsafe.Add(refdst_ptr,2+strideY*6)=(byte)blurredPixel2[4];Unsafe.Add(refdst_ptr,0+strideY*7)=(byte)blurredPixel2[5];Unsafe.Add(refdst_ptr,1+strideY*7)=(byte)blurredPixel2[6];Unsafe.Add(refdst_ptr,2+strideY*7)=(byte)blurredPixel2[7];dst_ptr=refUnsafe.Add(refdst_ptr,strideX);sum0-=sum_out0;sum1-=sum_out1;sum2-=sum_out2;intstack_start=sp+(int)radius+1;if(stack_start>=stack0.Length)stack_start-=stack0.Length;refvarstack_ptr0=refstack0[stack_start];sum_out0-=stack_ptr0;refvarstack_ptr1=refstack1[stack_start];sum_out1-=stack_ptr1;refvarstack_ptr2=refstack2[stack_start];sum_out2-=stack_ptr2;if(xp<areaMaxX){src_ptr=refUnsafe.Add(refsrc_ptr,strideX);FillVectorsFromPixels(refsrc_ptr,refdata0,refdata1,refdata2);++xp;}stack_ptr0=data0;sum_in0+=data0;sum0+=sum_in0;stack_ptr1=data1;sum_in1+=data1;sum1+=sum_in1;stack_ptr2=data2;sum_in2+=data2;sum2+=sum_in2;++sp;if(sp>=stack0.Length)sp=0;stack_ptr0=refstack0[sp];sum_out0+=stack_ptr0;sum_in0-=stack_ptr0;stack_ptr1=refstack1[sp];sum_out1+=stack_ptr1;sum_in1-=stack_ptr1;stack_ptr2=refstack2[sp];sum_out2+=stack_ptr2;sum_in2-=stack_ptr2;}}}else{[MethodImpl(MethodImplOptions.AggressiveInlining)]staticvoidFillVectorFromPixel(refVector128<uint>data,refbytesrc_ptr){data=Vector128.Create((uint)src_ptr,(uint)Unsafe.Add(refsrc_ptr,1),(uint)Unsafe.Add(refsrc_ptr,2),(uint)Unsafe.Add(refsrc_ptr,3));}Span<Vector128<uint>>stack=stackallocVector128<uint>[(int)radius*2+1];for(inty=0;y<=areaMaxY;y++){varsum_in=Vector128<uint>.Zero;refbytesrc_ptr=refUnsafe.Add(refsourcePtr,y*strideY);// start of line (0,y)Vector128<uint>data=Vector128<uint>.Zero;FillVectorFromPixel(refdata,refsrc_ptr);varsum=Vector128.Multiply(data,initialSum);varsum_out=Vector128.Multiply(data,radius+1);foreach(refvarstack_ptrinstack.Slice(0,(int)radius+1)){stack_ptr=data;}for(inti=1;i<=radius;i++){if(i<areaMaxX){src_ptr=refUnsafe.Add(refsrc_ptr,strideX);FillVectorFromPixel(refdata,refsrc_ptr);}refvarstack_ptr=refstack[(int)(i+radius)];stack_ptr=data;sum+=data*(uint)(radius+1-i);sum_in+=data;}intsp=(int)radius;intxp=(int)radius;if(xp>areaMaxX)xp=areaMaxX;src_ptr=refUnsafe.Add(refsourcePtr,xp*strideX+y*strideY);// img.pix_ptr(xp, y);refbytedst_ptr=refUnsafe.Add(refsourcePtr,y*strideY);// img.pix_ptr(0, y);for(intx=0;x<=areaMaxX;x++){varblurredPixel=Vector128.ShiftRightLogical(Vector128.Multiply(sum,mul_sum),shr_sum);dst_ptr=(byte)blurredPixel[0];Unsafe.Add(refdst_ptr,1)=(byte)blurredPixel[1];Unsafe.Add(refdst_ptr,2)=(byte)blurredPixel[2];Unsafe.Add(refdst_ptr,3)=(byte)blurredPixel[3];dst_ptr=refUnsafe.Add(refdst_ptr,strideX);sum-=sum_out;intstack_start=sp+(int)radius+1;if(stack_start>=stack.Length)stack_start-=stack.Length;refvarstack_ptr=refstack[stack_start];sum_out-=stack_ptr;if(xp<areaMaxX){src_ptr=refUnsafe.Add(refsrc_ptr,strideX);FillVectorFromPixel(refdata,refsrc_ptr);++xp;}stack_ptr=data;sum_in+=data;sum+=sum_in;++sp;if(sp>=stack.Length)sp=0;stack_ptr=refstack[sp];sum_out+=stack_ptr;sum_in-=stack_ptr;}}}}staticintSumNumbers(intn){returnn*(n+1)/2;// fast way of calculating range (0..n).Sum()}/// Stackblur algorithm by Mario Klingemann/// Details here:/// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html/// C++ implemenation base from:/// https://gist.github.com/benjamin9999/3809142/// http://www.antigrain.com/__code/include/agg_blur.h.html/// This version works only with RGBA colorstaticvoidStackBlur(Span<byte>src,///< input image dataintw,///< image widthinth,///< image heightintrowStride,uintradius///< blur intensity (should be in 2..254 range)){vararea=Vector128.Create(0,0,w,h);StackBlur(src,w,h,rowStride,radius,area);}staticvoidStackBlur(Span<byte>src,///< input image dataintw,///< image widthinth,///< image heightintrowStride,uintradius,///< blur intensity (should be in 2..254 range)Vector128<int>area){if(radius>254)return;if(radius<1)return;StackblurPass(src,w,h,rowStride,radius,vertical:false,area);StackblurPass(src,w,h,rowStride,radius,vertical:true,area);}}}
Description
In a release build with .NET 8 RC1 the compiler overwrites memory it shouldn't touch.
Input is a Span<byte> of pixels with layout RGBA, where every fourth byte is an alpha byte set to 255 (index 3, 7, 11, etc).
The code only overwrites the RGB bytes (indexes 0, 1, 2, 4, 5, 6, 8, etc), but the compiler overwrites 3, 7, 11, etc too (with 0).
This overwrite happens when a ref byte (3 bytes earlier) is overwritten with the first element of a Vector256
Below is this line in more context. I have tried to shorten the example code but the bug is very specific and is Vector256 related.
Configuration
Regression?
Yes, compared to .NET 7.0
Other information
The following configurations work fine:
dotnet run StackBlur --framework net7.0 --configuration Releasedotnet run StackBlur --framework net7.0 --configuration Debugdotnet run StackBlur --framework net8.0 --configuration DebugTo reproduce the error (from PowerShell):
$env:DOTNET_JitDisasm = 'StackblurPass'dotnet run StackBlur --framework net8.0 --configuration ReleaseHere the project code
StackBlur.csproj:And the source code
Program.cs:The program is rather long but scroll down to where it is not indented to find the issue.