Skip to content

erts: add erlang:ctz/1 and erlang:popcount/1 BIFs - #11371

Open
saleyn wants to merge 2 commits into
erlang:masterfrom
saleyn:bitops
Open

erts: add erlang:ctz/1 and erlang:popcount/1 BIFs#11371
saleyn wants to merge 2 commits into
erlang:masterfrom
saleyn:bitops

Conversation

@saleyn

@saleyn saleyn commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Adds functionality and tests for efficient bit operations on 64-bit values that implement compiler built-in functions.

  • Implement ctz/1, popcount/1 using compiler builtins with portable fallbacks
  • Export and document BIF stubs in erlang.erl
  • Implement JIT optimized versions of these functions
  • Add Common Test suite bitops_SUITE.erl
  • Update bif.tab entries for the new BIFs
  • Ensure build links the new object: add erl_bif_bitops.o to Makefile.in and regenerate build files
  • Recompile the preloaded erlang.beam

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

CT Test Results

    3 files    136 suites   56m 16s ⏱️
1 682 tests 1 625 ✅ 56 💤 1 ❌
2 325 runs  2 250 ✅ 74 💤 1 ❌

For more details on these failures, see this check.

Results for commit 7b4efd8.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@saleyn
saleyn force-pushed the bitops branch 2 times, most recently from 35013c5 to 62cab4c Compare July 18, 2026 06:46
@jhogberg

Copy link
Copy Markdown
Contributor

Thanks for the PR! We're going to review this once most of us are back from summer vacation. One thing that I'm sure will be brought up is that this is limited to 64 bits, whereas Erlang uses arbitrary precision arithmetic. A fast implementation would be relatively trivial as all three operations can be done in a data-parallel manner.

@saleyn
saleyn force-pushed the bitops branch 2 times, most recently from cd6e095 to 4f1295c Compare July 18, 2026 15:49
@saleyn

saleyn commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@jhogberg thanks for the update. One CI action I cannot get to pass is this, which requires maintainers to modify the preloaded erlang.beam:

