link
최적화는 안전한 러스트 코드가 젤 빠름.
어셈블리는 공부용으로 만듬
cpu 64비트 관리하는거 만들어봄.|🔝|
src/lib.rs
// safe rust
pub fn safe_rust_check_all_bits(value: u64) -> Vec<bool> {
let bits: Vec<bool> = (0..64)
.rev()
.map(|i| (value & (1 << i)) != 0)
.collect();
bits
}
// asm
unsafe extern "C" {
fn check_bit(value: u64, index: u64) -> u64;
}
pub fn check_all_bits(value: u64) -> [bool; 64] {
let mut bits = [false; 64];
for i in 0usize..64 {
let bit_index = (63 - i) as u64;
let result = unsafe { check_bit(value, bit_index) };
bits[i] = result != 0;
}
bits
}
tests/safe_rust.rs
#[test]
fn safe_rust_check_all_bits_test() {
let value: u64 = 1 << 62 | 1 << 32 | 1 << 30 | 1 << 20;
let bits = safe_rust_check_all_bits(value);
println!("{:?}", bits);
}tests/asm_rust_ffi_test.rs
#[test]
fn asm_test() {
let values: [u64; 4] = [0, 1 << 61, 1 << 62, u64::MAX];
for value in values {
let bits = check_all_bits(value);
println!("Value: {value:#066b}");
println!("Bits: {:?}", bits);
println!();
}
}asm/x86_64.S
.intel_syntax noprefix
#ifdef __APPLE__
#define SYMBOL(name) _##name
#else
#define SYMBOL(name) name
#endif
.text
.global SYMBOL(check_bit)
#ifndef __APPLE__
.type check_bit, @function
#endif
SYMBOL(check_bit):
# RDI = value
# RSI = bit index (0..63)
# RAX = result (0 or 1)
bt rdi, rsi
setc al
movzx eax, al
ret
#ifndef __APPLE__
.size check_bit, .-check_bit
.section .note.GNU-stack,"",@progbits
#endif#ifdef __APPLE__
#define SYMBOL(name) _##name
#else
#define SYMBOL(name) name
#endif
.text
.globl SYMBOL(check_bit)
#ifndef __APPLE__
.type check_bit, %function
#endif
SYMBOL(check_bit):
// X0 = value
// X1 = bit index (0..63)
// Return X0 = 0 or 1
lsr x0, x0, x1
and x0, x0, #1
ret
#ifndef __APPLE__
.size check_bit, .-check_bit
.section .note.GNU-stack,"",%progbits
#endifbuild.rs
use std::env;
fn main() {
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Missing target architecture");
let asm_file = match arch.as_str() {
"x86_64" => "asm/x86_64.S",
"aarch64" => "asm/aarch64.S",
other => panic!("Unsupported architecture: {other}"),
};
println!("cargo:rerun-if-changed={asm_file}");
cc::Build::new().file(asm_file).compile("bitcheck_asm");
}assembly분석(x86_64)|🔝|
Let’s examine your x86-64 assembly code line by line, including the GNU assembler (GAS) syntax, C preprocessor directives, System V AMD64 calling convention, CPU registers, and how the BT instruction checks individual bits.
Your assembly function implements this Rust declaration:
unsafe extern "C" {
fn check_bit(value: u64, index: u64) -> u64;
}Its job is to check one bit of a 64-bit integer and return 0 or 1.
1. Complete assembly code
.intel_syntax noprefix
#ifdef __APPLE__
#define SYMBOL(name) _##name
#else
#define SYMBOL(name) name
#endif
.text
.global SYMBOL(check_bit)
#ifndef __APPLE__
.type check_bit, @function
#endif
SYMBOL(check_bit):
# RDI = value
# RSI = bit index (0..63)
# RAX = result (0 or 1)
bt rdi, rsi
setc al
movzx eax, al
ret
#ifndef __APPLE__
.size check_bit, .-check_bit
.section .note.GNU-stack,"",@progbits
#endif2. Intel syntax
.intel_syntax noprefixThis tells GNU assembler to use Intel assembly syntax instead of AT&T syntax.
There are two common x86 assembly syntaxes:
Intel syntax:
mov rax, rdi
add rax, 10AT&T syntax:
movq %rdi, %rax
addq $10, %raxThe differences:
| Feature | Intel | AT&T |
|---|---|---|
| Operand order | Destination, source | Source, destination |
| Register prefix | None | % |
| Immediate prefix | None | $ |
| Memory operands | [rax] | (%rax) |
| Size suffix | Usually implicit | b, w, l, q |
For example:
mov rax, rdiMeans:
RAX = RDIThe destination is on the left.
3. C preprocessor directives
Your assembly file has .S as its extension.
This is important because uppercase .S tells GCC to run the C preprocessor before passing the source to the assembler.
3.1 Detect macOS
#ifdef __APPLE__
#define SYMBOL(name) _##name
#else
#define SYMBOL(name) name
#endifThese are C preprocessor directives, not CPU instructions.
They allow you to use different symbol names on different operating systems.
On Linux:
.global check_bit
check_bit:On macOS:
.global _check_bit
_check_bit:Why?
macOS Mach-O symbol naming convention typically adds a leading underscore to externally visible C symbols.
The macro:
#define SYMBOL(name) _##nameUses the C preprocessor’s ## token-pasting operator.
For example:
SYMBOL(check_bit)Expands to:
_check_biton macOS.
On Linux, it expands to:
check_bitThis makes the assembly function compatible with both platforms.
4. Section and symbol declarations
4.1 .text
.textThis tells the assembler that subsequent instructions belong to the executable code section.
A typical ELF executable has several sections:
| Section | Purpose |
|---|---|
.text | Executable machine instructions |
.rodata | Read-only constants |
.data | Initialized global variables |
.bss | Zero-initialized global variables |
.symtab | Symbol table |
.strtab | Symbol names |
Your check_bit function belongs in .text.
4.2 .global
.global SYMBOL(check_bit)This exports the symbol so that other object files can reference it.
Without this directive, the function might remain a local assembler symbol and Rust’s linker would not be able to resolve the external reference.
On Linux, this becomes:
.global check_bitOn macOS:
.global _check_bit4.3 .type
#ifndef __APPLE__
.type check_bit, @function
#endifThis tells the ELF assembler that check_bit is a function symbol.
The syntax is:
.type symbol_name, @functionThis is ELF-specific metadata.
It helps tools such as:
readelf -s target/debug/z99_asm_ffi_bits_check
nm target/debug/z99_asm_ffi_bits_check
objdump -t target/debug/z99_asm_ffi_bits_checkIdentify the symbol as a function.
Mach-O uses different object-file metadata, so this directive is excluded on macOS.
5. Function entry point
SYMBOL(check_bit):This defines the function’s entry label.
When Rust calls:
let result = unsafe {
check_bit(value, bit_index)
};The linker resolves the Rust external function reference to this assembly symbol.
The CPU starts executing at the address associated with this label.
6. Understanding the calling convention
This is one of the most important concepts when combining Rust and assembly.
Your function declaration:
fn check_bit(value: u64, index: u64) -> u64;Uses the C ABI.
On Linux x86-64, Rust follows the System V AMD64 ABI.
On macOS x86-64, the standard calling convention also passes these first two integer arguments in RDI and RSI.
Function argument registers
RDI
First argument
value: u64
RSI
Second argument
index: u64
RAX
Return value
result: u64 (0 or 1)
For example, suppose Rust calls:
check_bit(0x4000000000000000, 62);When assembly starts, the registers contain:
RDI = 0x4000000000000000
RSI = 62Your assembly function must return the result in RAX.
7. The actual bit-checking instructions
This is the most important part of your program.
7.1 bt rdi, rsi
bt rdi, rsiBT means Bit Test.
Syntax:
bt destination, bit_indexIt tests the specified bit and copies its value into the CPU Carry Flag (CF).
It does not modify the original value in RDI.
For example:
RDI = 0100 0000 0000 0000 ...
RSI = 62The CPU checks bit 62.
Remember that bit numbering starts at zero:
Bit position:
63 62 61 60 59 58 57 56 ... 3 2 1 0
0 1 0 0 0 0 0 0 ... 0 0 0 0Therefore:
bt rdi, 62Produces:
CF = 1If the selected bit is zero:
CF = 0The Carry Flag is a CPU status flag in RFLAGS.
CPU RFLAGS (simplified)
CF — Carry Flag
Bit test result
1
Selected bit = 1
0
Selected bit = 0
7.2 setc al
setc alSETC means Set if Carry.
It checks the Carry Flag.
The behavior:
If CF == 1:
AL = 1
If CF == 0:
AL = 0Here is an example:
Before:
RAX = 0x0000000000000000
CF = 1
After:
AL = 1
RAX = 0x0000000000000001One important detail: AL is only the lowest 8 bits of RAX.
The register relationship:
64-bit: RAX
32-bit: EAX
16-bit: AX
8-bit: AH | ALWriting to AL does not automatically clear the upper bits of RAX.
That is why the next instruction is important.
7.3 movzx eax, al
movzx eax, alMOVZX means Move with Zero Extension.
It copies the 8-bit value in AL into EAX, filling the remaining upper bits with zeros.
Example:
Before:
RAX = 0x123456789ABCDEF0
AL = 0x01After setc al:
RAX = 0x123456789ABCDE01After:
movzx eax, alThe result becomes:
EAX = 0x00000001On x86-64, writing to a 32-bit register such as EAX automatically zeroes the upper 32 bits of RAX.
Therefore:
RAX = 0x0000000000000001This guarantees that your Rust function returns a clean u64 value of either 0 or 1.
7.4 ret
retThis returns execution to the caller.
Conceptually:
- Read the return address from the stack.
- Transfer execution to that address.
- Continue executing the Rust code.
The ret instruction does not change your return value in RAX.
8. ELF metadata and stack protection
#ifndef __APPLE__
.size check_bit, .-check_bit
#endif.size specifies the size of the function symbol.
The expression:
.-check_bitMeans:
Current address - check_bit addressThis calculates the number of bytes occupied by the function.
Next:
.section .note.GNU-stack,"",@progbitsThis tells the ELF linker that this object file does not require an executable stack.
It helps avoid warnings such as:
warning: missing .note.GNU-stack section implies executable stackThis directive is excluded on macOS because it is ELF-specific.
9. Complete execution walkthrough
Let’s trace the exact instructions using:
let value = 1u64 << 62;
let index = 62;
let result = unsafe {
check_bit(value, index)
};The input:
value = 0x4000000000000000
Binary:
0100000000000000000000000000000000000000000000000000000000000000Execution:
CPU execution trace
- Function entry
check_bit:RDI = 0x4000000000000000 RSI = 62 - Test bit 62
bt rdi, rsiBit 62 = 1 CF = 1 - Copy Carry Flag
setc alAL = 1 - Zero extension
movzx eax, alEAX = 0x00000001 RAX = 0x0000000000000001 - Return
retRust receives: 1
Result: true
10. Debug it with GDB
Since you’re learning assembly through CLI tools, I recommend stepping through the actual instructions.
First, compile:
cargo buildFind the symbol:
nm -n target/debug/z99_asm_ffi_bits_check | grep check_bitDisassemble:
objdump -d -Mintel target/debug/z99_asm_ffi_bits_check \
| grep -A 10 '<check_bit>:'Start GDB:
gdb target/debug/z99_asm_ffi_bits_checkSet a breakpoint:
(gdb) break check_bit
(gdb) runInspect registers:
(gdb) info registers rdi rsi rax eflagsStep through the instructions:
(gdb) si
(gdb) si
(gdb) si
(gdb) siInspect the Carry Flag:
(gdb) p $eflags & 1If the result is 1, the Carry Flag is set.
Inspect the return value:
(gdb) p/x $raxExpected:
$1 = 0x111. Important performance optimization
Your current function:
bt rdi, rsi
setc al
movzx eax, al
retUses four instructions.
However, because check_all_bits() calls this function 64 times per value, you have 64 FFI calls and 64 function returns.
For your Rust program, a more efficient approach would be to process all 64 bits inside one assembly function.
For example, an alternative x86-64 implementation could use:
.intel_syntax noprefix
.text
.global check_all_bits
check_all_bits:
# RDI = input u64
# RSI = output bool array pointer
xor ecx, ecx
.loop:
bt rdi, 63
setc al
mov byte ptr [rsi + rcx], al
shl rdi, 1
inc ecx
cmp ecx, 64
jne .loop
ret
.section .note.GNU-stack,"",@progbitsThis processes all 64 bits in one assembly function call.
It writes the results directly into the output memory.
Note that this version uses one byte per Boolean, matching the element size of Rust’s [bool; 64]. It also shifts the input left each iteration, so it visits bit 63 first.
For maximum performance, the loop can be further optimized or unrolled, and the output representation can be changed to a bitmask if individual Boolean bytes are not required.
My recommendation: First understand and debug the four-instruction check_bit function. Then compare it against the single-call check_all_bits implementation using objdump and GDB. That is a useful exercise in understanding FFI overhead, calling conventions, register allocation, and instruction-level optimization.
Arm분석
- Let’s examine your ARM64 (AArch64) assembly code line by line, including the GNU assembler (GAS) syntax, C preprocessor directives, ARM64 registers, calling convention, bitwise instructions, and how Rust calls the assembly function through FFI.
Your assembly function implements this Rust declaration:
unsafe extern "C" {
fn check_bit(value: u64, index: u64) -> u64;
}Its purpose is to check one bit in a 64-bit integer and return 0 or 1.
1. Complete assembly code
#ifdef __APPLE__
#define SYMBOL(name) _##name
#else
#define SYMBOL(name) name
#endif
.text
.globl SYMBOL(check_bit)
#ifndef __APPLE__
.type check_bit, %function
#endif
SYMBOL(check_bit):
// X0 = value
// X1 = bit index (0..63)
// Return X0 = 0 or 1
lsr x0, x0, x1
and x0, x0, #1
ret
#ifndef __APPLE__
.size check_bit, .-check_bit
.section .note.GNU-stack,"",%progbits
#endif2. C preprocessor directives
Your file uses the .S extension (uppercase S).
This means GCC runs the C preprocessor before passing the source code to the assembler.
These directives are not ARM CPU instructions.
2.1 Detect macOS
#ifdef __APPLE__
#define SYMBOL(name) _##name
#else
#define SYMBOL(name) name
#endifThis handles symbol naming differences between macOS and Linux.
On macOS:
SYMBOL(check_bit)Expands to:
_check_bitOn Linux:
SYMBOL(check_bit)Expands to:
check_bitWhy?
Mach-O, the macOS object-file format, conventionally prefixes externally visible C symbols with an underscore.
ELF, used by Linux, normally does not.
2.2 Understanding ##
#define SYMBOL(name) _##nameThe ## operator is called the token-pasting operator.
It joins two tokens into one.
For example:
_##check_bitProduces:
_check_bitThis lets you maintain one assembly implementation for both operating systems.
3. Section and symbol declarations
3.1 .text
.textThis tells the assembler that subsequent instructions belong to the executable code section.
Typical ELF sections include:
| Section | Purpose |
|---|---|
.text | Executable instructions |
.rodata | Read-only data |
.data | Initialized global data |
.bss | Zero-initialized data |
.symtab | Symbol table |
.strtab | Symbol names |
Your check_bit function belongs in .text.
3.2 .globl
.globl SYMBOL(check_bit)This exports the function symbol so that other object files can reference it.
For example, Rust declares:
unsafe extern "C" {
fn check_bit(value: u64, index: u64) -> u64;
}The linker must be able to find the corresponding assembly symbol.
On Linux:
.globl check_bitOn macOS:
.globl _check_bitWithout global visibility, the linker may not be able to resolve Rust’s external function reference.
3.3 .type
#ifndef __APPLE__
.type check_bit, %function
#endifThis tells the ELF assembler that check_bit is a function symbol.
Syntax:
.type symbol_name, %functionThe %function attribute identifies the symbol as executable function code.
You can inspect this information with:
readelf -s target/debug/z99_asm_ffi_bits_checkOr:
nm -n target/debug/z99_asm_ffi_bits_checkThis directive is excluded on macOS because Mach-O uses different symbol metadata.
4. Function entry point
SYMBOL(check_bit):This defines the function’s entry label.
When Rust executes:
let result = unsafe {
check_bit(value, index)
};The linker resolves the function reference to this assembly symbol.
The CPU begins executing instructions at this address.
5. ARM64 calling convention
This is especially important when combining Rust and assembly.
Your Rust function declaration is:
unsafe extern "C" {
fn check_bit(value: u64, index: u64) -> u64;
}On Linux AArch64, the standard procedure-call convention (AAPCS64) specifies:
X0–X7: first eight integer or pointer arguments.X0–X1: integer return values.X19–X28: callee-saved general-purpose registers.X30: link register (LR), containing the return address.
ARM64 function registers
X0
First argument
value: u64
Also return register
X1
Second argument
index: u64
X30 (LR)
Link register
Stores the return address when called with BL.
For example, Rust calls:
check_bit(0x4000000000000000, 62);When assembly starts:
X0 = 0x4000000000000000
X1 = 62The assembly must return the result in X0.
6. Understanding the actual ARM64 instructions
This is the most important part of your program.
6.1 lsr x0, x0, x1
lsr x0, x0, x1LSR means Logical Shift Right.
Syntax:
lsr destination, source, shift_amountIt shifts the bits of the source register to the right.
The vacant bits on the left are filled with zeros.
For example:
Before:
X0 = 0100 0000 0000 0000 ...
X1 = 62The instruction:
lsr x0, x0, x1Shifts the value right by 62 positions.
Conceptually:
Original:
0100 0000 0000 0000 0000 0000 ...
^
After shifting right by 62:
0000 0000 0000 0000 0000 0000 ... 0001
^The selected bit moves into bit position zero.
The original value is replaced because X0 is both the source and destination.
Important: LSR does not preserve the original value in X0.
Why does it work?
Suppose:
value = 0b0100
index = 2Shift right by two:
0100 >> 2 = 0001The original bit 2 is now in position 0.
This is exactly what we need.
6.2 and x0, x0, #1
and x0, x0, #1AND performs a bitwise AND operation.
Syntax:
and destination, source, immediateHere:
#1 = 0000 ... 0001The instruction keeps only the lowest bit.
For example, after LSR:
X0 = 0000 ... 0001Then:
and x0, x0, #1Produces:
X0 = 0000 ... 0001If the selected bit was zero:
X0 = 0000 ... 0000The result is always:
X0 = 0or:
X0 = 1This is a very useful technique for extracting an individual bit.
6.3 ret
retRET returns execution to the caller.
In AArch64, the default behavior is equivalent to:
ret x30X30 is the link register.
When the caller executes:
bl check_bitThe BL instruction:
- Stores the return address in
X30. - Branches to
check_bit.
Then RET:
- Reads the return address from
X30. - Transfers execution back to the caller.
Your result remains in X0.
7. Complete execution walkthrough
Let’s trace the instructions with:
let value = 1u64 << 62;
let index = 62;
let result = unsafe {
check_bit(value, index)
};The input value is:
Hexadecimal:
0x4000000000000000
Binary:
0100000000000000000000000000000000000000000000000000000000000000ARM64 CPU execution trace
- Function entry
check_bit:X0 = 0x4000000000000000 X1 = 62 - Logical shift right
lsr x0, x0, x1X0 = 0x0000000000000001 - Bitwise AND
and x0, x0, #1X0 = 0x0000000000000001 - Return
retRust receives: 1
Result: true
8. ELF metadata and stack protection
8.1 .size
#ifndef __APPLE__
.size check_bit, .-check_bit
#endifThis tells the assembler the size of the function.
The expression:
.-check_bitMeans:
Current address - check_bit addressFor example:
check_bit:
lsr x0, x0, x1
and x0, x0, #1
retEach AArch64 instruction is 4 bytes.
Therefore:
Function size = 3 × 4 = 12 bytesThe .size directive records that information in the ELF symbol table.
8.2 .note.GNU-stack
.section .note.GNU-stack,"",%progbitsThis is ELF-specific metadata.
It indicates that the assembly object does not require an executable stack.
It helps prevent linker warnings such as:
warning: missing .note.GNU-stack section implies executable stackIt is excluded on macOS because Mach-O does not use this ELF section.
9. Debug using GDB on ARM64
Since you’re working with assembly and Rust FFI, I recommend inspecting the real CPU registers.
First compile:
cargo buildDisassemble:
aarch64-linux-gnu-objdump \
-d target/debug/z99_asm_ffi_bits_checkIf you’re running natively on ARM64 Linux:
objdump -d target/debug/z99_asm_ffi_bits_checkStart GDB:
gdb target/debug/z99_asm_ffi_bits_checkSet a breakpoint:
(gdb) break check_bit
(gdb) runInspect registers:
(gdb) info registers x0 x1 x30 pcStep through one instruction:
(gdb) siInspect X0:
(gdb) p/x $x0Inspect X1:
(gdb) p/x $x1You should see the input value and bit index before executing LSR.
After stepping through both bitwise instructions:
(gdb) p/x $x0Expected:
$1 = 0x110. Performance optimization: extracting all 64 bits
Your current implementation checks one bit per assembly call.
If you want to check all 64 bits, you can move the loop into assembly.
For example:
AArch64 implementation:
.text
.global check_all_bits
#ifndef __APPLE__
.type check_all_bits, %function
#endif
check_all_bits:
// X0 = input value
// X1 = output array pointer
// X2 = loop counter
mov x2, #0
.loop:
lsr x3, x0, #63
strb w3, [x1, x2]
lsl x0, x0, #1
add x2, x2, #1
cmp x2, #64
b.ne .loop
ret
#ifndef __APPLE__
.size check_all_bits, .-check_all_bits
.section .note.GNU-stack,"",%progbits
#endifCorresponding Rust declaration:
unsafe extern "C" {
fn check_all_bits(value: u64, output: *mut bool);
}
fn check_all(value: u64) -> [bool; 64] {
let mut bits = [false; 64];
unsafe {
check_all_bits(value, bits.as_mut_ptr());
}
bits
}This version:
- Makes only one FFI call.
- Processes all 64 bits inside assembly.
- Writes one byte per Boolean.
- Avoids Rust-side iteration.
Note that this is an educational implementation, not necessarily the fastest possible implementation. It uses a loop and shifts the input on each iteration.
11. x86-64 vs ARM64 comparison|🔝|
Here is the key difference between your two assembly implementations.
| Operation | x86-64 | ARM64 |
|---|---|---|
| First argument | RDI | X0 |
| Second argument | RSI | X1 |
| Return value | RAX | X0 |
| Test/extract bit | BT | LSR + AND |
| Conditional result | SETC | Not needed |
| Zero extension | MOVZX | Not needed |
| Return | RET | RET |
x86-64
bt rdi, rsi
setc al
movzx eax, al
retARM64
lsr x0, x0, x1
and x0, x0, #1
retThe main architectural difference:
- x86-64 has a dedicated
BTinstruction that transfers the selected bit into the Carry Flag. - ARM64 extracts the bit using a logical shift and a bitwise AND.
Both produce exactly the same logical result.
One final detail: Your function assumes index is between 0 and 63. For this range, the implementation is correct. If you want to support arbitrary u64 indices, you should explicitly validate the index in Rust before calling assembly.
For your next learning step, I recommend comparing LSR + AND against ARM64’s UBFX instruction. UBFX can extract the selected bit directly:
ubfx x0, x0, x1, #1
retThat is a particularly interesting comparison with x86-64’s BT instruction.
64-bit 패턴
- 네, 가능합니다! 다만 63-bit를 어떤 의미로 사용하는지 구분해야 합니다.
예를 들어 다음 비트 패턴을 말하는 거라면:
0100 0000 0000 0000 0000 0000 0000가장 왼쪽의 1이 63번째 비트(0부터 세면 bit 62인지, 1부터 세면 63번째인지)에 해당하는지 확인해야 합니다.
1. 63번째 비트가 설정되어 있는지 검사하기
Rust에서는 u64를 사용하면 됩니다.
fn main() {
let value: u64 = 1 << 62;
let is_63bit = value & (1 << 62) != 0;
let mut bits: Vec<bool> = Vec::new();
bits.push(is_63bit);
println!("Value: {:#066b}", value);
println!("63rd bit: {}", is_63bit);
println!("Vector: {:?}", bits);
}Output:
Value: 0b0100000000000000000000000000000000000000000000000000000000000000
63rd bit: true
Vector: [true]2. 여러 비트를 Vector에 저장하기
fn main() {
let value: u64 = 1 << 62;
let bits: Vec<bool> = (0..64)
.rev()
.map(|i| (value & (1 << i)) != 0)
.collect();
println!("{:?}", bits);
}이 코드는 64비트를 각각 bool로 변환하여 Vec<bool>에 저장합니다.
핵심 차이
| 검사 | 의미 |
|---|---|
usize::BITS == 64 | 시스템의 포인터 너비가 64-bit |
value & (1 << 62) != 0 | 63번째 비트가 1인지 검사 |
value.count_ones() | 값에서 1로 설정된 비트의 개수 |
주의: 여기서 63-bit는 CPU가 63-bit 아키텍처라는 뜻이 아니라, u64 값의 63번째 비트가 설정되어 있다는 뜻입니다.