🗂
C言語でif文を使わずにビット演算で条件分岐を行う際のアセンブリ言語の動き
サマリ
C言語でif文を書くときにbit演算で実現ができ、結果が同一であることを確認し、アセンブリレベルでの比較を行いました。
マシンスペック
MacBook Air M2 arm64
通常の記述
/* select_if.c : 分岐あり実装 */
#include <stdint.h>
#include <stdio.h>
__attribute__((noinline))
int32_t select_if(int32_t a, int32_t b, int cond)
{
if (cond)
return a;
else
return b;
}
int main(void)
{
volatile int32_t x = 42, y = -7;
volatile int c = 1; // 試しに真
int32_t z = select_if(x, y, c);
printf("z = %d\n", z);
return 0;
}
まずは結果を確認します。
clang -O3 -arch arm64 norm.c -o norm
./norm
z = 42
次にアセンブリ言語を確認します。
clang -O3 -arch arm64 -S -o norm.s norm.c
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 14, 0 sdk_version 14, 4
.globl _select_if ; -- Begin function select_if
.p2align 2
_select_if: ; @select_if
.cfi_startproc
; %bb.0:
cmp w2, #0
csel w0, w1, w0, eq
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #48
.cfi_def_cfa_offset 48
stp x29, x30, [sp, #32] ; 16-byte Folded Spill
add x29, sp, #32
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
mov w8, #42
stur w8, [x29, #-4]
mov w8, #-7
stur w8, [x29, #-8]
mov w8, #1
stur w8, [x29, #-12]
ldur w0, [x29, #-4]
ldur w1, [x29, #-8]
ldur w2, [x29, #-12]
bl _select_if
; kill: def $w0 killed $w0 def $x0
str x0, [sp]
Lloh0:
adrp x0, l_.str@PAGE
Lloh1:
add x0, x0, l_.str@PAGEOFF
bl _printf
mov w0, #0
ldp x29, x30, [sp, #32] ; 16-byte Folded Reload
add sp, sp, #48
ret
.loh AdrpAdd Lloh0, Lloh1
.cfi_endproc
; -- End function
.section __TEXT,__cstring,cstring_literals
l_.str: ; @.str
.asciz "z = %d\n"
.subsections_via_symbols
ビット演算
/* select_mask.c : ビット演算(ブランチレス) */
#include <stdint.h>
#include <stdio.h>
__attribute__((noinline))
int32_t select_mask(int32_t a, int32_t b, int cond)
{
uint32_t mask = -(uint32_t)cond; /* cond=1 → 0xFFFFFFFF, cond=0 → 0 */
return (a & mask) | (b & ~mask); /* 真なら a, 偽なら b */
}
int main(void)
{
volatile int32_t x = 42, y = -7;
volatile int c = 1; // 試しに真
int32_t z = select_mask(x, y, c);
printf("z = %d\n", z);
return 0;
}
同様に結果を確認します。
clang -O3 -arch arm64 mask.c -o mask
./mask
z = 42
次にアセンブリ言語を確認します。
clang -O3 -arch arm64 -S -o mask.s mask.c
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 14, 0 sdk_version 14, 4
.globl _select_mask ; -- Begin function select_mask
.p2align 2
_select_mask: ; @select_mask
.cfi_startproc
; %bb.0:
neg w8, w2
and w8, w8, w0
sub w9, w2, #1
and w9, w9, w1
orr w0, w9, w8
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #48
.cfi_def_cfa_offset 48
stp x29, x30, [sp, #32] ; 16-byte Folded Spill
add x29, sp, #32
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
mov w8, #42
stur w8, [x29, #-4]
mov w8, #-7
stur w8, [x29, #-8]
mov w8, #1
stur w8, [x29, #-12]
ldur w0, [x29, #-4]
ldur w1, [x29, #-8]
ldur w2, [x29, #-12]
bl _select_mask
; kill: def $w0 killed $w0 def $x0
str x0, [sp]
Lloh0:
adrp x0, l_.str@PAGE
Lloh1:
add x0, x0, l_.str@PAGEOFF
bl _printf
mov w0, #0
ldp x29, x30, [sp, #32] ; 16-byte Folded Reload
add sp, sp, #48
ret
.loh AdrpAdd Lloh0, Lloh1
.cfi_endproc
; -- End function
.section __TEXT,__cstring,cstring_literals
l_.str: ; @.str
.asciz "z = %d\n"
.subsections_via_symbols
差分を確認
それぞれのコードをアセンブリ言語で比較します。
3c3
< .globl _select_if ; -- Begin function select_if
---
> .globl _select_mask ; -- Begin function select_mask
5c5
< _select_if: ; @select_if
---
> _select_mask: ; @select_mask
8,9c8,12
< cmp w2, #0
< csel w0, w1, w0, eq
---
> neg w8, w2
> and w8, w8, w0
> sub w9, w2, #1
> and w9, w9, w1
> orr w0, w9, w8
34c37
< bl _select_if
---
> bl _select_mask
整理すると
cmp w2, #0 ; cond の比較(w2 = 0/1)
csel w0, w1, w0, eq ; EQ(=true) なら w1→w0, そうでなければ w0→w0
neg w8, w2 ; 真 → 0xFFFFFFFF, 偽 → 0x00000000
and w8, w8, w0 ; (mask & a)
sub w9, w2, #1 ; 真 → 0, 偽 → 0xFFFFFFFF
and w9, w9, w1 ; (~mask & b) ※ここでは逆側のマスクを生成
orr w0, w9, w8 ; 2 つを OR して結果
Discussion