【问题标题】:Assembling some code out-of-line with nasm macros用 nasm 宏组装一些代码
【发布时间】:2018-05-25 03:02:24
【问题描述】:

考虑一个 nasm 宏,用于在调用时注入一些程序集,在这种情况下测试传递的参数是否等于 42:

%macro special_handler_if_42 1
    cmp  42, %1
    jne  %%skip
    ; some additional assembly to handle the %1 == 42 case
    push %1
    push 42
    call some_func
%%skip:
%endmacro

在它相等的情况下,我们执行一些额外的操作,否则我们就继续执行宏后面的代码。到目前为止,一切顺利。

现在我想以一种功能相同的方式编写宏,除了碰巧非常罕见的“等于 42”的情况被移出“不符合规定”,因此跌落(没有跳转) case 是默认的,类似于(现在未以宏形式显示):

    cmp  42, rax
    je  equals_42
jump_back:
    ; the rest of the code that follows the macro
    ret

    ; somewhere outside the current function
equals_42:
    push rax
    push 42
    call some_func
    jmp jump_back

这将在执行时更有效,并且还可能节省 i-cache 空间。我不确定如何编写具有这样的非本地效果的宏。欢迎提出想法。

【问题讨论】:

  • 链接器部分?但是,对于内联部分,这意味着短函数底部的 6 字节 jcc rel32 而不是 2 字节 jcc rel8。外线部分可能必须非常罕见才能比函数的底部更好。 (通常在同一页面/TLB 条目中)。但也许更好地打包使用它的其他函数意味着在您的其余代码中更多的 TLB 和 I-cache 命中。
  • 是的,我考虑过说.text.unlikely。在我的用例中,不太可能的情况是“从不”或多或少地使用(它表示断言失败并终止程序)。所以这种情况下的性能权重为零(但jcc 的 2 个额外字节可能也会损害正常情况)。
  • 你(作为断言案例)难道不能做cmove eax,[42](可能[ss:42]有不同的故障代码),并处理抛出异常的失败吗? (我从未编写过异常处理程序,但我想有一种方法可以找出哪个地址失败,所以你可以在那里捕捉“42”,eax 是 eax .. 也许你可以捕捉到指令代码,应该是足以在 90% 的情况下将其与真正的失败区分开来)......哦,这是 8B 长的操作码...... :-o
  • @Ped7g: cmov 无条件加载内存源操作数。您不能使用它来获得其他微指令的谓词执行;它只是一个简单的 ALU uop,需要在执行之前准备好所有 3 个输入。 (标志 + 2 个整数)。如果您只是想出错,您可以在文件末尾使用一次org 0xdeadbeef / bad_condition:,然后在宏中使用jcc。 (或者可以确保它可以通过 64 位代码中的 rel32 访问。)
  • NASM 支持上下文和上下文标签。我有一个原型,它使用assert_not_equal 宏在测试站点使用,assertions_handler 在函数之后使用。支持多个assert_xxx 宏。但是我仍在研究如何将assert_xxx 宏参数传递给assertions_handler。和现在一样,我对它们进行了硬编码

标签: performance assembly optimization x86 nasm


【解决方案1】:

如果您不介意将宏分成两个宏,一个执行测试,一个处理断言,那么您可以使用NASM's context stack

我设想了一个assert_XXX 形式的宏系统,它们都非常相似并执行特定的测试。
函数末尾的单个assertions_handler 将生成所需的任何处理程序。

由于该系统使用上下文堆栈,您应该能够多次将其用于不同的功能。
基本上,每个assert_XXX 函数都会在堆栈上推送一个上下文,assertions_handler 将消耗它们。

assert_XXX 还将定义上下文本地宏 argX 以将其参数传递给处理程序,因此无需硬编码任何内容。

BITS 64

%macro assert_not_equal 2
    ;Create and push a new context (The name is optional but goodpractice)
    %push assert_equal_ctx

    %define %$arg1 %1
    %define %$arg2 %2

    cmp  %1, %2
    je  %$handler

%$jump_back:

%endmacro


%macro assert_greater 2
    %push assert_greater_ctx

    %define %$arg1 %1
    %define %$arg2 %2

    cmp  %1, %2
    jbe  %$handler

%$jump_back:

%endmacro

%macro assertions_handler 0

    %rep 1000

        %ifctx assert_equal_ctx

        %$handler:
            push %$arg1
            push %$arg2
            call somefunc
            jmp %$jump_back

            %pop assert_equal_ctx

        %elifctx assert_greater_ctx

        %$handler:
            push %$arg1
            push %$arg2
            call somefunc2

            %pop assert_greater_ctx

        %else

            %exitrep

        %endif
    %endrep
%endmacro


;
;TEST TEST TEST TEST TEST TEST TEST TEST
; 


assert_not_equal rax, 5

nop
nop
nop

assert_greater rax, 8

nop
nop
nop

ret

assertions_handler

;
; Handler functions
;

somefunc:
    ret

somefunc2:
    ret

每个函数的最大断言数设置为 1000,您可以将其增加到 262

【讨论】:

  • 谢谢,这看起来很棒!您猜对了,我的用例是通用断言宏。将每个断言分成两个是不行的,但是每个文件有一个 assertion_handler 就可以了(据我了解,没有限制将其限制为每个函数 1 个 - 您可以选择何时放置 assertions_handler 和如果我理解正确,它将“刷新”所有堆积的断言?)。我查看了上下文堆栈的内容,但没有看到适用性 - 并且找不到“保存内容以备后用”的方法。我想这确实主要排除了在用户 ... 的情况下使用
  • ... 已经在将上下文堆栈用于其他用途,除非这些用途对于断言内容是“嵌套良好”的(例如,如果在用户推送的一些断言和然后完全弹出上下文 - 但如果用户推送一个上下文并且在assertion_handler 之前没有弹出它是不行的,因为断言处理程序将无法处理所有累积的断言(在%else 情况下),对吧?
  • @BeeOnRope,是的,这基本上是使用堆栈上下文的缺点。 NASM 允许通过在 %$ 运算符(例如 %$$$$)中使用更多 $ 来任意深入地查看上下文堆栈,但如果这可以通用/编程方式完成,我现在不这样做
  • 如果拆分宏不可行,我确信我理解代码的外观。
  • @BeeOnRope 当然,无论是否注明出处 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-14
  • 2016-02-10
  • 1970-01-01
相关资源
最近更新 更多