Skip to content

arm64: Add support for BFI and BFX instruction#123138

Open
jonathandavies-arm wants to merge 9 commits intodotnet:mainfrom
jonathandavies-arm:upstream/co/bitfield-manipulation
Open

arm64: Add support for BFI and BFX instruction#123138
jonathandavies-arm wants to merge 9 commits intodotnet:mainfrom
jonathandavies-arm:upstream/co/bitfield-manipulation

Conversation

@jonathandavies-arm
Copy link
Contributor

This patch adds support for the Arm BFI and BFX instructions. I've added a optimisation in lowering for bit packing and unpacking using each instruction respectively.

BFI

This is used when you pack 2 or more values into an integer. e.g.
return (a & 0xf) | ((b & 0x3) << 4);
This is the node pattern it is looking for

N009 (  9, 12) [000008] -----+-----                         *  OR        int    $103
N003 (  3,  4) [000002] -----+-----                         +--*  AND       int    $100
N001 (  1,  1) [000000] -----+-----                         |  +--*  LCL_VAR   int    V00 arg0         u:1 (last use) $80
N002 (  1,  2) [000001] -c---+-----                         |  \--*  CNS_INT   int    15 $43
N008 (  5,  7) [000007] -----+-----                         \--*  LSH       int    $102
N006 (  3,  4) [000005] -----+-----                            +--*  AND       int    $101
N004 (  1,  1) [000003] -----+-----                            |  +--*  LCL_VAR   int    V01 arg1         u:1 (last use) $81
N005 (  1,  2) [000004] -c---+-----                            |  \--*  CNS_INT   int    3 $44
N007 (  1,  2) [000006] -c---+-----                            \--*  CNS_INT   int    4 $45

and changes to

     (  9, 12) [000011] -----------                         \--*  BFI       int   
N003 (  3,  4) [000002] -----+-----                            +--*  AND       int    $100
N001 (  1,  1) [000000] -----+-----                            |  +--*  LCL_VAR   int    V00 arg0         u:1 (last use) $80
N002 (  1,  2) [000001] -c---+-----                            |  \--*  CNS_INT   int    15 $43
N004 (  1,  1) [000003] -----+-----                            \--*  LCL_VAR   int    V01 arg1         u:1 (last use) $81

BFX

When you extract a continuous range of bits from an integer. This is the inverse of above. e.g.
return (x >> 5) & 0x1F;

This is the node pattern it's looking for

N005 (  5,  7) [000004] -----+-----                         \--*  AND       int    $101
N003 (  3,  4) [000002] -----+-----                            +--*  RSH       int    $100
N001 (  1,  1) [000000] -----+-----                            |  +--*  LCL_VAR   int    V00 arg0         u:1 (last use) $80
N002 (  1,  2) [000001] -----+-----                            |  \--*  CNS_INT   int    6 $42
N004 (  1,  2) [000003] -----+-----                            \--*  CNS_INT   int    63 $43

and changes to

     (  5,  7) [000007] -----------                         \--*  BFX       int   
N001 (  1,  1) [000000] -----+-----                            \--*  LCL_VAR   int    V00 arg0         u:1 (last use) $80

@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jan 13, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 13, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@a74nh
Copy link
Contributor

a74nh commented Jan 26, 2026

Spmidiff errors

[05:14:28] ISSUE: <ASSERT> #9081 /Users/runner/work/1/s/src/coreclr/jit/codegenarm64.cpp (5916) - Assertion failed '(lsb + width) <= bitWidth' in 'Microsoft.AspNetCore.Connections.CorrelationIdGenerator+<>c:<GenerateId>b__3_0(System.Span`1[char],long):this' during 'Generate code' (IL size 267; hash 0xbd4b4083; Tier1)

https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-pull-123138-merge-73d1af1f3b444777be/linux-arm64-0/1/console.704b2183.log?helixlogtype=result

@jonathandavies-arm
Copy link
Contributor Author

Spmidiff errors

