BeeOnRope
BeeOnRope

Reputation: 65006

Assembling some code out-of-line with nasm macros

Consider a nasm macro that is used to inject some assembly whenever it is called, in this case to test whether the passed argument is equal to 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

In the case it is equal we perform some additional action, otherwise we just continue with the code following the macro. So far, so good.

Now I want to write the macro in a way that's functionally identical, except that the "equals 42" case, which happens to be very rare, is moved "out of line", so that the fall-through (no jump) case is the default one, something like (not shown in macro form now):

    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

This will be more efficient at execution time and also potentially conserve i-cache space. I'm not sure how to write a macro with a non-local effect like that. Ideas welcome.

Upvotes: 4

Views: 720

Answers (1)

Margaret Bloom
Margaret Bloom

Reputation: 44116

If you don't mind splitting the macro into two macros, one performing the test and one handling the assertion, then you can use NASM's context stack.

I imagined a system of macros of the form assert_XXX that are all pretty similar and perform specific tests.
A single assertions_handler past the end of the function will generate any handler needed.

Since this system uses the context stack, you should be able to use it multiple times for different functions.
Basically, each assert_XXX function will push a context on the stack and the assertions_handler will consume them all.

assert_XXX will also define context local macros argX to pass its argument to the handler, so there is no need to hardcode anything.

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

The maximum number of assertions per function is set to 1000, you can increment it up to 262.

Upvotes: 3

Related Questions