Skip to content

Proposal: Expose Bit Manipulation functions  #27382

Description

@grant-d

Bit manipulation routines are common enough that we should expose a subset as platform primitives.
While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

Scope

Rationale and Usage

The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
(There is likely to be more; the initial search was timeboxed to ~1 hour)

Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

[MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

However making a call to it meets our goal of abstraction and performance:

uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

Proposed API

The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

Details

  • The focus will be on performance.
  • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
  • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
  • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

Questions

  • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

Decisions

  • What namespace this should be in. Decision: namespace System.Numerics.
  • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
  • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
  • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
  • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
  • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
  • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
  • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

Sample call sites

The following samples are taken from the linked units, from the method BitOps_Samples.
The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

// ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

Updates

  • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
  • Initial proposal submitted
  • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
  • Some name changes based on suggestions (eg FlipBit became ComplementBit).
  • TestAndSet methods have been refactored into simple scalar functions, for performance.
  • The offset parameter is int instead of byte. All POC units pass.
  • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
  • Added ExtractByte and friends
  • Added Evaluate(bool) (used internally, so may as well expose it)
  • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
  • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
  • Added Log2
  • Removed redundant overloads
  • Removed doc-comments so spec is easier to read
  • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
  • Added Scope section at top of spec
  • Separated calls into existing and proposed sections
  • Added task list, worded spec in a more concise manner
  • Moved RotateByte, etc into to do later proposal in comments

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
      Skip to content

      Proposal: Expose Bit Manipulation functions  #27382

      Description

      @grant-d

      Bit manipulation routines are common enough that we should expose a subset as platform primitives.
      While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

      The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

      The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

      Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

      Scope

      Rationale and Usage

      The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
      Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
      (There is likely to be more; the initial search was timeboxed to ~1 hour)

      Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

      Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

      [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

      However making a call to it meets our goal of abstraction and performance:

      uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

      Proposed API

      The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

      Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

      publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

      Details

      • The focus will be on performance.
      • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
      • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
      • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

      Questions

      • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

      Decisions

      • What namespace this should be in. Decision: namespace System.Numerics.
      • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
      • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
      • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
      • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
      • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
      • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
      • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

      Sample call sites

      The following samples are taken from the linked units, from the method BitOps_Samples.
      The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

      // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

      Updates

      • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
      • Initial proposal submitted
      • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
      • Some name changes based on suggestions (eg FlipBit became ComplementBit).
      • TestAndSet methods have been refactored into simple scalar functions, for performance.
      • The offset parameter is int instead of byte. All POC units pass.
      • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
      • Added ExtractByte and friends
      • Added Evaluate(bool) (used internally, so may as well expose it)
      • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
      • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
      • Added Log2
      • Removed redundant overloads
      • Removed doc-comments so spec is easier to read
      • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
      • Added Scope section at top of spec
      • Separated calls into existing and proposed sections
      • Added task list, worded spec in a more concise manner
      • Moved RotateByte, etc into to do later proposal in comments

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

        Type

        No type

        Projects

        No projects

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
          Skip to content

          Proposal: Expose Bit Manipulation functions  #27382

          Description

          @grant-d

          Bit manipulation routines are common enough that we should expose a subset as platform primitives.
          While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

          The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

          The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

          Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

          Scope

          Rationale and Usage

          The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
          Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
          (There is likely to be more; the initial search was timeboxed to ~1 hour)

          Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

          Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

          [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

          However making a call to it meets our goal of abstraction and performance:

          uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

          Proposed API

          The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

          Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

          publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

          Details

          • The focus will be on performance.
          • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
          • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
          • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

          Questions

          • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

          Decisions

          • What namespace this should be in. Decision: namespace System.Numerics.
          • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
          • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
          • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
          • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
          • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
          • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
          • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

          Sample call sites

          The following samples are taken from the linked units, from the method BitOps_Samples.
          The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

          // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

          Updates

          • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
          • Initial proposal submitted
          • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
          • Some name changes based on suggestions (eg FlipBit became ComplementBit).
          • TestAndSet methods have been refactored into simple scalar functions, for performance.
          • The offset parameter is int instead of byte. All POC units pass.
          • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
          • Added ExtractByte and friends
          • Added Evaluate(bool) (used internally, so may as well expose it)
          • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
          • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
          • Added Log2
          • Removed redundant overloads
          • Removed doc-comments so spec is easier to read
          • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
          • Added Scope section at top of spec
          • Separated calls into existing and proposed sections
          • Added task list, worded spec in a more concise manner
          • Moved RotateByte, etc into to do later proposal in comments

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

            Type

            No type

            Projects

            No projects

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
              Skip to content

              Proposal: Expose Bit Manipulation functions  #27382

              Description

              @grant-d

              Bit manipulation routines are common enough that we should expose a subset as platform primitives.
              While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

              The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

              The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

              Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

              Scope

              Rationale and Usage

              The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
              Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
              (There is likely to be more; the initial search was timeboxed to ~1 hour)

              Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

              Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

              [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

              However making a call to it meets our goal of abstraction and performance:

              uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

              Proposed API

              The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

              Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

              publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

              Details

              • The focus will be on performance.
              • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
              • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
              • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

              Questions

              • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

              Decisions

              • What namespace this should be in. Decision: namespace System.Numerics.
              • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
              • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
              • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
              • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
              • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
              • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
              • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

              Sample call sites

              The following samples are taken from the linked units, from the method BitOps_Samples.
              The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

              // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

              Updates

              • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
              • Initial proposal submitted
              • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
              • Some name changes based on suggestions (eg FlipBit became ComplementBit).
              • TestAndSet methods have been refactored into simple scalar functions, for performance.
              • The offset parameter is int instead of byte. All POC units pass.
              • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
              • Added ExtractByte and friends
              • Added Evaluate(bool) (used internally, so may as well expose it)
              • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
              • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
              • Added Log2
              • Removed redundant overloads
              • Removed doc-comments so spec is easier to read
              • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
              • Added Scope section at top of spec
              • Separated calls into existing and proposed sections
              • Added task list, worded spec in a more concise manner
              • Moved RotateByte, etc into to do later proposal in comments

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

                Type

                No type

                Projects

                No projects

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
                  Skip to content

                  Proposal: Expose Bit Manipulation functions  #27382

                  Description

                  @grant-d

                  Bit manipulation routines are common enough that we should expose a subset as platform primitives.
                  While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

                  The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

                  The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

                  Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

                  Scope

                  Rationale and Usage

                  The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
                  Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
                  (There is likely to be more; the initial search was timeboxed to ~1 hour)

                  Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

                  Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

                  [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

                  However making a call to it meets our goal of abstraction and performance:

                  uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

                  Proposed API

                  The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

                  Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

                  publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

                  Details

                  • The focus will be on performance.
                  • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
                  • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
                  • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

                  Questions

                  • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

                  Decisions

                  • What namespace this should be in. Decision: namespace System.Numerics.
                  • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
                  • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
                  • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
                  • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
                  • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
                  • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
                  • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

                  Sample call sites

                  The following samples are taken from the linked units, from the method BitOps_Samples.
                  The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

                  // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

                  Updates

                  • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
                  • Initial proposal submitted
                  • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
                  • Some name changes based on suggestions (eg FlipBit became ComplementBit).
                  • TestAndSet methods have been refactored into simple scalar functions, for performance.
                  • The offset parameter is int instead of byte. All POC units pass.
                  • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
                  • Added ExtractByte and friends
                  • Added Evaluate(bool) (used internally, so may as well expose it)
                  • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
                  • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
                  • Added Log2
                  • Removed redundant overloads
                  • Removed doc-comments so spec is easier to read
                  • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
                  • Added Scope section at top of spec
                  • Separated calls into existing and proposed sections
                  • Added task list, worded spec in a more concise manner
                  • Moved RotateByte, etc into to do later proposal in comments

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

                    Type

                    No type

                    Projects

                    No projects

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
                      Skip to content

                      Proposal: Expose Bit Manipulation functions  #27382

                      Description

                      @grant-d

                      Bit manipulation routines are common enough that we should expose a subset as platform primitives.
                      While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

                      The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

                      The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

                      Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

                      Scope

                      Rationale and Usage

                      The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
                      Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
                      (There is likely to be more; the initial search was timeboxed to ~1 hour)

                      Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

                      Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

                      [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

                      However making a call to it meets our goal of abstraction and performance:

                      uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

                      Proposed API

                      The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

                      Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

                      publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

                      Details

                      • The focus will be on performance.
                      • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
                      • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
                      • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

                      Questions

                      • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

                      Decisions

                      • What namespace this should be in. Decision: namespace System.Numerics.
                      • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
                      • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
                      • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
                      • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
                      • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
                      • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
                      • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

                      Sample call sites

                      The following samples are taken from the linked units, from the method BitOps_Samples.
                      The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

                      // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

                      Updates

                      • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
                      • Initial proposal submitted
                      • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
                      • Some name changes based on suggestions (eg FlipBit became ComplementBit).
                      • TestAndSet methods have been refactored into simple scalar functions, for performance.
                      • The offset parameter is int instead of byte. All POC units pass.
                      • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
                      • Added ExtractByte and friends
                      • Added Evaluate(bool) (used internally, so may as well expose it)
                      • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
                      • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
                      • Added Log2
                      • Removed redundant overloads
                      • Removed doc-comments so spec is easier to read
                      • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
                      • Added Scope section at top of spec
                      • Separated calls into existing and proposed sections
                      • Added task list, worded spec in a more concise manner
                      • Moved RotateByte, etc into to do later proposal in comments

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

                        Type

                        No type

                        Projects

                        No projects

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
                          Skip to content

                          Proposal: Expose Bit Manipulation functions  #27382

                          Description

                          @grant-d

                          Bit manipulation routines are common enough that we should expose a subset as platform primitives.
                          While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

                          The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

                          The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

                          Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

                          Scope

                          Rationale and Usage

                          The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
                          Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
                          (There is likely to be more; the initial search was timeboxed to ~1 hour)

                          Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

                          Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

                          [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

                          However making a call to it meets our goal of abstraction and performance:

                          uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

                          Proposed API

                          The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

                          Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

                          publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

                          Details

                          • The focus will be on performance.
                          • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
                          • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
                          • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

                          Questions

                          • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

                          Decisions

                          • What namespace this should be in. Decision: namespace System.Numerics.
                          • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
                          • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
                          • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
                          • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
                          • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
                          • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
                          • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

                          Sample call sites

                          The following samples are taken from the linked units, from the method BitOps_Samples.
                          The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

                          // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

                          Updates

                          • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
                          • Initial proposal submitted
                          • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
                          • Some name changes based on suggestions (eg FlipBit became ComplementBit).
                          • TestAndSet methods have been refactored into simple scalar functions, for performance.
                          • The offset parameter is int instead of byte. All POC units pass.
                          • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
                          • Added ExtractByte and friends
                          • Added Evaluate(bool) (used internally, so may as well expose it)
                          • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
                          • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
                          • Added Log2
                          • Removed redundant overloads
                          • Removed doc-comments so spec is easier to read
                          • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
                          • Added Scope section at top of spec
                          • Separated calls into existing and proposed sections
                          • Added task list, worded spec in a more concise manner
                          • Moved RotateByte, etc into to do later proposal in comments

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

                            Type

                            No type

                            Projects

                            No projects

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Proposal: Expose Bit Manipulation functions · Issue #27382 · dotnet/runtime · GitHub
                              Skip to content

                              Proposal: Expose Bit Manipulation functions  #27382

                              Description

                              @grant-d

                              Bit manipulation routines are common enough that we should expose a subset as platform primitives.
                              While some of them may be simple to write, it's much harder to achieve the requisite performance desired, especially since they tend to be used within tight loops.

                              The aim of this proposal is to scope & design a minimal set of functions, to be implemented with a bias towards performance. Per @tannergooding

                              The point of these APIs is to provide a general-purpose API that works on all platforms (which means providing a software fallback) and is generally-usable. Hardware Intrinsics are for performance oriented scenarios where you require hardware acceleration and need more direct control of the code that is emitted.

                              Note that even though some of the formula may be simple, relevant callsites are more self-documenting when using the intrinsics (should the dev choose to use them).

                              Scope

                              Rationale and Usage

                              The proposed functions are already implemented throughout the stack, often with different algorithms, performance characteristics and test coverage.
                              Existing callsites below: https://github.com/dotnet/corefx/issues/32269#issuecomment-457689128
                              (There is likely to be more; the initial search was timeboxed to ~1 hour)

                              Some of the implementation have suboptimal performance or bugs. Something like ExtractBit is trivial to implement, but PopCount is more complex and thus prone to logic and performance issues. Hiding these complex formulae behind friendly signatures makes using them more approachable.

                              Here's an example of a function (BTC) whose signature is simple but the algebra is easy to get wrong.

                              [MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticboolComplementBit(refuintvalue,intbitOffset){uintmask=1u<<bitOffset;boolbtc=(value&mask)!=0;value=~(~mask^value);returnbtc;}

                              However making a call to it meets our goal of abstraction and performance:

                              uintvalue=123;boolpreviouslyTrue=BitOperations.ComplementBit(refvalue,6);

                              Proposed API

                              The proposed API is purposefully kept lean. We can add more methods in later design iterations. We should view this as an opportunity to get simple, base functionality out the door and not stray into the dangerous territory of adding every bit twiddling hack that exists.

                              Assume all methods are decorated with [MethodImpl(MethodImplOptions.AggressiveInlining)]

                              publicstaticclassBitOperations{// BTboolExtractBit(bytevalue,intbitOffset);// Could name this BitTest or TestBitboolExtractBit(uintvalue,intbitOffset);boolExtractBit(intvalue,intbitOffset);// BTS (scalar)byteInsertBit(bytevalue,intbitOffset);// BitSet or SetBituintInsertBit(uintvalue,intbitOffset);intInsertBit(intvalue,intbitOffset);// True BTS (returns original value)boolInsertBit(refbytevalue,intbitOffset);boolInsertBit(refuintvalue,intbitOffset);boolInsertBit(refintvalue,intbitOffset);// BTRbyteClearBit(bytevalue,intbitOffset);// BitReset or ResetBituintClearBit(uintvalue,intbitOffset);intClearBit(intvalue,intbitOffset);boolClearBit(refbytevalue,intbitOffset);boolClearBit(refuintvalue,intbitOffset);boolClearBit(refintvalue,intbitOffset);// BTCbyteComplementBit(bytevalue,intbitOffset);uintComplementBit(uintvalue,intbitOffset);intComplementBit(intvalue,intbitOffset);boolComplementBit(refbytevalue,intbitOffset);boolComplementBit(refuintvalue,intbitOffset);boolComplementBit(refintvalue,intbitOffset);// on ? BTS : BTRbyteWriteBit(bytevalue,intbitOffset,boolon);uintWriteBit(uintvalue,intbitOffset,boolon);intWriteBit(intvalue,intbitOffset,boolon);boolWriteBit(refbytevalue,intbitOffset,boolon);boolWriteBit(refuintvalue,intbitOffset,boolon);boolWriteBit(refintvalue,intbitOffset,boolon);}

                              Details

                              • The focus will be on performance.
                              • Ultimately, most of the code should be branchless and leverage intrinsics where possible.
                              • Somewhat of an overlap in functionality between InsertBit/ClearBit and WriteBit where the latter conditionally executes the equivalent of either former (using twiddling to do so without branching). But there's enough twiddling in Write to avoid any branching that it's maybe worth keeping both variants.
                              • Since these functions are used in performance-sensitive applications, we avoid input checking and exceptions. The current design tries to dispense with the requirement by sharpening input types, contractual assumptions (eg out-of-bounds offset will use mod n in some functions or result in a no-op in others) and specific design choices.

                              Questions

                              • What additional sizes & signs of integers do we support, and in which methods? It looks like int, uint and byte are commonly used. Anything else?

                              Decisions

                              • What namespace this should be in. Decision: namespace System.Numerics.
                              • The class name of BitOps is self-documenting, terse (it might be specified frequently in a callsite, if not aliased with using) and both terms are well-known names or abbreviations. Decision: BitOperations
                              • Decision: We favor well-known names over the alternatives. For example, PopCount could be called CountSetBits but that's not the common lingo used by twiddlers. Furthermore, intrinsics already expose the well-known names, eg Popcnt.PopCount.
                              • Likewise, method names should also be as concise as possible, for example, TrailingZeroCount may be more concisely described as TrailingZeros. Decision: TrailingZeroCount already chosen by a previous PR.
                              • Decision: Count-oriented methods such as PopCount return (idiomatic) int, not uint
                              • Should offset or position parameters be int or uint. Latter is preferred since negatives not permitted regardless. Decision: int is an idiomatic input/output type in C#.
                              • Log(0) is mathematically undefined. Should it return 0 or -1? Decision: Returns 0
                              • Do we care about endianness (ie do we need BE and LE variants of relevant methods). The current proposal is only LE, and is shaped in such a way that we are not future-proof wrt supporting BE. Discussion proposes an alternative. For example, LeadingZerosmight need a different algorithm on BE/LE platforms. Decision: Endianess only matters if we are reinterpreting integers.

                              Sample call sites

                              The following samples are taken from the linked units, from the method BitOps_Samples.
                              The code chooses values that are easy to eyeball for correctness. The real units cover many more boundaries & conditions.

                              // ExtractBit: Reads whether the specified bit in a mask is set.Assert.True(BitOps.ExtractBit((byte)0b0001_0000,4));Assert.False(BitOps.ExtractBit((byte)0b0001_0000,7));// InsertBit: Sets the specified bit in a mask and returns the new value.bytedest=0b0000_1001;Assert.Equal(0b0010_1001,BitOps.InsertBit(dest,5));// InsertBit(ref): Sets the specified bit in a mask and returns whether it was originally set.Assert.False(BitOps.InsertBit(refdest,5));Assert.Equal(0b0010_1001,dest);// ClearBit: Clears the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ClearBit(dest,3));// ClearBit(ref): Clears the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ClearBit(refdest,3));Assert.Equal(0b0000_0001,dest);// ComplementBit: Complements the specified bit in a mask and returns the new value.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.ComplementBit(dest,3));// ComplementBit(ref): Complements the specified bit in a mask and returns whether it was originally set.Assert.True(BitOps.ComplementBit(refdest,3));Assert.Equal(0b0000_0001,dest);// WriteBit: Writes the specified bit in a mask and returns the new value. Does not branch.dest=0b0000_1001;Assert.Equal(0b0000_0001,BitOps.WriteBit(dest,3,on:false));// WriteBit(ref): Writes the specified bit in a mask and returns whether it was originally set. Does not branch.Assert.True(BitOps.WriteBit(refdest,3,on:false));Assert.Equal(0b0000_0001,dest);

                              Updates

                              • Original issue authored by @mburbea here: Proposal: Add a BitManipulation class
                              • Initial proposal submitted
                              • Methods such as InsertBit accepted a bool that determined whether it set or cleared the bit in question. Such methods have now been refactored in two, InsertBit and ClearBit.
                              • Some name changes based on suggestions (eg FlipBit became ComplementBit).
                              • TestAndSet methods have been refactored into simple scalar functions, for performance.
                              • The offset parameter is int instead of byte. All POC units pass.
                              • Added WriteBit(value, offset, bool on) overloads that conditionally set/clear the specified bit.
                              • Added ExtractByte and friends
                              • Added Evaluate(bool) (used internally, so may as well expose it)
                              • Removed all Span<T> overloads per @tannergooding advice. Will maybe submit in separate proposal
                              • Moved TrailingOnes and LeadingOnes into to do later proposal in comments
                              • Added Log2
                              • Removed redundant overloads
                              • Removed doc-comments so spec is easier to read
                              • Added byte overloads to all crud methods based on code analysis of existing callsites. eg eg bool ExtractBit(byte value, int bitOffset)
                              • Added Scope section at top of spec
                              • Separated calls into existing and proposed sections
                              • Added task list, worded spec in a more concise manner
                              • Moved RotateByte, etc into to do later proposal in comments

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Numerics

                                Type

                                No type

                                Projects

                                No projects

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions