x86-64 machine code, 12 bytes
This is a function you can call with the x86-64 System V calling convention, with this signature:
void binary_placevalues_withzero(uint8_t out[8] /*rdi*/, uint8_t val /*sil*/);
or
void binary_placevalues_withzero(uint64_t *out /*rdi*/, uint8_t val /*sil*/);
as a quick hack for making the output something you can print as one hex number for all 8 bytes.
It isolates the place values in increasing order into the output array, producing a result like 80 00 00 00 00 00 00 01
for esi = 129.
We start with mask = 1, and left-shift the mask by 1 until it overflows an 8-bit register.
nasm -l listing
25 address code bytes global binary_placevalues_withzero
26 binary_placevalues_withzero:
27 00000020 B101 mov cl,1
28 .loop:
29 00000022 88C8 mov al,cl
30 00000024 21F0 and eax, esi ; al,sil would cost a REX prefix
31 00000026 AA stosb ; *rdi++ = val & mask
32 00000027 00C9 add cl,cl ; left-shift the mask
33 00000029 73F7 jnc .loop ; until it shifts out
34 0000002B C3 ret
An alternate version that only writes non-zero entries (thus producing an output array of length __builtin_popcount(val)
) is also 12 bytes. This needs val
zero-extended into ESI to avoid test
false positives, unlike the version that doesn't skip zeros.
void binary_placevalues(uint8_t out[8] /*rdi*/, unsigned val /*esi*/);
11 global binary_placevalues
12 binary_placevalues:
13 00000010 B001 mov al,1
14 .loop:
15 00000012 85F0 test eax, esi ; ESI must have its high bits clear, so high garbage in EAX doesn't matter
16 00000014 7401 jz .skip
17 00000016 AA stosb ; store if the mask matches
18 .skip:
19 00000017 00C0 add al,al ; left-shift the mask
20 00000019 73F7 jnc .loop
21 0000001B C3 ret
22
As usual, ISA extensions that let us save instructions cost more code bytes: a BMI1 version is 14 bytes. (https://www.felixcloutier.com/x86/BLSI.html and https://www.felixcloutier.com/x86/BLSR.html). Very efficient, though: each of these BLSI/BLSR instructions is only 1 uop.
1 global binary_placevalues_bmi1
2 binary_placevalues_bmi1:
3 .loop:
4 00000000 C4E278F3DE blsi eax, esi ; bit lowest-set isolate: n &-n
5 00000005 AA stosb
6 00000006 C4E248F3CE blsr esi, esi ; bit lowest-set reset: (n-1) & n, and sets ZF according to the result.
7 0000000B 75F3 jnz .loop
8 0000000D C3 ret
(If there was high garbage in ESI, this would keep going, storing 0
bytes when EAX held an isolated bit outside the low 8.)
If you wanted speed, with BMI2 pdep
you'd use a 64-bit mask that deposited the low 8 bits at their corresponding position within each byte. 1 uop / 3 cycle latency on Haswell/Skylake, at the cost of a 10-byte instruction to create the mask. :P But slow on Ryzen.
I'm not sure this is optimal. I don't think manually implementing n & -n
and so on with xor/sub/and and so on would be a win, though.