了解 NASM 宏

Understanding NASM Macro

我在汇编源文件中遇到过这个宏,但我无法弄清楚它是如何工作的。

所以我先绕过这个函数 (hevc_deblock.h):

cglobal hevc_v_loop_filter_chroma_8, 3, 5, 7, pix, stride, tc, pix0, r3stride
    sub            pixq, 2
    lea       r3strideq, [3*strideq]
    mov           pix0q, pixq
    add            pixq, r3strideq
    TRANSPOSE4x8B_LOAD  PASS8ROWS(pix0q, pixq, strideq, r3strideq)
    CHROMA_DEBLOCK_BODY 8
    TRANSPOSE8x4B_STORE PASS8ROWS(pix0q, pixq, strideq, r3strideq)
    RET

所以我假设 cglobal 似乎做了一些名称修改,所以我在其他包含的文件中查找它我发现 cglobal 宏中的那个宏 (x86util.asm) :

%macro CAT_UNDEF 2
    %undef %1%2
%endmacro

%macro DEFINE_ARGS 0-*
    %ifdef n_arg_names
        %assign %%i 0
        %rep n_arg_names
            CAT_UNDEF arg_name %+ %%i, q
            CAT_UNDEF arg_name %+ %%i, d
            CAT_UNDEF arg_name %+ %%i, w
            CAT_UNDEF arg_name %+ %%i, h
            CAT_UNDEF arg_name %+ %%i, b
            CAT_UNDEF arg_name %+ %%i, m
            CAT_UNDEF arg_name %+ %%i, mp
            CAT_UNDEF arg_name, %%i
            %assign %%i %%i+1
        %endrep
    %endif

    %xdefine %%stack_offset stack_offset
    %undef stack_offset ; so that the current value of stack_offset doesn't get baked in by xdefine
    %assign %%i 0
    %rep %0
        %xdefine %1q r %+ %%i %+ q
        %xdefine %1d r %+ %%i %+ d
        %xdefine %1w r %+ %%i %+ w
        %xdefine %1h r %+ %%i %+ h
        %xdefine %1b r %+ %%i %+ b
        %xdefine %1m r %+ %%i %+ m
        %xdefine %1mp r %+ %%i %+ mp
        CAT_XDEFINE arg_name, %%i, %1
        %assign %%i %%i+1
        %rotate 1
    %endrep
    %xdefine stack_offset %%stack_offset
    %assign n_arg_names %0
%endmacro

它似乎进行了名称修改并在参数末尾添加了 q。但是,我不明白为什么有几行%undef指令,函数中似乎只使用了带有q后缀的变量名。它似乎也在末尾附加了一个数字,但出于某种原因我没有在另一个 asm 文件中看到它。

我在这里错过了什么?

DEFINE_ARGS 宏定义了一些单行宏,这些宏用于引用 cglobal 宏引入的函数的参数。因此,例如,如果 foo 作为第一个参数的名称给出,则 DEFINE_ARGS 会创建以下定义:

%xdefine fooq r0q
%xdefine food r0d
%xdefine foow r0w
%xdefine fooh r0h
%xdefine foob r0b
%xdefine foom r0m
%xdefine foomp r0mp

后缀表示应如何访问参数。前五个qdwh,b后缀表示大小:指针(四字或双字)、双字分别是字、字、字节和字节。 h 后缀表示字节是 16 位值的高位部分。 m 后缀将参数作为未指定大小的内存操作数进行访问,而 mp 后缀将其作为指针大小的内存操作数进行访问。

这些参数宏定义的r<i>Nx</i> 名称本身就是宏。它们扩展到 mmp 后缀的寄存器或内存位置,其中存储了第 N 个参数。因此,在构建 64 位 Windows 时,第一个参数的宏有效地是:

%define r0q rcx
%define r0d ecx
%define r0w cx
%define r0h ch
%define r0b cl
%define r0m ecx
%define r0mp rcx

请注意,由于 Windows 64 位调用约定在寄存器 (RCX) 中传递第一个参数,因此没有与该参数对应的内存位置。

为 32 位目标构建时,第一个参数 r<i>Nx</i> 宏最终定义如下:

%define r0q eax
%define r0d eax
%define r0w ax
%define r0h ah
%define r0b al
%define r0m [esp + stack_size + 4]
%define r0mp dword [esp + stack_size + 4]

本例中的 r0q 宏仅访问 32 位寄存器,因为 64 位寄存器在 32 位代码中不可访问。当遵循 32 位调用约定时,第一个参数在堆栈上传递,由 cglobal 宏生成的序言代码将第一个参数加载到 EAX 中。

显然,您看到的使用这些参数宏的代码仅访问指针大小的参数,因此这就是为什么您只看到 q 后缀的原因。

宏 DEFINE_ARGS 开头的 %undef 行的目的是取消定义先前调用 DEFINES_ARGS 定义的参数宏。否则它们将在当前函数中保持定义。前一个函数的参数名称存储在名为 arg_nameN.

的一行宏中

请不要按照您正在阅读的代码设置的示例进行操作。他们本质上创造了一种衍生的和独特的编程语言,只有宏的作者才能真正理解这种语言。这也不是最有效的做事方式。如果我正在编写这段代码,我会使用 C/C++ 及其向量内在函数。这会将 32 位和 64 位、Windows 和 Linux 之间的所有差异留给编译器,编译器可以生成比这些宏更好的代码。