Run PERMISSION=$(gh api \
Error: Workflow failed: Only maintainers can make modifications to '*.beam' files:
erts/preloaded/ebin/erlang.beam
Error: Process completed with exit code 1.

I added support for bignums in the commit below.

@jhogberg

jhogberg commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

@jhogberg thanks for the update. One CI action I cannot get to pass is this, which requires maintainers to modify the preloaded erlang.beam:

Yes, we'll need to do that for you later on.

I added support for bignums in the commit below.

I don't like that it still assumes 64 bits, highest_set_bit and lowest_set_bit (or whatever to name them) that returns the respective position from 0 leaves that to the user.

I also think the implementation is a bit too ambitious, I'd prefer just a dead-simple C implementation that ignores SIMD/__builtin_xyz because in practice the BIF will be a fallback only for use in apply/3 with the JIT doing all the heavy lifting. Implementing them in the JIT is pretty quick once the semantics have been decided. :)

@saleyn

saleyn commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@jhogberg thanks for the update. One CI action I cannot get to pass is this, which requires maintainers to modify the preloaded erlang.beam:

Yes, we'll need to do that for you later on.

I added support for bignums in the commit below.

I don't like that it still assumes 64 bits, highest_set_bit and lowest_set_bit (or whatever to name them) that returns the respective position from 0 leaves that to the user.

In the last commit, there's no more 64-bit implicit limitation, it just handles 64bit integers differently (built-in GCC) from bignums.

I also think the implementation is a bit too ambitious, I'd prefer just a dead-simple C implementation that ignores SIMD/__builtin_xyz because in practice the BIF will be a fallback only for use in apply/3 with the JIT doing all the heavy lifting. Implementing them in the JIT is pretty quick once the semantics have been decided. :)

You are right, I didn't think about JIT. I will remove SIMD optimization, leaving only bignum support from the last commit. Or should I leave it for reference (I assume in JIT the same implementation would be needed), and remove once implemented in JIT?

@jhogberg

Copy link
Copy Markdown
Contributor

In the last commit, there's no more 64-bit implicit limitation, it just handles 64bit integers differently (built-in GCC) from bignums.

It assumes a bit width of 64, which doesn't really make sense for bignums (cf. ?assertEqual(0, erlang:clz(BigNum100))). It would be better to have a "get position of highest set bit" operation that's independent of bit width.

You are right, I didn't think about JIT. I will remove SIMD optimization, leaving only bignum support from the last commit. Or should I leave it for reference (I assume in JIT the same implementation would be needed), and remove once implemented in JIT?

You can probably do that part yourself. Look at how erlang:yield/0 is turned into an i_yield instruction in ops.tab, which is implemented in instr_common.cpp. Then look at how i_unary_minus is implemented, specifically the "body" variant of it, and how its bignum fallback is implemented in a shared code fragment to reduce code size (you can declare these in beam_asm_global.hpp.pl).

(You can ignore the emit_enter_runtime stuff since there's no need to call the C implementation.)

@jhogberg jhogberg added team:VM Assigned to OTP team VM team:LG Assigned to OTP language group stalled waiting for input by the Erlang/OTP team labels Jul 20, 2026
@saleyn

saleyn commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

It assumes a bit width of 64, which doesn't really make sense for bignums (cf. ?assertEqual(0, erlang:clz(BigNum100))). It would be better to have a "get position of highest set bit" operation that's independent of bit width.

I see what you meant, and I agree, in presence of bignums the clz/1 makes no sense, so I deleted it completely.

Look at how erlang:yield/0 is turned into an i_yield instruction in ops.tab, which is implemented in instr_common.cpp. Then look at how i_unary_minus is implemented, specifically the "body" variant of it, and how its bignum fallback is implemented in a shared code fragment to reduce code size (you can declare these in beam_asm_global.hpp.pl).

I followed your hints, and added JIT implementation accompanied by test cases.

@saleyn saleyn changed the title erts: add erlang:ctz/1, erlang:clz/1 and erlang:popcount/1 BIFs erts: add erlang:ctz/1 and erlang:popcount/1 BIFs Jul 21, 2026
@saleyn
saleyn force-pushed the bitops branch 7 times, most recently from 5a8415d to 18ee827 Compare July 21, 2026 14:58
@saleyn

saleyn commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@jhogberg, please review my latest changes, and if you could, please check for the two failing CI checks. I am not sure if the emulator failing test is related to my changes.

@saleyn
saleyn force-pushed the bitops branch 2 times, most recently from fc3f642 to 6127578 Compare July 21, 2026 18:43
…and BIF cases

erts_ctz() and erts_popcount() now handle BOTH small 64-bit integers AND
bignums with optimized fast paths:
* Fast path: term_to_Uint64() extracts 64-bit values, uses __builtin_ctzll()
  and __builtin_popcountll() on ARCH_64, or dual-half processing on ARCH_32
* Slow path: Existing SIMD-optimized bignum logic (AVX-512 or ARM NEON)
@jhogberg

jhogberg commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

I see what you meant, and I agree, in presence of bignums the clz/1 makes no sense, so I deleted it completely.

"Get position of highest (set) bit" makes sense however, and can be used to implement clz/1 on any bit-width.

I followed your hints, and added JIT implementation accompanied by test cases.

That implementation won't run, they're normal BIFs in implementation and bif.tab, but the pattern matching in ops.tab has them as GC BIFs (which would also require additional compiler support). My instructions were probably a bit to unclear.

Thanks for giving it a shot though, if we accept these, we'll take care of it. :-)

@jhogberg, please review my latest changes, and if you could, please check for the two failing CI checks. I am not sure if the emulator failing test is related to my changes.

It looks unrelated to me too.

@saleyn

saleyn commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

I see what you meant, and I agree, in presence of bignums the clz/1 makes no sense, so I deleted it completely.

"Get position of highest (set) bit" makes sense however, and can be used to implement clz/1 on any bit-width.

I followed your hints, and added JIT implementation accompanied by test cases.

That implementation won't run, they're normal BIFs in implementation and bif.tab, but the pattern matching in ops.tab has them as GC BIFs (which would also require additional compiler support). My instructions were probably a bit to unclear.

Thanks for giving it a shot though, if we accept these, we'll take care of it. :-)

@jhogberg, please review my latest changes, and if you could, please check for the two failing CI checks. I am not sure if the emulator failing test is related to my changes.

It looks unrelated to me too.

Ok, so I'll leave it up to you to fill in the gaps if the team decides to merge.

@jhogberg jhogberg self-assigned this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stalled waiting for input by the Erlang/OTP team team:LG Assigned to OTP language group team:VM Assigned to OTP team VM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants