-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Consolidate implementation of ExtractBit #22225
Conversation
- Change return type to byte
|
OK @tannergooding per your request I have shaken the tree: removed all unused code, and only left the consolidated methods. We can perf-tune those later on. Let me know if that's suitable, I can make any changes needed. |
|
CC. @GrabYourPitchforks, who I believe was interested in some of this work. |
|
This is adding a bunch of dead methods that are not used from anywhere else in CoreLib (different from previous PRs). We should have the API review discussion first whether these methods are worth adding to public surface. The |
Makes sense, we can wait for api review. |
I think they are likely useful in the scenario where they can be backed by intrinsics. Whether or not |
I do not think it does today. https://github.com/dotnet/coreclr/issues/11471#issue-227453324 tracks it (look for |
If they are useful, they should be handled as peephole optimization. All code out there is using the idiomatic pattern today, and we should not be asking all of it to be rewritten to do something else to run well. |
|
OK, closing this PR pending API review |
I was suggesting that it might be beneficial to both recognize the code pattern and have a standard API for it. Much like |
I agree with that: the callsites I updated in the previous PRs are more self documenting, especially where operator-heavy code is present, like inline
This also opens the door to analysis - we can then directly measure how many callers use (say) |
The difference is that the idiomatic pattern for Rotate is not a simple code. The idiomatic pattern for bit test is compact code (ie the method won't save you much typing). |
Right, but the point of the method isn't to save on typing. It is to provide clarity in the code. |
|
It is matter of personal preference. I do not think I have ever created/used There are ~500 occurrences of |
|
The JIT generates BT for a while now. It could generate BTS & co. from code like As such, it's probably more useful to make the JIT use SHLX/SHRX/SARX instead of the traditional shift instructions when available, these are efficient on both AMD and Intel and cover more scenarios. BTS & co. might still be useful for its "test and set" capability as that saves an additional instruction. But that's more difficult to recognize in the JIT. Perhaps an intrinsic would be useful, though still not extraordinarily valuable. |
|
Here's an example where I personally believe the utility methods make the code more self-documenting and simpler to digest. In public bool this[int perEventSourceSessionId]
{
get
{
return BitOps.ExtractBit(m_mask, perEventSourceSessionId);
//return (m_mask & (1 << perEventSourceSessionId)) != 0;
}
set
{
if (value)
m_mask = BitOps.InsertBit(m_mask, perEventSourceSessionId);
//m_mask |= ((uint)1 << perEventSourceSessionId);
else
m_mask = BitOps.ClearBit(m_mask, perEventSourceSessionId);
//m_mask &= ~((uint)1 << perEventSourceSessionId);
// Or, even better, a one-liner
BitOps.WriteBit(ref m_mask, perEventSourceSessionId, value);
}
}In public bool this[int index1, int index2]
{
get
{
Debug.Assert(index1 < _bits.Length && index2 < _bits.Length, "Index out of range.");
return (_bits[index1] & ((ulong)1 << index2)) != 0;
}
set
{
Debug.Assert(index1 < _bits.Length && index2 < _bits.Length, "Index out of range.");
if (value == true)
{
_bits[index1] |= (ulong)1 << index2;
}
else
{
_bits[index1] &= ~((ulong)1 << index2);
}
}
} |
|
[Edited]
It's hard to figure out a narrow search phrase for all these methods, but here's some I came across: In private static bool IsWordSeparator(UnicodeCategory category)
{
return (c_wordSeparatorMask & (1 << (int) category)) != 0;
}In private bool InAttributeActiveState
{
get
{
#if DEBUG
Debug.Assert(0 == (AttributeActiveStates & (1 << (int)State.Initial)));
Debug.Assert(0 != (AttributeActiveStates & (1 << (int)State.Interactive)));
// elided 9 more, which ~might be easier understood as:
Debug.Assert(BitOps.ExtractBit(AttributeActiveStates, (int)State.Interactive));
#endif
return 0 != (AttributeActiveStates & (1 << (int)_state));
}
}In private static bool IsTextualNode(XmlNodeType nodeType)
{
return 0 != (s_isTextualNodeBitmap & (1 << (int)nodeType));
}In public bool MatchesPattern(OptimizerPatternName pattern)
{
Debug.Assert(Enum.IsDefined(typeof(OptimizerPatternName), pattern));
return (_patterns & (1 << (int)pattern)) != 0;
}In public override bool IsFiltered(XPathNavigator navigator)
{
return ((1 << (int)navigator.NodeType) & _mask) == 0;
}In private static bool CanPrimitiveWiden(Type source, Type target)
{
Primitives widerCodes = s_primitiveConversions[(int)(Type.GetTypeCode(source))];
Primitives targetCode = (Primitives)(1 << (int)(Type.GetTypeCode(target)));
return 0 != (widerCodes & targetCode);
}In private void SetFlagToAddListSeparatorBeforeNextItem()
{
// Surprising this is not manually inlined, but it supports the point about helpers
_currentDepth |= 1 << 31;
}2 more in Roslyn |
What's interesting about these examples is that there are no two helpers that are the same. Everybody customizes them for their usage pattern. Here are the variations from these 4 examples. |
|
[Edited - also edited previous message, added more inline examples]
However, it does support there being a use-case for helpers - devs don't always want to inline the algebra. For reference, here's the original PR signatures: // Overloads exist for byte|uint|int
public static bool ExtractBit(byte value, int bitOffset); // Used in SessionMask example above
public static bool WriteBit(ref byte value, int bitOffset, bool on); // Used in SessionMask
public static byte WriteBit(byte value, int bitOffset, bool on);
public static bool InsertBit(ref byte value, int bitOffset);
public static byte InsertBit(byte value, int bitOffset); // Used in SessionMask
// ClearBit, ComplementBit similar |
When majority of cases do inline the algebra, is it worth having an API for small minority of cases in the framework? It feels similar to the situation with "throw helpers". Majority of code throws exceptions directly. Some folks like to have helper methods for throwing exceptions, but there is a lot of variety, styles and trade-offs. We had API suggestions for introducing a helper methods for this in the framework (like https://github.com/dotnet/corefx/issues/17068). None of them made it because of the use case is not significant enough, and it is easy enough for folks who likes to have these to have their own local copy that they like. |
|
Fair point, I agree with you on throw helpers. // Devs would likely need to unit this one to make sure it's right
public static uint ComplementBit(uint value, int bitOffset)
=> ~(~(1u << bitOffset) ^ value);
// Easy once you know the idiom but at a glance it certainly doesn't say `BTR`
public static uint ClearBit(uint value, int bitOffset)
=> value & ~(1u << bitOffset);
// True BTR behavior, ie returns original value
public static bool ClearBit(ref uint value, int bitOffset)
{
uint mask = 1u << bitOffset;
bool btr = (value & mask) != 0;
value &= ~mask;
return btr;
} |
|
@grant-d Hi Grant, I'm hunting for the latest version of your |
|
|
|
@benaadams ah yes, thanks! I saw that the other day, but I'm looking for the unapproved ones like I'm working on a |
|
This might also help: |
ExtractBit, InsertBit, ClearBit
[Edited]
This is intended to be the last PR in this series (LZCNT, LOG2, TZCNT, ROTL, POPCNT)
Previously, all the above changes were in this PR; I decided to split them into separate PRs to make CR easier.
This PR is now repurposed to focus on the final set of changes.
There are several implementations of
ExtractBit,InsertBit,Clearbitin the stack.BitOpsmethods pass unitsThe benefits here are:
All known callers only use
byte,intoruintarguments.We may decide to add
long,ulongfor consistency.The know callers are mostly downstream (also see review comments):
cc @tannergooding, @jkotas