{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/exp-simd-vectorization",
  "version": "1.0.0",
  "name": "exp-simd-vectorization",
  "description": "Optimizes hot-path scalar loops in .NET 8+ with cross-platform Vector128/Vector256/Vector512 SIMD intrinsics, or replaces manual math loops with single TensorPrimitives API calls. Covers byte-range validation, character counting, bulk bitwise ops, cross-type conversion, fused multi-array computations, and float/double math operations.",
  "system_prompt_fragment": "# SIMD Vectorization\n\n## Decision Gate\n1. **Check `Span<T>` and `MemoryExtensions` first.** If the operation can be expressed using built-in `Span<T>` methods (e.g., `Contains`, `IndexOf`, `CopyTo`, `SequenceEqual`) or `MemoryExtensions`, use them — no additional dependency is needed and the runtime already vectorizes many of these internally.\n2. **Check for TensorPrimitives next.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**, for example: `<PackageReference Include=\"System.Numerics.Tensors\" />` (or use the versioning approach already used by your solution). Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles.\n3. **Scalar loop over contiguous array/span** of `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `nint`, `nuint`, `float`, `double` (and `char` via reinterpretation as `ushort`)? → Implement with explicit `Vector128<T>` / `Vector256<T>` / `Vector512<T>` intrinsics using the patterns below.\n4. **No contiguous numeric arrays to process** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., \"State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state\"). This explanation is graded.\n\n## TensorPrimitives API Reference\nTensorPrimitives APIs are generic and work for any primitive type that satisfies the method's generic constraints — not just `float`/`double`. For example, `Sum` requires `IAdditionOperators<T,T,T>` + `IAdditiveIdentity<T,T>` and works for all primitive numeric types, while `CosineSimilarity` requires `IRootFunctions<T>` and only works for `float`/`double`. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible):\n\n### Reductions (span → scalar)\n| Operation | API |\n|-----------|-----|\n| Sum | `TensorPrimitives.Sum(span)` |\n| Sum of squares | `TensorPrimitives.SumOfSquares(span)` |\n| Sum of magnitudes (L1 norm) | `TensorPrimitives.SumOfMagnitudes(span)` |\n| L2 norm | `TensorPrimitives.Norm(span)` |\n| Product of all elements | `TensorPrimitives.Product(span)` |\n| Min value | `TensorPrimitives.Min(span)` |\n| Max value | `TensorPrimitives.Max(span)` |\n| Index of max | `TensorPrimitives.IndexOfMax(span)` |\n| Index of min | `TensorPrimitives.IndexOfMin(span)` |\n| Dot product | `TensorPrimitives.Dot(a, b)` |\n| Cosine similarity | `TensorPrimitives.CosineSimilarity(a, b)` |\n| Euclidean distance | `TensorPrimitives.Distance(a, b)` |\n\n### Element-wise transforms (span → span)\n| Operation | API |\n|-----------|-----|\n| Negate | `TensorPrimitives.Negate(src, dst)` |\n| Abs | `TensorPrimitives.Abs(src, dst)` |\n| Sqrt | `TensorPrimitives.Sqrt(src, dst)` |\n| Exp | `TensorPrimitives.Exp(src, dst)` |\n| Log | `TensorPrimitives.Log(src, dst)` |\n| Log2 | `TensorPrimitives.Log2(src, dst)` |\n| Tanh | `TensorPrimitives.Tanh(src, dst)` |\n| Sigmoid | `TensorPrimitives.Sigmoid(src, dst)` |\n| SoftMax | `TensorPrimitives.SoftMax(src, dst)` |\n| Sinh | `TensorPrimitives.Sinh(src, dst)` |\n| Cosh | `TensorPrimitives.Cosh(src, dst)` |\n| Round | `TensorPrimitives.Round(src, dst)` |\n| Floor | `TensorPrimitives.Floor(src, dst)` |\n| Ceiling | `TensorPrimitives.Ceiling(src, dst)` |\n| CopySign | `TensorPrimitives.CopySign(src, sign, dst)` |\n| Pow | `TensorPrimitives.Pow(bases, exponents, dst)` |\n\n### Two-span operations (a, b → dst)\n| Operation | API |\n|-----------|-----|\n| Add | `TensorPrimitives.Add(a, b, dst)` |\n| Subtract | `TensorPrimitives.Subtract(a, b, dst)` |\n| Multiply | `TensorPrimitives.Multiply(a, b, dst)` |\n| Divide | `TensorPrimitives.Divide(a, b, dst)` |\n| Element-wise Min | `TensorPrimitives.Min(a, b, dst)` |\n| Element-wise Max | `TensorPrimitives.Max(a, b, dst)` |\n\n### Three-span fused operations\n| Operation | API |\n|-----------|-----|\n| (x+y)*z | `TensorPrimitives.AddMultiply(x, y, z, dst)` |\n| x*y+z | `TensorPrimitives.MultiplyAdd(x, y, z, dst)` |\n| fma(x,y,z) | `TensorPrimitives.FusedMultiplyAdd(x, y, z, dst)` |\n\n> `AddMultiply` and `MultiplyAdd` are distinct — they optimize differently depending on whether the dependency chain flows from the addend or the multiplier. `FusedMultiplyAdd` is the IEEE 754 fused form of (x*y)+z with a single rounding step.\n\n## Manual SIMD with Vector128/Vector256/Vector512\n\nUse this when TensorPrimitives doesn't have a single API for the operation. This is required for byte-level operations, character class counting, range validation, bitwise bulk ops, cross-type conversions, and custom patterns.\n\n### Required imports\n```csharp\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Intrinsics;\n```\nPrefer cross-platform APIs (`System.Runtime.Intrinsics`). Only use platform-specific intrinsics (`System.Runtime.Intrinsics.X86`, `.Arm`) when there is a significant performance advantage that justifies the increased code complexity of maintaining separate code paths.\n\n### Three-tier dispatch pattern\nAlways include all three tiers. Use `if`/`else if` so that small inputs hit only one branch before reaching the scalar fallback — a fallthrough pattern (sequential `if`s) pessimizes the scalar case by requiring up to three not-taken branches that may mispredict. The `IsHardwareAccelerated` checks are JIT-time constants, so dead paths are eliminated at compile time:\n```csharp\nref var src = ref MemoryMarshal.GetReference(span);\nuint i = 0;\nuint length = (uint)span.Length;\n\nif (Vector512.IsHardwareAccelerated && Vector512<T>.IsSupported)\n{\n    uint vec512Count = (uint)Vector512<T>.Count;\n    while (i + vec512Count <= length)\n    {\n        var vec = Vector512.LoadUnsafe(ref src, i);\n        // ... process vec ...\n        i += vec512Count;\n    }\n}\nelse if (Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported)\n{\n    uint vec256Count = (uint)Vector256<T>.Count;\n    while (i + vec256Count <= length)\n    {\n        var vec = Vector256.LoadUnsafe(ref src, i);\n        // ... process vec ...\n        i += vec256Count;\n    }\n}\nelse if (Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported)\n{\n    uint vec128Count = (uint)Vector128<T>.Count;\n    while (i + vec128Count <= length)\n    {\n        var vec = Vector128.LoadUnsafe(ref src, i);\n        // ... process vec ...\n        i += vec128Count;\n    }\n}\n// Scalar fallback for remaining elements (and the only loop hit for small inputs)\nfor (; i < length; i++)\n{\n    // ... scalar processing ...\n}\n```\n\n### Core SIMD operations\n- **Load/Store:** `Vector128.LoadUnsafe(ref src, offset)` / `.StoreUnsafe(ref dst, offset)`\n- **Arithmetic:** `+`, `-`, `*`, `/` operators on vector types\n- **Multiply-add (approximate):** `Vector128.MultiplyAddEstimate(a, b, c)` — performs a multiply-add with implementation-defined approximation; not guaranteed to be a strict IEEE fused multiply-add. For precise fused semantics, use `Vector128.FusedMultiplyAdd(a, b, c)`.\n- **Comparison:** `Vector128.Equals`, `.LessThan`, `.GreaterThan` — returns mask vector\n- **Mask ops:** `Vector128.All(mask)`, `.Any(mask)`, `.None(mask)`, `.Count(mask)`, `.CountWhereAllBitsSet(mask)`\n- **Horizontal:** `Vector128.Sum(vec)` for reduction; `.Min(a,b)`, `.Max(a,b)` element-wise\n- **Broadcast:** `Vector128.Create(scalarValue)` — fill all lanes with one value\n- **Bitwise:** `&`, `|`, `^`, `~` operators; `Vector128.ShiftLeft`, `.ShiftRightLogical`\n- **Widening:** `Vector128.WidenLower(v)` / `.WidenUpper(v)` for byte→short, short→int\n- **Narrowing:** `Vector128.Narrow(lower, upper)` for int→short, short→byte\n- **Type convert:** `Vector128.ConvertToSingle(intVec)`, `.ConvertToInt32(floatVec)`\n- **Shuffle:** `Vector128.Shuffle(vec, indices)` — lookup table / permutation\n- **Conditional:** `Vector128.ConditionalSelect(mask, trueVec, falseVec)`\n\n### Pattern: Unsigned range check (byte-range validation)\nFor checking if all bytes are in range [lo, hi]:\n```csharp\nvar vLo = Vector128.Create((byte)lo);\nvar vRange = Vector128.Create((byte)(hi - lo));\n// (b - lo) > range means out-of-range (unsigned wraparound catches b < lo)\nvar shifted = Vector128.Subtract(vec, vLo);\nvar inRange = Vector128.LessThanOrEqual(shifted, vRange);\nif (!Vector128.All(inRange.AsByte())) return false; // for validation\n// or: count += Vector128.CountWhereAllBitsSet(inRange); // for counting\n```\n\n### Pattern: Nibble-lookup counting (character classes, popcount, etc.)\nFor counting bytes matching a sparse set of values (vowels, digits, punctuation, bit counts) — build two 16-byte lookup tables indexed by low/high nibble:\n```csharp\nvar lo_lut = Vector128.Create(/* 16 bytes: bit pattern for low nibble match */);\nvar hi_lut = Vector128.Create(/* 16 bytes: bit pattern for high nibble match */);\nvar nibbleMask = Vector128.Create((byte)0x0F);\n\nvar lo_nibble = vec & nibbleMask;\nvar hi_nibble = Vector128.ShiftRightLogical(vec.AsUInt16(), 4).AsByte() & nibbleMask;\nvar lo_match = Vector128.Shuffle(lo_lut, lo_nibble);\nvar hi_match = Vector128.Shuffle(hi_lut, hi_nibble);\nvar match = lo_match & hi_match;\ncount += Vector128.CountWhereAllBitsSet(~Vector128.Equals(match, Vector128<byte>.Zero));\n```\nThis same technique works for popcount (LUT = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}).\nFor simpler cases (single byte value, adjacent range), use `Equals` + `Count` or range check instead.\n\n### Pattern: Cross-type conversion (widening chains)\nWhen the source and destination types differ (e.g., byte→float for dequantization, short→byte for narrowing):\n```csharp\n// Widen: byte → short → int → float\nvar bytes = Vector128.LoadUnsafe(ref src, offset);\nvar (lo16, hi16) = Vector128.Widen(bytes);\nvar (lo32a, lo32b) = Vector128.Widen(lo16);\nvar f0 = Vector128.ConvertToSingle(lo32a.AsInt32());\n\n// Narrow: int → short → byte (with saturation via Min/Max clamping)\nvar clamped = Vector128.Min(Vector128.Max(vec, Vector128<short>.Zero), Vector128.Create((short)255));\nvar narrowed = Vector128.Narrow(clamped.AsUInt16(), nextVec.AsUInt16());\n```\n\n### Trailing elements\n- **Idempotent ops** (validation, search): overlap last vector — re-processing is safe\n- **Aggregations** (sum, count, min/max): scalar loop for remainder to avoid double-counting\n- **Store ops** (transform in-place): use `ConditionalSelect` to merge with last stored vector\n\n## Key Rules\n- Preserve original method signature — drop-in replacement\n- Keep scalar code as fallback — never delete it\n- Use `Vector128<T>` / `Vector256<T>` / `Vector512<T>` explicitly — never `Vector<T>`\n- Prefer portable `Vector128<T>`/`Vector256<T>`/`Vector512<T>` APIs over platform-specific intrinsics (`Avx2`, `Sse42`, `AdvSimd`, `Fma`) unless there is a significant performance advantage\n- Testing: use `dotnet run` (NOT `dotnet test`) — xunit.v3 is an in-process runner",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/exp-simd-vectorization"
  ],
  "tags": [
    "dotnet-experimental",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-experimental/skills/exp-simd-vectorization/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-experimental/skills/exp-simd-vectorization/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}