[05:14:28] ISSUE: <ASSERT> #9081 /Users/runner/work/1/s/src/coreclr/jit/codegenarm64.cpp (5916) - Assertion failed '(lsb + width) <= bitWidth' in 'Microsoft.AspNetCore.Connections.CorrelationIdGenerator+<>c:<GenerateId>b__3_0(System.Span`1[char],long):this' during 'Generate code' (IL size 267; hash 0xbd4b4083; Tier1)

https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-runtime-refs-pull-123138-merge-73d1af1f3b444777be/linux-arm64-0/1/console.704b2183.log?helixlogtype=result

Fixed

@jakobbotsch
Copy link
Member

/azp run runtime-coreclr superpmi-replay

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jakobbotsch
Copy link
Member

Looks like there are replay failures.

[12:10:56] ISSUE: #367597 D:\a_work\1\s\src\coreclr\jit\gentree.cpp (7036) - Assertion failed 'this->OperIsBinary()' in 'System.Text.Unicode.Utf8Utility:TranscodeToUtf8(ptr,int,ptr,int,byref,byref):int' during 'LSRA allocate' (IL size 1666; hash 0xb2a9635e; FullOpts)

Comment on lines +6902 to +6910
#ifdef TARGET_ARM64
case GT_BFX:
if (operand == this->AsOp()->gtOp1)
{
*pUse = &this->AsOp()->gtOp1;
return true;
}
return false;
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about GT_BFI?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Multiple places)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please double check with another node type like GT_AND that this is being added to all the general utilities.

{
assert(tree->OperIs(GT_BFI));

GenTreeBfm* bfm = tree->AsBfm();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just take GenTreeBfm*

{
GenTreeBfm* result = new (this, GT_BFI) GenTreeBfm(GT_BFI, type, base, src, offset, width);
result->gtFlags |= (base->gtFlags | src->gtFlags) & (GTF_ALL_EFFECT);
result->gtFlags &= ~GTF_SET_FLAGS;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result->gtFlags &= ~GTF_SET_FLAGS;

Unnecessary?

{
GenTreeBfm* result = new (this, GT_BFX) GenTreeBfm(GT_BFX, type, base, nullptr, offset, width);
result->gtFlags |= (base->gtFlags & GTF_ALL_EFFECT);
result->gtFlags &= ~GTF_SET_FLAGS;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result->gtFlags &= ~GTF_SET_FLAGS;

Comment on lines +224 to +226
GTNODE(BFI , GenTreeBfm ,0,0,GTK_BINOP|GTK_EXOP|DBK_NOTHIR) // Bitfield Insert.
GTNODE(BFIZ , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR) // Bitfield Insert in Zero.
GTNODE(BFX , GenTreeBfm ,0,0,GTK_UNOP|GTK_EXOP|DBK_NOTHIR) // Bitfield Extract.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume bfiz is subsumed by bfi? It might be nice to replace uses of one by the other and confirm that you see no regressions.

baseMaskKnown = false;
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it needs some interference checks (via IsInvariantInRange). The values being evaluated at the original node locations may change when you are evaluating them in a new node location if you don't check for interference.

Comment on lines +12226 to +12227
bfm->CopyCosts(tree);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bfm->CopyCosts(tree);

Don't think that's necessary.

Comment on lines +12232 to +12236
LIR::Use use;
if (BlockRange().TryGetUse(tree, &use))
{
use.ReplaceWith(bfm);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LIR::Use use;
if (BlockRange().TryGetUse(tree, &use))
{
use.ReplaceWith(bfm);
}
LIR::Use use;
if (BlockRange().TryGetUse(tree, &use))
{
use.ReplaceWith(bfm);
}
else
{
bfm->SetUnusedValue();
}

Value-producing nodes without users need to be set as unused. If tree was unused, your transformation is also leaving bfm unused.

return false;
}

BlockRange().Remove(tree);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop above seems to be able to match more nodes than are being removed here. I would expect that to hit asserts -- you remove some of the nodes and those nodes may have operands that you aren't marking as unused values.

Do you have test cases where the loop above matches a large number of nodes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants