GCC のインラインアセンブラのプログラムの部分は文字列リテラルであるため、プリプロセッサを使ってトークンを埋め込むのは難しい話ではありません。

#define ITEMTYPE "byte"
...
    asm(R"(
        .intel_syntax noprefix
        movzx eax, )" ITEMTYPE R"( ptr [rax]
    )")

一方、C++ テンプレートプログラミングをする場合、こういった置き換えはプリプロセッサによらない方法でやりたいところですが、やり方が分からなかったので UtVideo ではプリプロセッサでやっています(呼び出し側実装側)。

で、昨日久しぶりに調べていたら、とりあえず x86/x86-64 でできるダーティーハックを見つけました。

$ cat aaa.cc
static constexpr char byte_[0] asm("byte") = {};

void foo()
{
    asm(R"(
        .intel_syntax noprefix
        movzx eax, %p[aaa] ptr [rax]
    )":: [aaa]"i"(byte_));
}
$ gcc -S -o - -std=c++11 -masm=intel aaa.cc
        .file   "aaa.cc"
        .intel_syntax noprefix
        .text
        .section        .rodata
        .type   byte, @object
        .size   byte, 0
byte:
        .text
        .globl  _Z3foov
        .type   _Z3foov, @function
_Z3foov:
.LFB0:
        .cfi_startproc
        endbr64
        push    rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        mov     rbp, rsp
        .cfi_def_cfa_register 6
#APP
# 5 "aaa.cc" 1

        .intel_syntax noprefix
        movzx rax, byte ptr [rax]

# 0 "" 2
#NO_APP
        nop
        pop     rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   _Z3foov, .-_Z3foov
        .ident  "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
        .section        .note.GNU-stack,"",@progbits
        .section        .note.gnu.property,"a"
        .align 8
        .long   1f - 0f
        .long   4f - 1f
        .long   5
0:
        .string "GNU"
1:
        .align 8
        .long   0xc0000002
        .long   3f - 2f
2:
        .long   0x3
3:
        .align 8
4:

byte_ という名前の変数の後ろについている asm はアセンブラレベルでのシンボル名を指定する記法で、これが埋め込みたい文字列になります(C での変数名は何でもいい)。この変数(の先頭アドレスの値)を "i" constraint で input operand として渡し、生のシンボル名を出力するための p modifier を付けて参照させると、オペランドとして指定した値に対応するシンボル名がそこに埋め込まれます。

これを利用すると以下のようなテンプレートプログラミングができます。

$ cat bbb.cc
#include <stdint.h>

static constexpr char byte_[0] asm("byte") = {};
static constexpr char word_[0] asm("word") = {};

template<typename T>
void bar()
{
        static_assert(sizeof(T) == 1 || sizeof(T) == 2);

        asm (R"(
                .intel_syntax noprefix
                movzx rax, %p[aaa] ptr [rax]
        )"::[aaa]"i"((sizeof(T) == 1) ? byte_ : word_));
}

template void bar<uint8_t>();
template void bar<uint16_t>();
$ gcc -c -o bbb.o -std=c++11 -masm=intel bbb.cc
$ objdump -d -Mintel bbb.o

bbb.o:     file format elf64-x86-64


Disassembly of section .text._Z3barIhEvv:

0000000000000000 <_Z3barIhEvv>:
   0:   f3 0f 1e fa             endbr64
   4:   55                      push   rbp
   5:   48 89 e5                mov    rbp,rsp
   8:   48 0f b6 00             movzx  rax,BYTE PTR [rax]
   c:   90                      nop
   d:   5d                      pop    rbp
   e:   c3                      ret

Disassembly of section .text._Z3barItEvv:

0000000000000000 <_Z3barItEvv>:
   0:   f3 0f 1e fa             endbr64
   4:   55                      push   rbp
   5:   48 89 e5                mov    rbp,rsp
   8:   48 0f b7 00             movzx  rax,WORD PTR [rax]
   c:   90                      nop
   d:   5d                      pop    rbp
   e:   c3                      ret

シンボルを埋め込むための constexpr 変数そのものは何でもいいので、サイズがゼロの配列にします。こうするとシンボルはオブジェクトファイルに出力されますがデータは何も出力されないので、最終的なプログラムが大きくなることはありません。

あと、constexpr 変数が static とはいえグローバルスコープを持つのが嫌だからといってローカル変数にした場合、asm で指定した名前にプレフィックスやサフィックスが付いてしまうのでダメです。GCC と Clang でここの挙動がちょっと違いますが、なんか付いちゃってダメなのは同じです。

p modifier はどうやら x86/x86-64 にしかないため、他のアーキテクチャで同じことはたぶんできません。汎用性のある利用法があるのなら他のアーキテクチャにもあって良さそうなのですがそういうことはなく、ではどういう利用法が想定されてるんですかね…?

参考文献: Is it possible to template the asm opcode in c++? – Stack Overflow

Trackback

no comment untill now

Add your comment now