🗂

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