From 7b48b934702d9be0797d993dfa7a37576f9d7893 Mon Sep 17 00:00:00 2001 From: qt3r Date: Sat, 18 Jul 2026 03:49:16 -0700 Subject: [PATCH 1/2] pad non-byte-multiple vectors instead of skipping --- lib/Nybbler.cpp | 70 +++++++++++++++++++++++++++++++++----------- test/pad_diff.ll | 68 ++++++++++++++++++++++++++++++++++++++++++ test/pad_nonbyte.ll | 49 +++++++++++++++++++++++++++++++ test/skip_nonbyte.ll | 21 ------------- tools/diff_runner.py | 54 +++++++++++++++++++++++++++------- 5 files changed, 213 insertions(+), 49 deletions(-) create mode 100644 test/pad_diff.ll create mode 100644 test/pad_nonbyte.ll delete mode 100644 test/skip_nonbyte.ll diff --git a/lib/Nybbler.cpp b/lib/Nybbler.cpp index edc5d7b..4ade134 100644 --- a/lib/Nybbler.cpp +++ b/lib/Nybbler.cpp @@ -5,10 +5,13 @@ // // The carrier pattern (spec section 3) is the same for every operation: // 1. total = K * N. -// 2. If total % 8 != 0, skip (leave to the default legalizer; no padding yet). -// 3. bitcast each operand from to the carrier type . +// 2. If total % 8 != 0, widen each operand to the next byte boundary by +// appending zero lanes (K -> K' with K' * N % 8 == 0). +// 3. bitcast each (padded) operand from to the carrier type +// . // 4. emit the operation's body on the carrier operands. -// 5. bitcast the result back to , replaceAllUsesWith, erase original. +// 5. bitcast the result back to , narrow to the original K lanes +// (dropping the pad lanes), replaceAllUsesWith, erase original. // // Only step 4 differs between operations. So that pattern lives once in the // dispatch engine (tryLower), and each operation is a single *handler* function @@ -61,10 +64,11 @@ bool isNarrowFieldVector(Type *T) { namespace { /// Per-instruction context handed to a CarrierHandler. Carries the live builder -/// (insertion point already at the original instruction), the original field -/// vector type \c FieldTy (), the byte carrier type \c CarrierTy -/// (), the field width \c FieldBits (N), and the original opcode -/// (so handlers shared across several opcodes, like bitwise, can branch on it). +/// (insertion point already at the original instruction), the field vector type +/// \c FieldTy (, already padded to a byte multiple if the original +/// lane count wasn't one), the byte carrier type \c CarrierTy (), +/// the field width \c FieldBits (N), and the original opcode (so handlers +/// shared across several opcodes, like bitwise, can branch on it). struct CarrierOp { IRBuilder<> &B; FixedVectorType *FieldTy; @@ -275,22 +279,54 @@ static bool tryLower(Instruction &I) { if (!Handler) return false; - unsigned Total = FieldTy->getNumElements() * FieldBits; - if (Total % 8 != 0) - return false; - LLVM_DEBUG(dbgs() << "nybbler: lowering " << *BO << "\n"); IRBuilder<> B(BO); - auto *CarrierTy = FixedVectorType::get(B.getInt8Ty(), Total / 8); - Value *LHS = B.CreateBitCast(BO->getOperand(0), CarrierTy); - Value *RHS = B.CreateBitCast(BO->getOperand(1), CarrierTy); + // Pad non-byte-multiple vectors to the next byte boundary by appending zero + // lanes, run the handler on the padded carrier, and drop the pad lanes on + // the way back. Zero pad fields are inert for every handler: the field masks + // are per-byte splats so they already cover pad fields; add/sub confine + // carries/borrows within each field, and a zero field neither generates one + // nor lets one escape into the adjacent real field; shift steps operate on + // each field independently (the boundary masks confine every carrier shift), + // so a pad lane's value can never reach a real lane. Whatever junk the pad + // fields do compute is discarded by the narrowing shuffle. + unsigned K = FieldTy->getNumElements(); + unsigned FieldsPerByte = 8 / FieldBits; + unsigned PaddedK = (K + FieldsPerByte - 1) / FieldsPerByte * FieldsPerByte; + + Value *LHS = BO->getOperand(0); + Value *RHS = BO->getOperand(1); + auto *PaddedTy = FieldTy; + if (PaddedK != K) { + PaddedTy = FixedVectorType::get(FieldTy->getElementType(), PaddedK); + // Widen by shuffling with a zero vector: lanes >= K select lane K, i.e. + // lane 0 of the zero operand. + SmallVector WidenMask; + for (unsigned J = 0; J < PaddedK; ++J) + WidenMask.push_back(J < K ? J : K); + Value *Zero = Constant::getNullValue(FieldTy); + LHS = B.CreateShuffleVector(LHS, Zero, WidenMask, "pad.widen.lhs"); + RHS = B.CreateShuffleVector(RHS, Zero, WidenMask, "pad.widen.rhs"); + } - CarrierOp Op{B, FieldTy, CarrierTy, FieldBits, Opcode}; - Value *Carrier = Handler(Op, {LHS, RHS}); + auto *CarrierTy = + FixedVectorType::get(B.getInt8Ty(), PaddedK * FieldBits / 8); - Value *Result = B.CreateBitCast(Carrier, FieldTy); + Value *CarrierLHS = B.CreateBitCast(LHS, CarrierTy); + Value *CarrierRHS = B.CreateBitCast(RHS, CarrierTy); + + CarrierOp Op{B, PaddedTy, CarrierTy, FieldBits, Opcode}; + Value *Carrier = Handler(Op, {CarrierLHS, CarrierRHS}); + + Value *Result = B.CreateBitCast(Carrier, PaddedTy); + if (PaddedK != K) { + SmallVector NarrowMask; + for (unsigned J = 0; J < K; ++J) + NarrowMask.push_back(J); + Result = B.CreateShuffleVector(Result, NarrowMask, "pad.narrow"); + } BO->replaceAllUsesWith(Result); BO->eraseFromParent(); return true; diff --git a/test/pad_diff.ll b/test/pad_diff.ll new file mode 100644 index 0000000..d290fca --- /dev/null +++ b/test/pad_diff.ll @@ -0,0 +1,68 @@ +; Differential coverage for the zero-padding path: non-byte-multiple shapes +; (K*N % 8 != 0) across every lowered op must match LLVM's scalar reference. +; Representative shapes only -- the full padded matrix belongs to U3-C. +; +; Lives at the test root (not diff/) because coverage_check.py treats every +; diff/*.ll filename as an operation requiring per-width shape tests. +; RUN: %python "%diff_runner" --opt "%opt" --lli "%lli" --plugin "%nybbler" "%s" | %FileCheck "%s" +; CHECK: ALL PASS + +define <7 x i1> @and_i1_pad(<7 x i1> %a, <7 x i1> %b) { + %r = and <7 x i1> %a, %b + ret <7 x i1> %r +} + +define <3 x i2> @or_i2_pad(<3 x i2> %a, <3 x i2> %b) { + %r = or <3 x i2> %a, %b + ret <3 x i2> %r +} + +define <5 x i1> @xor_i1_pad(<5 x i1> %a, <5 x i1> %b) { + %r = xor <5 x i1> %a, %b + ret <5 x i1> %r +} + +define <13 x i1> @add_i1_pad(<13 x i1> %a, <13 x i1> %b) { + %r = add <13 x i1> %a, %b + ret <13 x i1> %r +} + +define <5 x i2> @add_i2_pad(<5 x i2> %a, <5 x i2> %b) { + %r = add <5 x i2> %a, %b + ret <5 x i2> %r +} + +define <3 x i4> @add_i4_pad(<3 x i4> %a, <3 x i4> %b) { + %r = add <3 x i4> %a, %b + ret <3 x i4> %r +} + +define <7 x i2> @sub_i2_pad(<7 x i2> %a, <7 x i2> %b) { + %r = sub <7 x i2> %a, %b + ret <7 x i2> %r +} + +define <3 x i4> @sub_i4_pad(<3 x i4> %a, <3 x i4> %b) { + %r = sub <3 x i4> %a, %b + ret <3 x i4> %r +} + +define <5 x i2> @shl_i2_pad(<5 x i2> %a, <5 x i2> %b) { + %r = shl <5 x i2> %a, %b + ret <5 x i2> %r +} + +define <3 x i4> @shl_i4_pad(<3 x i4> %a, <3 x i4> %b) { + %r = shl <3 x i4> %a, %b + ret <3 x i4> %r +} + +define <7 x i2> @lshr_i2_pad(<7 x i2> %a, <7 x i2> %b) { + %r = lshr <7 x i2> %a, %b + ret <7 x i2> %r +} + +define <3 x i4> @lshr_i4_pad(<3 x i4> %a, <3 x i4> %b) { + %r = lshr <3 x i4> %a, %b + ret <3 x i4> %r +} diff --git a/test/pad_nonbyte.ll b/test/pad_nonbyte.ll new file mode 100644 index 0000000..e805d77 --- /dev/null +++ b/test/pad_nonbyte.ll @@ -0,0 +1,49 @@ +; Vectors whose total bit width is not a multiple of 8 are widened to the next +; byte boundary with zero lanes, lowered on the padded carrier, and narrowed +; back to the original lane count. (Replaces skip_nonbyte.ll, which asserted +; the old behaviour of leaving these to the default legalizer.) +; RUN: %opt -load-pass-plugin "%nybbler" -passes=nybbler "%s" -S | %FileCheck "%s" + +; <3 x i4> = 12 bits -> pad to <4 x i4> = 16 bits, carrier <2 x i8>. +define <3 x i4> @and_i4_nonbyte(<3 x i4> %a, <3 x i4> %b) { +; CHECK-LABEL: @and_i4_nonbyte +; CHECK: shufflevector <3 x i4> %a, <3 x i4> zeroinitializer, <4 x i32> +; CHECK: shufflevector <3 x i4> %b, <3 x i4> zeroinitializer, <4 x i32> +; CHECK: bitcast <4 x i4> %{{.*}} to <2 x i8> +; CHECK: and <2 x i8> +; CHECK: bitcast <2 x i8> %{{.*}} to <4 x i4> +; CHECK: shufflevector <4 x i4> %{{.*}}, <3 x i32> +; CHECK-NOT: and <3 x i4> + %r = and <3 x i4> %a, %b + ret <3 x i4> %r +} + +; <5 x i1> = 5 bits -> pad to <8 x i1> = 8 bits, carrier <1 x i8>. +define <5 x i1> @xor_i1_nonbyte(<5 x i1> %a, <5 x i1> %b) { +; CHECK-LABEL: @xor_i1_nonbyte +; CHECK: shufflevector <5 x i1> %a, <5 x i1> zeroinitializer, <8 x i32> +; CHECK: shufflevector <5 x i1> %b, <5 x i1> zeroinitializer, <8 x i32> +; CHECK: bitcast <8 x i1> %{{.*}} to <1 x i8> +; CHECK: xor <1 x i8> +; CHECK: bitcast <1 x i8> %{{.*}} to <8 x i1> +; CHECK: shufflevector <8 x i1> %{{.*}}, <5 x i32> +; CHECK-NOT: xor <5 x i1> + %r = xor <5 x i1> %a, %b + ret <5 x i1> %r +} + +; A field-masked op through the same padding wrapper: the SWAR sub body runs +; unchanged on the padded <2 x i8> carrier. +; <5 x i2> = 10 bits -> pad to <8 x i2> = 16 bits, carrier <2 x i8>. +define <5 x i2> @sub_i2_nonbyte(<5 x i2> %a, <5 x i2> %b) { +; CHECK-LABEL: @sub_i2_nonbyte +; CHECK: shufflevector <5 x i2> %a, <5 x i2> zeroinitializer, <8 x i32> +; CHECK: shufflevector <5 x i2> %b, <5 x i2> zeroinitializer, <8 x i32> +; CHECK: bitcast <8 x i2> %{{.*}} to <2 x i8> +; CHECK: sub <2 x i8> +; CHECK: bitcast <2 x i8> %{{.*}} to <8 x i2> +; CHECK: shufflevector <8 x i2> %{{.*}}, <5 x i32> +; CHECK-NOT: sub <5 x i2> + %r = sub <5 x i2> %a, %b + ret <5 x i2> %r +} diff --git a/test/skip_nonbyte.ll b/test/skip_nonbyte.ll deleted file mode 100644 index ffba74b..0000000 --- a/test/skip_nonbyte.ll +++ /dev/null @@ -1,21 +0,0 @@ -; Vectors whose total bit width is not a multiple of 8 are left untouched in -; Slice 1 (no padding) -- the default legalizer handles them. -; RUN: %opt -load-pass-plugin "%nybbler" -passes=nybbler "%s" -S | %FileCheck "%s" - -; <3 x i4> = 12 bits, 12 % 8 != 0 -> skip. -define <3 x i4> @and_i4_nonbyte(<3 x i4> %a, <3 x i4> %b) { -; CHECK-LABEL: @and_i4_nonbyte -; CHECK: and <3 x i4> %a, %b -; CHECK-NOT: bitcast - %r = and <3 x i4> %a, %b - ret <3 x i4> %r -} - -; <5 x i1> = 5 bits, 5 % 8 != 0 -> skip. -define <5 x i1> @xor_i1_nonbyte(<5 x i1> %a, <5 x i1> %b) { -; CHECK-LABEL: @xor_i1_nonbyte -; CHECK: xor <5 x i1> %a, %b -; CHECK-NOT: bitcast - %r = xor <5 x i1> %a, %b - ret <5 x i1> %r -} diff --git a/tools/diff_runner.py b/tools/diff_runner.py index bd5c9ed..04feb92 100644 --- a/tools/diff_runner.py +++ b/tools/diff_runner.py @@ -15,6 +15,10 @@ here: the harness is fully op-agnostic, so adding a new operation is just a new kernel file -- no harness changes (see the test-suite ticket "Done when"). +Non-byte-multiple kernels (K*N % 8 != 0, exercising the pass' zero-padding +path) are supported: their operands are spelled as literal constants +and their results printed per-field, since no legal byte bitcast exists. + Reproducibility: the RNG seed defaults to 42 and can be overridden with the NYBBLER_DIFF_SEED environment variable (CI sets a random one); the seed actually used is printed so any CI failure can be reproduced exactly. @@ -58,8 +62,21 @@ def trials(nbytes, rng): [rng.randrange(256) for _ in range(nbytes)]) +def fields(bytez, K, N): + """Slice the little-endian packed `bytez` into K N-bit field values.""" + mask = (1 << N) - 1 + return [(bytez[(i * N) // 8] >> ((i * N) % 8)) & mask for i in range(K)] + + def vec_const(bytez, K, N): - """A `` operand built by bitcasting an `` constant.""" + """A `` operand built by bitcasting an `` constant. + + Non-byte-multiple vectors (K*N % 8 != 0) have no legal byte bitcast, so + their fields are spelled directly as an `` literal instead; the + trailing bits of the last trial byte simply go unused.""" + if (K * N) % 8 != 0: + elems = ", ".join("i%d %d" % (N, v) for v in fields(bytez, K, N)) + return "<%s>" % elems elems = ", ".join("i8 %d" % i8(v) for v in bytez) M = len(bytez) return "bitcast (<%d x i8> <%s> to <%d x i%d>)" % (M, elems, K, N) @@ -76,6 +93,7 @@ def build_module(src, kernels, batteries): "declare i32 @printf(i8*, ...)"] uid = 0 for (K, N, name), battery in zip(kernels, batteries): + byte_multiple = (K * N) % 8 == 0 M = K * N // 8 for (a, b) in battery: fn = ["define void @trial{0}() {{".format(uid)] @@ -83,15 +101,27 @@ def build_module(src, kernels, batteries): "<{K} x i{N}> {bv})".format( K=K, N=N, name=name, av=vec_const(a, K, N), bv=vec_const(b, K, N))) - fn.append(" %rb = bitcast <{K} x i{N}> %r to <{M} x i8>" - .format(K=K, N=N, M=M)) - for j in range(M): - fn.append(" %e{0} = extractelement <{M} x i8> %rb, i32 {0}" - .format(j, M=M)) - fn.append(" %z{0} = zext i8 %e{0} to i32".format(j)) - fn.append(" call i32 (i8*, ...) @printf(i8* getelementptr(" - "[5 x i8], [5 x i8]* @.hex, i32 0, i32 0), i32 %z{0})" - .format(j)) + if byte_multiple: + fn.append(" %rb = bitcast <{K} x i{N}> %r to <{M} x i8>" + .format(K=K, N=N, M=M)) + for j in range(M): + fn.append(" %e{0} = extractelement <{M} x i8> %rb, i32 {0}" + .format(j, M=M)) + fn.append(" %z{0} = zext i8 %e{0} to i32".format(j)) + fn.append(" call i32 (i8*, ...) @printf(i8* getelementptr(" + "[5 x i8], [5 x i8]* @.hex, i32 0, i32 0), i32 %z{0})" + .format(j)) + else: + # No legal byte bitcast: print each field instead. Both the + # reference and the candidate run this same module, so the + # formats always line up. + for j in range(K): + fn.append(" %e{0} = extractelement <{K} x i{N}> %r, i32 {0}" + .format(j, K=K, N=N)) + fn.append(" %z{0} = zext i{N} %e{0} to i32".format(j, N=N)) + fn.append(" call i32 (i8*, ...) @printf(i8* getelementptr(" + "[5 x i8], [5 x i8]* @.hex, i32 0, i32 0), i32 %z{0})" + .format(j)) fn.append(" call i32 (i8*, ...) @printf(i8* getelementptr(" "[2 x i8], [2 x i8]* @.nl, i32 0, i32 0))") fn.append(" ret void") @@ -127,7 +157,9 @@ def main(): seed = int(os.environ.get("NYBBLER_DIFF_SEED", "42")) print("seed=%d kernels=%s" % (seed, ",".join(k[2] for k in kernels))) rng = random.Random(seed) - batteries = [list(trials(K * N // 8, rng)) for (K, N, _) in kernels] + # ceil-divide so non-byte-multiple kernels get enough trial bytes to fill + # every field (the last byte's leftover bits go unused). + batteries = [list(trials((K * N + 7) // 8, rng)) for (K, N, _) in kernels] with tempfile.TemporaryDirectory() as d: raw = os.path.join(d, "m.ll") From 492f17ccc71d1d53c9c106c8862b591c03863824 Mon Sep 17 00:00:00 2001 From: qt3r Date: Sat, 18 Jul 2026 22:26:51 -0700 Subject: [PATCH 2/2] test: mask shift amounts in differential kernels Out-of-range shift amounts are poison; the scalar reference's output for them varies by target and LLVM build (0 locally, amt % N on current CI), so diff/shl and diff/lshr flapped whenever apt.llvm.org updated. Mask amounts into [0, N-1] in-kernel so the reference is only evaluated on defined inputs, and fix the lowerShift comment that claimed the reference guarantees over-shift-to-0. --- lib/Nybbler.cpp | 24 ++++++++++-------------- test/diff/lshr.ll | 14 +++++++++++--- test/diff/shl.ll | 14 +++++++++++--- test/pad_diff.ll | 15 +++++++++++---- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/lib/Nybbler.cpp b/lib/Nybbler.cpp index 4ade134..da1c4da 100644 --- a/lib/Nybbler.cpp +++ b/lib/Nybbler.cpp @@ -152,24 +152,20 @@ static Value *lowerSub(CarrierOp &Op, ArrayRef Ops) { /// SWAR shl/lshr for i4/i2 via per-field bit-serial conditional shifts. /// -/// Shift-amount model: per-field variable amounts, masked to [0, N-1] via -/// `& (N-1)`. This deliberately defines results for out-of-range amounts -/// rather than propagating LLVM IR poison semantics, matching the differential -/// harness' scalar reference behaviour. -/// -/// For each power-of-two step s = 1, 2, ..., N/2: -/// - extract whether each field's amount has bit s set +/// For each power-of-two step s = 1, 2, ..., 2^(N-1): +/// - extract whether each field's amount has that bit set /// - conditionally apply a carrier shift by s with a boundary mask /// - blend shifted / unshifted per field /// -/// Shift-amount model: each field's amount is its full N-bit value. Amounts -/// >= N shift every bit out, yielding 0 -- matching LLVM's scalar reference -/// (the differential harness' ground truth), which over-shifts to 0 rather -/// than masking the amount into range. +/// Shift-amount model: each field's amount is its full N-bit value; amounts +/// >= N shift every bit out, yielding 0. That out-of-range behaviour is our +/// choice, not something the scalar reference pins down: `shl/lshr iN x, amt` +/// with amt >= N is poison, and lli's observed result varies by target and +/// LLVM build (0 on some, amt % N on others -- it has flipped across CI +/// updates). The differential kernels therefore mask amounts into [0, N-1] +/// in-kernel, and only in-range amounts are verified against the reference. /// -/// i1 is special-cased to identity: its only in-range amount is 0, and the -/// scalar reference (lli interpreting `shl/lshr i1 x, 1`) returns x, so we -/// match that rather than clearing the field. +/// i1 is special-cased to identity: its only in-range amount is 0. static Value *lowerShift(CarrierOp &Op, ArrayRef Ops) { IRBuilder<> &B = Op.B; unsigned N = Op.FieldBits; diff --git a/test/diff/lshr.ll b/test/diff/lshr.ll index e34546f..ec6937b 100644 --- a/test/diff/lshr.ll +++ b/test/diff/lshr.ll @@ -1,19 +1,27 @@ ; Differential test: per-field lshr on the SWAR carrier must match ; LLVM's scalar reference for every input. Driven by tools/diff_runner.py. +; +; Shift amounts are masked into [0, N-1] inside each kernel: `lshr iN x, amt` +; with amt >= N is poison, so the scalar reference's output for it is +; target/LLVM-build dependent (it has flipped between 0 and amt%N across CI +; runs). Only in-range amounts have defined semantics to differentiate. ; RUN: %python "%diff_runner" --opt "%opt" --lli "%lli" --plugin "%nybbler" "%s" | %FileCheck "%s" ; CHECK: ALL PASS define <64 x i1> @lshr_i1(<64 x i1> %a, <64 x i1> %b) { - %r = lshr <64 x i1> %a, %b + %amt = and <64 x i1> %b, zeroinitializer + %r = lshr <64 x i1> %a, %amt ret <64 x i1> %r } define <8 x i2> @lshr_i2(<8 x i2> %a, <8 x i2> %b) { - %r = lshr <8 x i2> %a, %b + %amt = and <8 x i2> %b, splat (i2 1) + %r = lshr <8 x i2> %a, %amt ret <8 x i2> %r } define <32 x i4> @lshr_i4(<32 x i4> %a, <32 x i4> %b) { - %r = lshr <32 x i4> %a, %b + %amt = and <32 x i4> %b, splat (i4 3) + %r = lshr <32 x i4> %a, %amt ret <32 x i4> %r } diff --git a/test/diff/shl.ll b/test/diff/shl.ll index bc8a6e8..35d7e94 100644 --- a/test/diff/shl.ll +++ b/test/diff/shl.ll @@ -1,19 +1,27 @@ ; Differential test: per-field shl on the SWAR carrier must match ; LLVM's scalar reference for every input. Driven by tools/diff_runner.py. +; +; Shift amounts are masked into [0, N-1] inside each kernel: `shl iN x, amt` +; with amt >= N is poison, so the scalar reference's output for it is +; target/LLVM-build dependent (it has flipped between 0 and amt%N across CI +; runs). Only in-range amounts have defined semantics to differentiate. ; RUN: %python "%diff_runner" --opt "%opt" --lli "%lli" --plugin "%nybbler" "%s" | %FileCheck "%s" ; CHECK: ALL PASS define <64 x i1> @shl_i1(<64 x i1> %a, <64 x i1> %b) { - %r = shl <64 x i1> %a, %b + %amt = and <64 x i1> %b, zeroinitializer + %r = shl <64 x i1> %a, %amt ret <64 x i1> %r } define <8 x i2> @shl_i2(<8 x i2> %a, <8 x i2> %b) { - %r = shl <8 x i2> %a, %b + %amt = and <8 x i2> %b, splat (i2 1) + %r = shl <8 x i2> %a, %amt ret <8 x i2> %r } define <32 x i4> @shl_i4(<32 x i4> %a, <32 x i4> %b) { - %r = shl <32 x i4> %a, %b + %amt = and <32 x i4> %b, splat (i4 3) + %r = shl <32 x i4> %a, %amt ret <32 x i4> %r } diff --git a/test/pad_diff.ll b/test/pad_diff.ll index d290fca..789b8d6 100644 --- a/test/pad_diff.ll +++ b/test/pad_diff.ll @@ -47,22 +47,29 @@ define <3 x i4> @sub_i4_pad(<3 x i4> %a, <3 x i4> %b) { ret <3 x i4> %r } +; Shift amounts are masked into [0, N-1] in-kernel: out-of-range amounts are +; poison, so the scalar reference's output for them is target/build dependent +; (see diff/shl.ll). define <5 x i2> @shl_i2_pad(<5 x i2> %a, <5 x i2> %b) { - %r = shl <5 x i2> %a, %b + %amt = and <5 x i2> %b, splat (i2 1) + %r = shl <5 x i2> %a, %amt ret <5 x i2> %r } define <3 x i4> @shl_i4_pad(<3 x i4> %a, <3 x i4> %b) { - %r = shl <3 x i4> %a, %b + %amt = and <3 x i4> %b, splat (i4 3) + %r = shl <3 x i4> %a, %amt ret <3 x i4> %r } define <7 x i2> @lshr_i2_pad(<7 x i2> %a, <7 x i2> %b) { - %r = lshr <7 x i2> %a, %b + %amt = and <7 x i2> %b, splat (i2 1) + %r = lshr <7 x i2> %a, %amt ret <7 x i2> %r } define <3 x i4> @lshr_i4_pad(<3 x i4> %a, <3 x i4> %b) { - %r = lshr <3 x i4> %a, %b + %amt = and <3 x i4> %b, splat (i4 3) + %r = lshr <3 x i4> %a, %amt ret <3 x i4> %r }