Adrien Guinet via llvm-dev
2020-May-18 13:58 UTC
[llvm-dev] Use Galois field New Instructions (GFNI) to combine affine instructions
Hello everyone, On the last couple of days, I have been experimenting with teaching LLVM how to combine a set of affine instructions into an instruction that uses the GFNI [1] AVX512 extension, especially GF2P8AFFINEQB [2]. While the general idea seems to work, I have some questions about my current implementation (see below). FTR, I have named this transformation AffineCombineExpr (ACE). Let's first introduce the general idea, which is to transform code like this: static uint8_t rol2(uint8_t v) { return (v<<2)^(v>>6); } uint8_t func(uint8_t v) { v = rol2(v); v ^= 0xAA; return v; } into this: define zeroext i8 @func(i8 zeroext %v) local_unnamed_addr #0 { %0 = insertelement <16 x i8> undef, i8 %v, i64 0 %1 = call <16 x i8> @llvm.x86.vgf2p8affineqb.128(<16 x i8> %0, <16 x i8> bitcast (<2 x i64> <i64 4647715923615551520, i64 undef> to <16 x i8>), i8 -86) %2 = extractelement <16 x i8> %1, i64 0 ret i8 %2 } (if that's profitable, which might not be the case here, see below) Another more interesting example where we could see potential benefits is this one: https://github.com/aguinet/llvm-project/commit/9ed424cbac0fe3566f801167e2190fad5ad07507#diff-21dd247f3b8aa49860ae8122fe3ea698R22 This gets even more interesting with vectorized code, with an example here: * original C code: https://pastebin.com/4JjF7DPu * LLVM IR after clang -O2 -mgfni -mavx2: https://pastebin.com/Ti0Vm2gj [3] * LLVM IR after ACE (using opt -aggressive-instcombine -S): https://pastebin.com/2zFU7J6g (interesting things happened at line 67) If, like me, you don't have a GFNI-enabled CPU, you can use Intel SDE [4] to run the compiled code. The code of the pass is available here: https://github.com/aguinet/llvm-project/blob/feature/gfni_combine/llvm/lib/Transforms/AggressiveInstCombine/AffineExprCombine.cpp And there are test cases here: https://github.com/aguinet/llvm-project/tree/feature/gfni_combine/llvm/test/Transforms/AggressiveInstCombine (aec_*.ll) Questions ======== The high-level view of the algorithm is the following: a) gather, from a basic block, suites of instructions that process an 8-bit integer using affine instructions, and generate another 8-bit integer. b) compute the linear matrix and affine constant related to that set of instructions c) emit the GFNI instructions Even thought the current code showcases the idea, there are quite a few things I'm unhappy with and would like some advice: 1) about a): what we want is an analysis that can gather, from a given basic block, the largest DAG*s* of instructions based on a given predicate (in our case: is this an affine transformation?), that process an 8-bit value and output another 8-bit value. After looking at {Aggressive,}InstCombine, I hadn't find exactly what I wanted, so I've rewritten something from scratch to validate the overall idea. But is there some facility within LLVM I could reuse for this purpose? This feels like for instance the same kind of analyses that might be already done in ScheduleDAG (?). 2) profitability: according to [6], the latency of the GFNI instructions is 3 cycles. and in the general case insertelement and extractelement also maps to 3-cycle latency instructions [8] [9]. This makes the whole replacement a latency of 9 cycles (in the scalar case). Is there any example on how can I compare this 9 cycles latency against the set of instructions I am replacing against? What I do for now is really simple and I think far from reality (see https://github.com/aguinet/llvm-project/commit/9ed424cbac0fe3566f801167e2190fad5ad07507#diff-3a29c490bdd8d147d4044818c2da0509R115) 3) loop vectorization: related to 2), it seems that we could generate more efficient code if this instruction combining process would happen directly within the loop vectorization algorithm. Indeed, we could benefit from the cost model analysis that already exists there, and also tweak the loop unrolling factor to better hide this latency of 3 cycles.>From the documentation [7], it looks like this need to happen in the VPlan representation,but I've had a hard time figuring out where I should plug myself in. Is there any example that showcases instruction combining within this representation? 4) I inserted this transformation into AggressiveInstCombine, because it is indeed on paper a combination of instructions, and the analysis to make it work can be quite costly. That being said, it seems to be ran too early in the pipeline, and just running clang -03 -mgfni does not combine anything at all (I still got to investigate this). It might be linked to 1) and the fact that my analysis is very naïve, and assume some "clean" and optimized LLVM IR. Question is: where does that kind of transformation should happen in the current optimization pipeline? Current limitations/issues ========================= 1) compile-time performances: I haven't run benchmarks yet to see the compile-cost of this. Some efforts have been made on this preliminary version to drop as early as possible basic blocks that does not seem interesting [5], but that deserves more work 2) run-time performances: if someone has a GFNI-enabled CPU and can ran some benchmarks (for instance on the aec_vec.ll one), that could be very interesting :) 3) if we run the "vectorization" test above with avx512 (and thus 32-bytes vectors), we generate the LLVM IR here: https://pastebin.com/Rwn43N4x, but llc crashes with this message: https://pastebin.com/bbSZPFe5 . Am I doing something wrong or is there an actual issue in the X86 backend? 4) for now we limit ourselves to 8x8 functions, but there are chances we could extend this to bigger inputs/outputs (eg. 32x32 for CRC32-like functions would be nice) Thanks for any help! Regards, [1] https://en.wikipedia.org/wiki/AVX-512#GFNI [2] https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=gf2p&expand=2901 [3] if you wonder why not -mavx512f , see section about current issues below [4] https://software.intel.com/content/www/us/en/develop/articles/intel-software-development-emulator.html [5] https://github.com/aguinet/llvm-project/commit/9ed424cbac0fe3566f801167e2190fad5ad07507#diff-3a29c490bdd8d147d4044818c2da0509R308 [6] https://rizmediateknologi.blogspot.com/2019/08/the-ice-lake-benchmark-preview-inside_1.html [7] https://llvm.org/docs/Proposals/VectorizationPlan.html [8] https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=insert_epi8&expand=3145 [9] https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=extract_epi8&expand=3145,2432
Craig Topper via llvm-dev
2020-May-18 18:24 UTC
[llvm-dev] Use Galois field New Instructions (GFNI) to combine affine instructions
I can tell you that your avx512 issue is that v64i8 gfni instructions also require avx512bw to be enabled to make v64i8 a supported type. The C intrinsics handling in the front end know this rule. But since you generated your own intrinsics you bypassed that. On Mon, May 18, 2020 at 6:58 AM Adrien Guinet via llvm-dev < llvm-dev at lists.llvm.org> wrote:> Hello everyone, > > On the last couple of days, I have been experimenting with teaching LLVM > how to combine a > set of affine instructions into an instruction that uses the GFNI [1] > AVX512 extension, > especially GF2P8AFFINEQB [2]. While the general idea seems to work, I have > some questions > about my current implementation (see below). FTR, I have named this > transformation > AffineCombineExpr (ACE). > > Let's first introduce the general idea, which is to transform code like > this: > > static uint8_t rol2(uint8_t v) { > return (v<<2)^(v>>6); > } > uint8_t func(uint8_t v) { > v = rol2(v); > v ^= 0xAA; > return v; > } > > into this: > > define zeroext i8 @func(i8 zeroext %v) local_unnamed_addr #0 { > %0 = insertelement <16 x i8> undef, i8 %v, i64 0 > %1 = call <16 x i8> @llvm.x86.vgf2p8affineqb.128(<16 x i8> %0, <16 x i8> > bitcast (<2 x > i64> <i64 4647715923615551520, i64 undef> to <16 x i8>), i8 -86) > %2 = extractelement <16 x i8> %1, i64 0 > ret i8 %2 > } > > (if that's profitable, which might not be the case here, see below) > > Another more interesting example where we could see potential benefits is > this one: > > https://github.com/aguinet/llvm-project/commit/9ed424cbac0fe3566f801167e2190fad5ad07507#diff-21dd247f3b8aa49860ae8122fe3ea698R22 > > This gets even more interesting with vectorized code, with an example here: > > * original C code: https://pastebin.com/4JjF7DPu > * LLVM IR after clang -O2 -mgfni -mavx2: https://pastebin.com/Ti0Vm2gj [3] > * LLVM IR after ACE (using opt -aggressive-instcombine -S): > https://pastebin.com/2zFU7J6g > (interesting things happened at line 67) > > If, like me, you don't have a GFNI-enabled CPU, you can use Intel SDE [4] > to run the > compiled code. > > The code of the pass is available here: > > > https://github.com/aguinet/llvm-project/blob/feature/gfni_combine/llvm/lib/Transforms/AggressiveInstCombine/AffineExprCombine.cpp > > And there are test cases here: > > https://github.com/aguinet/llvm-project/tree/feature/gfni_combine/llvm/test/Transforms/AggressiveInstCombine > (aec_*.ll) > > Questions > ========> > The high-level view of the algorithm is the following: > > a) gather, from a basic block, suites of instructions that process an > 8-bit integer using > affine instructions, and generate another 8-bit integer. > b) compute the linear matrix and affine constant related to that set of > instructions > c) emit the GFNI instructions > > Even thought the current code showcases the idea, there are quite a few > things I'm unhappy > with and would like some advice: > > 1) about a): what we want is an analysis that can gather, from a given > basic block, the > largest DAG*s* of instructions based on a given predicate (in our case: is > this an affine > transformation?), that process an 8-bit value and output another 8-bit > value. > > After looking at {Aggressive,}InstCombine, I hadn't find exactly what I > wanted, so I've > rewritten something from scratch to validate the overall idea. But is > there some facility > within LLVM I could reuse for this purpose? This feels like for instance > the same kind of > analyses that might be already done in ScheduleDAG (?). > > 2) profitability: according to [6], the latency of the GFNI instructions > is 3 cycles. and > in the general case insertelement and extractelement also maps to 3-cycle > latency > instructions [8] [9]. This makes the whole replacement a latency of 9 > cycles (in the > scalar case). Is there any example on how can I compare this 9 cycles > latency against the > set of instructions I am replacing against? What I do for now is really > simple and I think > far from reality (see > > https://github.com/aguinet/llvm-project/commit/9ed424cbac0fe3566f801167e2190fad5ad07507#diff-3a29c490bdd8d147d4044818c2da0509R115 > ) > > 3) loop vectorization: related to 2), it seems that we could generate more > efficient code > if this instruction combining process would happen directly within the > loop vectorization > algorithm. Indeed, we could benefit from the cost model analysis that > already exists > there, and also tweak the loop unrolling factor to better hide this > latency of 3 cycles. > > From the documentation [7], it looks like this need to happen in the VPlan > representation, > but I've had a hard time figuring out where I should plug myself in. Is > there any example > that showcases instruction combining within this representation? > > 4) I inserted this transformation into AggressiveInstCombine, because it > is indeed on > paper a combination of instructions, and the analysis to make it work can > be quite costly. > That being said, it seems to be ran too early in the pipeline, and just > running clang -03 > -mgfni does not combine anything at all (I still got to investigate this). > It might be > linked to 1) and the fact that my analysis is very naïve, and assume some > "clean" and > optimized LLVM IR. > Question is: where does that kind of transformation should happen in the > current > optimization pipeline? > > Current limitations/issues > =========================> > 1) compile-time performances: I haven't run benchmarks yet to see the > compile-cost of > this. Some efforts have been made on this preliminary version to drop as > early as possible > basic blocks that does not seem interesting [5], but that deserves more > work > > 2) run-time performances: if someone has a GFNI-enabled CPU and can ran > some benchmarks > (for instance on the aec_vec.ll one), that could be very interesting :) > > 3) if we run the "vectorization" test above with avx512 (and thus 32-bytes > vectors), we > generate the LLVM IR here: https://pastebin.com/Rwn43N4x, but llc crashes > with this > message: https://pastebin.com/bbSZPFe5 . Am I doing something wrong or is > there an actual > issue in the X86 backend? > > 4) for now we limit ourselves to 8x8 functions, but there are chances we > could extend this > to bigger inputs/outputs (eg. 32x32 for CRC32-like functions would be nice) > > Thanks for any help! > > Regards, > > [1] https://en.wikipedia.org/wiki/AVX-512#GFNI > [2] > https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=gf2p&expand=2901 > [3] if you wonder why not -mavx512f , see section about current issues > below > [4] > > https://software.intel.com/content/www/us/en/develop/articles/intel-software-development-emulator.html > [5] > > https://github.com/aguinet/llvm-project/commit/9ed424cbac0fe3566f801167e2190fad5ad07507#diff-3a29c490bdd8d147d4044818c2da0509R308 > [6] > > https://rizmediateknologi.blogspot.com/2019/08/the-ice-lake-benchmark-preview-inside_1.html > [7] https://llvm.org/docs/Proposals/VectorizationPlan.html > [8] > https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=insert_epi8&expand=3145 > [9] > > https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=extract_epi8&expand=3145,2432 > _______________________________________________ > LLVM Developers mailing list > llvm-dev at lists.llvm.org > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev >-- ~Craig -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20200518/91f55bba/attachment.html>
Adrien Guinet via llvm-dev
2020-May-18 18:29 UTC
[llvm-dev] Use Galois field New Instructions (GFNI) to combine affine instructions
On 5/18/20 8:24 PM, Craig Topper wrote:> I can tell you that your avx512 issue is that v64i8 gfni instructions also > require avx512bw to be enabled to make v64i8 a supported type. The C > intrinsics handling in the front end know this rule. But since you > generated your own intrinsics you bypassed that.Indeed that's the issue... I was stick with what Intel announces here (https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=gf2p&expand=2907), but I guess I should have checked the C intrinsics. I will fix my code to verify the presence of avx512bw if I ever need v64i8. Thanks for the hint!