[Draft] Accelerate Half with FP16 ISA#122649
[Draft] Accelerate Half with FP16 ISA#122649anthonycanino wants to merge 2 commits intodotnet:mainfrom
Half with FP16 ISA#122649Conversation
|
@tannergooding @jakobbotsch please take a look when you get a chance. |
3b8abaa to
f633726
Compare
|
@dotnet/intel @tannergooding may I get some high level feedback on the structure of the PR? |
src/coreclr/jit/compiler.cpp
Outdated
| if (!compOpportunisticallyDependsOn(InstructionSet_AVX10v1)) | ||
| { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
We need this last, not first, otherwise code gets tagged as benefiting from using AVX10v1 unnecessarily
| // kmov instructions reach this path with EA_8BYTE size, even on x86 | ||
| || IsKMOVInstruction(ins) |
There was a problem hiding this comment.
What's the reason for removing this part of the assert?
There was a problem hiding this comment.
Think that was an error, will fix.
src/coreclr/jit/emitxarch.cpp
Outdated
|
|
||
| case INS_vmovsh: | ||
| { | ||
| hasSideEffect = false; |
There was a problem hiding this comment.
Doesn't this have a side effect of clearing the upper-bits?
That is, it always does DEST[MAXVL:128] := 0
There was a problem hiding this comment.
You are correct, I will change.
src/coreclr/jit/emitxarch.cpp
Outdated
|
|
||
| #if defined(TARGET_AMD64) | ||
| case INS_movsxd: | ||
| case INS_vmovsh: |
There was a problem hiding this comment.
This isn't TARGET_AMD64 exclusive as vmovsh is listed with V/V for support, so is valid for both 64 and 32-bit mode.
| if (IsXMMReg(reg)) | ||
| { | ||
| return emitXMMregName(reg); | ||
| } |
There was a problem hiding this comment.
This shouldn't be TARGET_AMD64 exclusive either.
src/coreclr/jit/emitxarch.cpp
Outdated
| else if (code & 0xFF000000) | ||
| { | ||
| if (size == EA_2BYTE) | ||
| if (size == EA_2BYTE && (ins != INS_vmovsh && ins != INS_vaddsh)) |
There was a problem hiding this comment.
Can we just use && !IsSimdInstruction(ins)?
src/coreclr/jit/emitxarch.cpp
Outdated
| case INS_movapd: | ||
| case INS_movupd: | ||
| // todo-xarch-half: come back to fix | ||
| case INS_vmovsh: |
There was a problem hiding this comment.
Shouldn't this be grouped with vmovss and vmovsd? While we may not have exact numbers, I'd expect it to have identical perf/latency to those rather than the more general movaps and friends.
src/coreclr/jit/emitxarch.cpp
Outdated
| float insLatency = insLatencyInfos[ins]; | ||
|
|
||
| // todo-xarch-half: hacking an exit on the unhandled ins to make prototyping easier | ||
| if (ins == INS_vcvtss2sh || ins == INS_vcvtsh2ss || ins == INS_vaddsh || ins == INS_vsubsh || |
There was a problem hiding this comment.
I think we want to put most of these with the v*ss and v*sd equivalents prior to mergine this PR.
There was a problem hiding this comment.
Yes, and for the above, I will get the proper numbers before putting the PR in non-draft.
src/coreclr/jit/gentree.cpp
Outdated
| // todo-half: this is only to create zero constant half nodes for use in instrincis, anything | ||
| // else will not work |
There was a problem hiding this comment.
Not sure I understand this comment.
Presumably we just need a FloatingPointUtils::convertDoubleToHalf(...) method which returns a float16_t type (these were added in C++23, which is newer than our baseline, so we'd just typedef uint16_t float16_t; for the time being).
We then vecCon->gtSimdVal.f16[i] = cnsVal
src/coreclr/jit/gentree.h
Outdated
| { | ||
| if (arg->IsCnsFltOrDbl()) | ||
| { | ||
| simdVal.f16[argIdx] = static_cast<uint16_t>(arg->AsDblCon()->DconValue()); |
There was a problem hiding this comment.
This looks incorrect as it does a double->uint16_t cast, when we rather need double->float16_t
| } | ||
| } | ||
| else if (node->TypeIs(TYP_VOID)) | ||
| else if (node->TypeIs(TYP_VOID) || node->TypeIs(TYP_INT)) |
There was a problem hiding this comment.
What's the reason for this change?
There was a problem hiding this comment.
Think it was also a bug, I have removed.
| if (sizeBytes < getMinVectorByteLength()) | ||
| { | ||
| *pSimdBaseJitType = simdBaseType; | ||
| // The struct itself is accelerated, in this case, it is `Half`. |
There was a problem hiding this comment.
Add an assert(sizeBytes == 2) in case we add other sizes in the future?
| break; | ||
| } | ||
|
|
||
| case NI_System_Half_op_Increment: |
There was a problem hiding this comment.
Some of these, like Increment/Decrement, could be merged as well using lookupHalfIntrinsic
| if (srcSize == 2) | ||
| return INS_vmovsh; |
There was a problem hiding this comment.
General convention is to have braces, particularly if it is part of an if/else chain:
| if (srcSize == 2) | |
| return INS_vmovsh; | |
| if (srcSize == 2) | |
| { | |
| return INS_vmovsh; | |
| } |
src/coreclr/jit/lower.cpp
Outdated
| // if (node->TypeGet() == TYP_HALF) | ||
| //{ | ||
| // return false; | ||
| // } |
src/coreclr/jit/lsrabuild.cpp
Outdated
| case TYP_HALF: | ||
| #ifdef TARGET_X86 | ||
| useCandidates = RBM_FLOATRET; | ||
| #else | ||
| useCandidates = RBM_FLOATRET.GetFloatRegSet(); | ||
| #endif | ||
| break; |
There was a problem hiding this comment.
This looks to be identical to the TYP_FLOAT path and can be collapsed to share it:
| case TYP_HALF: | |
| #ifdef TARGET_X86 | |
| useCandidates = RBM_FLOATRET; | |
| #else | |
| useCandidates = RBM_FLOATRET.GetFloatRegSet(); | |
| #endif | |
| break; | |
| case TYP_HALF: |
| // We ONLY want the valid double register in the RBM_DOUBLERET mask. | ||
| #ifdef TARGET_AMD64 | ||
| useCandidates = (RBM_DOUBLERET & RBM_ALLDOUBLE).GetFloatRegSet(); | ||
| #else | ||
| useCandidates = (RBM_DOUBLERET & RBM_ALLDOUBLE).GetFloatRegSet(); | ||
| #endif // TARGET_AMD64 |
There was a problem hiding this comment.
not related to this PR, but these two paths are the same
|
@tannergooding I've made a number of changes for the PR. I think I will go ahead and add the |
|
I was incorrect about I think we are converging on a first PR now. I am looking into if any remaining operations can be covered with the FP16 ISA instructions. |
It can still be used to accelerate a lot of functionality for scalars (and is essentially the same support needed where we generate However, I think it's fine to wait for a subsequent PR to do that work (we do want to do it since that covers all |
3537f96 to
4235f30
Compare
|
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
af542eb to
9b0edca
Compare
There was a problem hiding this comment.
Pull request overview
Draft work to enable System.Half acceleration on xarch by introducing a dedicated TYP_HALF JIT type and mapping key Half operations/conversions to AVX10v1 FP16 scalar instructions, while updating VM calling-convention plumbing to match the new ABI behavior.
Changes:
- Mark
System.Halfand several operators/properties/conversions as[Intrinsic]to enable JIT recognition and expansion. - Extend CoreCLR VM + JIT ABI paths so
Halfcan be passed/returned in FP registers on xarch when AVX10v1 is available. - Add broad JIT support for
TYP_HALFacross SIMD/type normalization, codegen/emitter, HW intrinsics tables, and value numbering.
Reviewed changes
Copilot reviewed 44 out of 45 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Private.CoreLib/src/System/Half.cs | Marks Half members as intrinsics to enable JIT recognition/expansion. |
| src/coreclr/vm/reflectioninvocation.cpp | Adjusts reg type map so reflection invocation passes Half like float on xarch. |
| src/coreclr/vm/methodtable.h | Declares MethodTable::IsNativeHalfType() for xarch ABI checks. |
| src/coreclr/vm/invokeutil.cpp | Ensures Half args are copied/extended appropriately for call dispatch on xarch. |
| src/coreclr/vm/class.cpp | Implements IsNativeHalfType() gated by intrinsic-ness, layout, and AVX10v1. |
| src/coreclr/vm/callingconvention.h | Treats Half as FP-reg passed/returned in arg iterator/return flags on xarch. |
| src/coreclr/vm/callhelpers.cpp | Updates call descriptor reg map generation to treat Half like R4 when applicable. |
| src/coreclr/vm/amd64/profiler.cpp | Updates profiler arg/return handling to treat native Half as FP register data. |
| src/coreclr/jit/vartype.h | Adds helper varTypeIsAccelerated and updates float arg-reg usage for TYP_HALF. |
| src/coreclr/jit/valuenumfuncs.h | Expands xarch HW intrinsic VN macro shape to include a TYP_HALF slot. |
| src/coreclr/jit/valuenum.h | Adds VNForHalfCon and type conversion traits for TYP_HALF. |
| src/coreclr/jit/valuenum.cpp | Implements Half constant VN allocation and extends various VN helpers for TYP_HALF. |
| src/coreclr/jit/utils.h | Adds FloatingPointUtils::convertDoubleToFloat16 declaration. |
| src/coreclr/jit/utils.cpp | Implements software double -> float16 conversion used in vector constant materialization. |
| src/coreclr/jit/typelist.h | Defines TYP_HALF in the core type list with FP-reg classification. |
| src/coreclr/jit/simd.h | Adds float16_t lanes to SIMD value unions; introduces SIZE_UNKNOWN. |
| src/coreclr/jit/simd.cpp | Extends SIMD type recognition to normalize System.Half to TYP_HALF (xarch+AVX10v1). |
| src/coreclr/jit/scopeinfo.cpp | Extends variable location encoding to handle TYP_HALF in stack/register locs. |
| src/coreclr/jit/registeropswasm.cpp | Marks TYP_HALF as invalid for wasm value types. |
| src/coreclr/jit/regalloc.cpp | Allows register allocation candidacy for TYP_HALF. |
| src/coreclr/jit/namedintrinsiclist.h | Adds NI_System_Half_* named intrinsics and expands HW intrinsic macro shape. |
| src/coreclr/jit/morph.cpp | Updates struct/SIMD size checks to include accelerated types; excludes TYP_HALF from struct promotion. |
| src/coreclr/jit/lsraxarch.cpp | Extends LSR handling for new AVX10v1 FP16 FMA scalar intrinsic. |
| src/coreclr/jit/lsrabuild.cpp | Ensures return handling includes TYP_HALF in float return candidates. |
| src/coreclr/jit/lowerxarch.cpp | Adds lowering for AVX10v1 half-compare helpers and updates scalar base-type asserts. |
| src/coreclr/jit/lower.cpp | Treats TYP_HALF similarly to SIMD for some lowering paths; excludes from FP store retyping. |
| src/coreclr/jit/lclvars.cpp | Updates struct promotion helper to use accelerated-type sizing predicate. |
| src/coreclr/jit/instrsxarch.h | Updates instruction metadata/flags for FP16 scalar ops and defines AVX10v1 FMA range markers. |
| src/coreclr/jit/instr.cpp | Selects INS_vmovsh for 2-byte FP-reg load/store/copy (TYP_HALF) on xarch. |
| src/coreclr/jit/importercalls.cpp | Adds importer expansions for System.Half ops/conversions/properties to AVX10v1 scalar intrinsics; adjusts Half arg normalization. |
| src/coreclr/jit/importer.cpp | Extends struct normalization logic to treat intrinsic 2-byte Half as accelerated TYP_HALF. |
| src/coreclr/jit/hwintrinsiccodegenxarch.cpp | Enables AVX10v1 family codegen path and relaxes base-type asserts for TYP_HALF. |
| src/coreclr/jit/hwintrinsic.h | Expands instruction table storage on xarch to include a TYP_HALF instruction slot. |
| src/coreclr/jit/hwintrinsic.cpp | Updates HW intrinsic macro expansion and type-range checks to include TYP_HALF. |
| src/coreclr/jit/gentree.h | Allows TYP_HALF in some floating-constant assertions and adds vector-constant population for half lanes. |
| src/coreclr/jit/gentree.cpp | Extends zero constants, scalar create, to-scalar asserts, and embedded rounding handling to include TYP_HALF. |
| src/coreclr/jit/float16.h | Adds shared float16_t typedef for JIT components without relying on C++23. |
| src/coreclr/jit/emitxarch.cpp | Extends xarch emitter for AVX10v1 ranges, EVEX prefix maps, vmovsh, and perf scoring for FP16 instructions. |
| src/coreclr/jit/emit.h | Adds perf-score throughput constants used by new FP16 perf modeling. |
| src/coreclr/jit/compiler.h | Adds Half intrinsic helper declarations and renames SIMD-size predicate to “accelerated”. |
| src/coreclr/jit/compiler.cpp | Implements isNativeHalfStructType and uses it to map 2-byte structs to TYP_HALF when applicable. |
| src/coreclr/jit/codegenxarch.cpp | Treats TYP_HALF like floating for return registers and stack arg emission in key paths. |
| src/coreclr/jit/codegencommon.cpp | Updates struct-return assertions to allow TYP_HALF special casing. |
| src/coreclr/jit/abi.cpp | Maps 2-byte ABI passing segments to TYP_HALF. |
| Both simd.cpp, gentree.cpp, and utils.cpp need a definition of float16_t | ||
| but do not share a common header. | ||
|
|
||
| Defining here so as to not create accidental implict include dependencies. |
There was a problem hiding this comment.
Typo in comment: "implict" should be "implicit".
| Defining here so as to not create accidental implict include dependencies. | |
| Defining here so as to not create accidental implicit include dependencies. |
| case NI_System_Half_FusedMultiplyAdd: | ||
| { | ||
| #if defined(TARGET_XARCH) | ||
| if (compOpportunisticallyDependsOn(InstructionSet_AVX10v1)) | ||
| { | ||
| // We are constructing a chain of intrinsics similar to: | ||
| // return FMA.MultiplyAddScalar( | ||
| // Vector128.CreateScalarUnsafe(x), |
There was a problem hiding this comment.
New JIT intrinsic expansion for System.Half is introduced here (AVX10v1-based lowering), but the PR doesn't add corresponding JIT/HardwareIntrinsics tests. Please add targeted tests (correctness + codegen) under the existing AVX10v1 HW-intrinsics test projects so regressions/call-conv mismatches are caught.
| using System.Numerics; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Runtime.InteropServices; | ||
| using System.Runtime.Intrinsics; |
There was a problem hiding this comment.
using System.Runtime.Intrinsics; appears unused in this file (the [Intrinsic] attribute comes from System.Runtime.CompilerServices). Unused using directives typically trigger CS8019, which may fail the build if warnings are treated as errors.
Consider removing this using unless another Intrinsics type is going to be referenced from Half.cs.
| if (sigType == TYP_STRUCT) | ||
| { | ||
| var_types normSigType = impNormStructType(classHnd); | ||
| sigType == (normSigType == TYP_HALF) ? TYP_HALF : sigType; |
There was a problem hiding this comment.
sigType is not updated here because the ternary expression uses == instead of assignment. This means Half arguments will still be treated as TYP_STRUCT, which can cause incorrect implicit-coercion decisions and GDV incompatibility checks.
Change this to an assignment (and keep the conditional expression) so the signature type is actually normalized to TYP_HALF when appropriate.
| sigType == (normSigType == TYP_HALF) ? TYP_HALF : sigType; | |
| sigType = (normSigType == TYP_HALF) ? TYP_HALF : sigType; |
| // todo-half: we need to make this work properly | ||
| ValueNum ValueNumStore::VNForHalfCon(float cnsVal) | ||
| { | ||
| return VnForConst(cnsVal, GetFloatCnsMap(), TYP_HALF); | ||
| } |
There was a problem hiding this comment.
VNForHalfCon currently uses GetFloatCnsMap() as its lookup table. This will cause Half constants and float constants with the same bit pattern to share the same ValueNum, which breaks the invariant that a VN’s constant storage type matches its var_types (e.g., you can end up with a VN allocated in a TYP_FLOAT chunk but later used as TYP_HALF).
Introduce a dedicated Half constant map (keyed by the 16-bit Half payload or a distinct key type) so Half constants cannot collide with float constants.
| static constexpr uint64_t HALF_POSITIVE_INFINITY_BITS = 0x7C00; | ||
| static constexpr uint64_t HALF_NEGATIVE_INFINITY_BITS = 0xFC00; | ||
|
|
There was a problem hiding this comment.
HALF_POSITIVE_INFINITY_BITS/HALF_NEGATIVE_INFINITY_BITS are declared as uint64_t but are returned from helpers that return float16_t (uint16_t). This introduces implicit narrowing conversions that are likely to trigger warnings (and may fail the build under /WX).
Consider making these constants uint16_t (or explicitly casting at the return sites) so the return type matches without narrowing.
Draft PR for in-progress work to accelerate
System.Halfwith FP16 ISA.Current work done:
Add a
TYP_HALFto the .NET runtime, which is treated like aTYP_SIMDXX, but with some notable differences. Namely, aTYP_HALFis passed around via the xmm registers, and while it will pass avarTypeIsStructtest, it must be treated as a primitive in other places.Accelerate
System.Halfoperations with theTYP_HALFand some FP16 intrinsics. Not every possible function has been accelerated yet.For discussion:
I have currently worked around some checks to make
TYP_HALFbehave like a struct and a primitive. It's very ad-hoc at the moment.Much of the work to transform the named
System.Halfintrinsics into a sequence of intrinsic nodes is done inimportcall.cppand might want to be moved up into some of thegtNewSimdXXnodes.