4

I was thinking about functional programming techniques, recursions, and constness, and linked lists, so I made two experiments to test my compiler. In theory the compiler should be capable of optimizing away tail recursion (I now realize the functions provided here are NOT tail recursive and I apologize for trying to sneak them by as if they are, the actual tail recursive variants can be found in an answer I posted below) and produce a result at compile time without even building the structures in the first place at runtime.

It works as expected on the array variant below:

/**
 * @file test1.c
 */

static inline int my_array_sum(const int n, const int * const xs) {
    if (n == 0) {
        return n;
    } else {
        return n + my_array_sum(n - 1, xs + 1);
    }
}

int main(int argc, char **argv)
{    
    const int xs[] = {1, 2, 3, 4, 5, 6, 7, 8};
    const int n = 8;    
    const int sum = my_array_sum(n, xs); 

    return sum;
}

producing the following object code (gcc test1.c -o test1.obj -c -O3, objdump -D test1.obj):

In MinGW/MSys 64bit:

0000000000000000 <main>:
   0:   48 83 ec 28             sub    $0x28,%rsp
   4:   e8 00 00 00 00          callq  9 <main+0x9>
   9:   b8 24 00 00 00          mov    $0x24,%eax
   e:   48 83 c4 28             add    $0x28,%rsp
  12:   c3                      retq

In Linux Mint 64 bit:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   b8 24 00 00 00          mov    $0x24,%eax
   9:   c3                      retq   

Whereas the linked list (which is a dirty word, but it is not malloced, and all the info about the list is available to the compiler) version below (sentence finished after code):

/**
 * @file test2.c
 */

typedef struct Cons {
    const int x;
    const struct Cons * const next;
} Cons;

static inline int cons_sum(const Cons c) {
    if (c.next == 0) {
        return c.x;
    } else {
        return c.x + cons_sum(*(c.next));
    }
}

int main(int argc, char **argv)
{    
    // | To build the stack-local linked list, with tail s0 and head h.
    const Cons s0 = {8, 0};
    const Cons s1 = {7, &s0};
    const Cons s2 = {6, &s1};
    const Cons s3 = {5, &s2};
    const Cons s4 = {4, &s3};
    const Cons s5 = {3, &s4};
    const Cons s6 = {2, &s5};
    const Cons h = {1, &s6};    
    
    // | To produce a return value (expect 36).
    const int sum = cons_sum(h); 

    return sum;
}

produces the following object code (gcc test2.c -o test2.obj -c -O3, objdump -D test2.obj):

In MinGW/MSys 64bit:

0000000000000000 <main>:
   0:   48 81 ec 88 00 00 00    sub    $0x88,%rsp
   7:   e8 00 00 00 00          callq  c <main+0xc>
   c:   48 8d 44 24 20          lea    0x20(%rsp),%rax
  11:   48 8d 54 24 70          lea    0x70(%rsp),%rdx
  16:   b9 02 00 00 00          mov    $0x2,%ecx
  1b:   48 89 44 24 38          mov    %rax,0x38(%rsp)
  20:   48 8d 44 24 30          lea    0x30(%rsp),%rax
  25:   48 89 44 24 48          mov    %rax,0x48(%rsp)
  2a:   48 8d 44 24 40          lea    0x40(%rsp),%rax
  2f:   48 89 44 24 58          mov    %rax,0x58(%rsp)
  34:   48 8d 44 24 50          lea    0x50(%rsp),%rax
  39:   48 89 44 24 68          mov    %rax,0x68(%rsp)
  3e:   48 8d 44 24 60          lea    0x60(%rsp),%rax
  43:   c7 44 24 20 08 00 00    movl   $0x8,0x20(%rsp)
  4a:   00
  4b:   48 c7 44 24 28 00 00    movq   $0x0,0x28(%rsp)
  52:   00 00
  54:   c7 44 24 30 07 00 00    movl   $0x7,0x30(%rsp)
  5b:   00
  5c:   c7 44 24 40 06 00 00    movl   $0x6,0x40(%rsp)
  63:   00
  64:   c7 44 24 50 05 00 00    movl   $0x5,0x50(%rsp)
  6b:   00
  6c:   c7 44 24 60 04 00 00    movl   $0x4,0x60(%rsp)
  73:   00
  74:   c7 44 24 70 03 00 00    movl   $0x3,0x70(%rsp)
  7b:   00
  7c:   48 89 44 24 78          mov    %rax,0x78(%rsp)
  81:   e8 00 00 00 00          callq  86 <main+0x86>
  86:   83 c0 01                add    $0x1,%eax
  89:   48 81 c4 88 00 00 00    add    $0x88,%rsp
  90:   c3                      retq

In Linux Mint 64 bit:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   48 83 ec 78             sub    $0x78,%rsp
   8:   b9 01 00 00 00          mov    $0x1,%ecx
   d:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  14:   00 00 
  16:   48 89 44 24 68          mov    %rax,0x68(%rsp)
  1b:   31 c0                   xor    %eax,%eax
  1d:   48 89 e0                mov    %rsp,%rax
  20:   48 8d 54 24 50          lea    0x50(%rsp),%rdx
  25:   c7 04 24 08 00 00 00    movl   $0x8,(%rsp)
  2c:   48 89 44 24 18          mov    %rax,0x18(%rsp)
  31:   48 8d 44 24 10          lea    0x10(%rsp),%rax
  36:   48 89 44 24 28          mov    %rax,0x28(%rsp)
  3b:   48 8d 44 24 20          lea    0x20(%rsp),%rax
  40:   48 89 44 24 38          mov    %rax,0x38(%rsp)
  45:   48 8d 44 24 30          lea    0x30(%rsp),%rax
  4a:   48 c7 44 24 08 00 00    movq   $0x0,0x8(%rsp)
  51:   00 00 
  53:   c7 44 24 10 07 00 00    movl   $0x7,0x10(%rsp)
  5a:   00 
  5b:   c7 44 24 20 06 00 00    movl   $0x6,0x20(%rsp)
  62:   00 
  63:   c7 44 24 30 05 00 00    movl   $0x5,0x30(%rsp)
  6a:   00 
  6b:   c7 44 24 40 04 00 00    movl   $0x4,0x40(%rsp)
  72:   00 
  73:   c7 44 24 50 03 00 00    movl   $0x3,0x50(%rsp)
  7a:   00 
  7b:   48 89 44 24 48          mov    %rax,0x48(%rsp)
  80:   48 8d 44 24 40          lea    0x40(%rsp),%rax
  85:   48 89 44 24 58          mov    %rax,0x58(%rsp)
  8a:   b8 02 00 00 00          mov    $0x2,%eax
  8f:   90                      nop
  90:   89 c6                   mov    %eax,%esi
  92:   8b 02                   mov    (%rdx),%eax
  94:   48 8b 52 08             mov    0x8(%rdx),%rdx
  98:   01 f1                   add    %esi,%ecx
  9a:   48 85 d2                test   %rdx,%rdx
  9d:   75 f1                   jne    90 <main+0x90>
  9f:   01 c8                   add    %ecx,%eax
  a1:   48 8b 7c 24 68          mov    0x68(%rsp),%rdi
  a6:   64 48 33 3c 25 28 00    xor    %fs:0x28,%rdi
  ad:   00 00 
  af:   75 05                   jne    b6 <main+0xb6>
  b1:   48 83 c4 78             add    $0x78,%rsp
  b5:   c3                      retq   
  b6:   e8 00 00 00 00          callq  bb <main+0xbb>

I tried compiling the above in both C and C++, and both gave more or less the same disassembly.

Does the standard guarantee even strictly constant linked list variant (on the stack, as I can see reasons for a global linked list not to get optimized) does not get optimized to the extent it would with simple arrays, or is it just the current state of the compiler and it may get optimized in the future? Or am I forgetting some kind of a flag to enable this kind of optimization?

Jens
  • 69,818
  • 15
  • 125
  • 179
Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 5
    "_Does the standard guarantee_": The standard says nothing about this. It only says what the observable behavior of the program ought to be. Everything else is up to the compiler. – user17732522 Jul 12 '22 at 04:13
  • 3
    In C++ you would use `constexpr`/`consteval` on the variable declarations and/or function declarations in order to ask the compiler to do the calculation at compile-time. For example: https://godbolt.org/z/z1c3P33q1 – user17732522 Jul 12 '22 at 04:14
  • @user17732522 Interesting, I haven't considered looking into linked list constexpr, I am not sure if that is possible but it would be really cool if it is, and does the optimizations I expect. – Dmytro Jul 12 '22 at 04:19
  • Since C++20 it is straight-forward to implement a linked list, even allocator-aware, as `constexpr`-friendly. (`std::list` hasn't been made `constexpr`-friendly yet though) You just can't use the container at the compile-time/runtime boundary, where you have to translate to something else that doesn't rely on dynamic allocation. – user17732522 Jul 12 '22 at 04:22
  • It would be legal, but apparently the compiler's default tuning heuristics don't get it to look that far. Maybe it's not clear to it how many iterations the tail-recursion will do, and it doesn't look deep enough to see the end. (Whereas with the array, transforming tail-recursion to iteration does immediately produce a small constant loop trip count.) As @user17732522 says, forcing the issue with `consteval` can encourage the compiler to keep going until it hits a showstopper, vs. guessing whether it will be profitable. (You'd hope it would have at least peeled the first few iters...) – Peter Cordes Jul 12 '22 at 04:23
  • BTW, [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116) shows how to get cleaner asm output from compilers. Your disassembly of an object file (not executable) is why you have `e8 00 00 00 00 callq 86
    ` with a meaningless target name: note the `rel32=0` in the machine code, that's just a placeholder for the linker to fill in. And you didn't use `-r` with objdump, so there's not even a comment with the actual symbol name. (I use `alias disas="objdump -drwC -Mintel"`.)
    – Peter Cordes Jul 12 '22 at 04:26
  • I think I just managed to do it by... constexpring everything, with expected optimization result. – Dmytro Jul 12 '22 at 04:29
  • Oh, the non-MinGW build that inlined the recursive function to a loop did peel the first couple iterations and object-construction. Like clang 14 does (https://godbolt.org/z/d36TjoTrY - note it starts with `mov eax, 6` before the loop, the sum of the 1,2, and 3 elements which it doesn't construct.) That might be a sign of how far it was looking to see if it got to a termination at compile time without `consteval` or `constexpr`. – Peter Cordes Jul 12 '22 at 04:30
  • 3
    These are not tail-recursive functions, so there's no tail recursion conversion or optimization going on here. Instead the first one is just recursively inlined until it can be constant folded away, while the second is not. – Chris Dodd Jul 12 '22 at 04:56
  • This question is in a bit of an inconsistent state, I came here wanting to see if I can wrangle C into doing the desired optimization, without even thinking about constexpr, constexpr solution is all I wanted from this, but I am not sure I can just swap the question this way. The question initially had nothing to do with constexpr, I would feel dirty altering my question from the clear C focus, and whether it is possible or not to wrangle C to do it,, to something that isn't usable from C at all. Is the consensus that this has nothing to do with standard but may be implemented in the future? – Dmytro Jul 12 '22 at 05:10
  • 1
    @ChrisDodd: Note that GCC targeting Linux *did* inline the recursive function and optimize the recursion into a loop, as we can see in the `test` / `jne 90` part of the code. Despite the lack of *tail* recursion. Transforming recursion into iteration is a very useful optimization for compilers in general, because recursion sucks for small functions especially with potentially high recursion depth. – Peter Cordes Jul 12 '22 at 10:22
  • @ChrisDodd You are right, sorry for trying to sneak my functions off as tail recursive. I added the tail recursive versions as a separate answer for completeness, to avoid adding it in the main post which is already quite crammed. – Dmytro Jul 12 '22 at 11:00

2 Answers2

1

I managed to get the desireable results in C++ by constexpring everything as follows (having to use static seems a little ugly, but it works):

/**
 * @file test3.cpp
 */

typedef struct Cons {
    const int x;
    const struct Cons * const next;
} Cons;

constexpr int cons_sum(const Cons c) {
    if (c.next == 0) {
        return c.x;
    } else {
        return c.x + cons_sum(*(c.next));
    }
}

int main(int argc, char **argv)
{    
    // | To build the stack-local linked list, with tail s0 and head h.
    static constexpr const Cons s0 {8, 0};
    static constexpr const Cons s1 {7, &s0};
    static constexpr const Cons s2 {6, &s1};
    static constexpr const Cons s3 {5, &s2};
    static constexpr const Cons s4 {4, &s3};
    static constexpr const Cons s5 {3, &s4};
    static constexpr const Cons s6 {2, &s5};
    static constexpr const Cons h {1, &s6};    
    
    // | To produce a return value(expect 36).
    constexpr int sum = cons_sum(h); 

    return sum;
}

producing (g++ test3.cpp -o test3.obj -c -O3, objdump -D test3.obj):

In MinGW/MSys 64bit:

0000000000000000 <main>:
   0:   48 83 ec 28             sub    $0x28,%rsp
   4:   e8 00 00 00 00          callq  9 <main+0x9>
   9:   b8 24 00 00 00          mov    $0x24,%eax
   e:   48 83 c4 28             add    $0x28,%rsp
  12:   c3                      retq

In Linux Mint 64 bit:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   b8 24 00 00 00          mov    $0x24,%eax
   9:   c3                      retq 
Jens
  • 69,818
  • 15
  • 125
  • 179
Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 3
    Your list now isn't on the stack anymore. You gave it static storage duration. As I showed in my comment under the question you don't need this "ugly" construction. You only need to make the function either `consteval` or `constexpr` (in the latter case the return value needs to be stored to a `constexpr` variable). Unfortunately you can't declare `main` as either though, so you have to move it to another function. Also, your question is tagged `c`, so it doesn't seem like a C++-specific answer is appropriate. – user17732522 Jul 12 '22 at 04:37
  • @user17732522 it isn't but I never cared about it being on the stack in the first place, sorry for being unclear. I put it on the stack hoping it would make the compiler more likely to do this behavior. I wanted the compiler to just ignore everything and return 36. – Dmytro Jul 12 '22 at 04:38
  • @user17732522 you're right, it is a C tagged question, I wasn't sure how to tag it since I wanted the solution as either C or C++(preferably both), but the initial question was inherently C, and I didn't even consider constexpr at the time of writing the question, so I was hoping the solution would be a C solution. I am not sure how to remedy this inconsistency, I am not sure this is even a valid solution, since my question was whether it is possible to wrangle C into doing this. – Dmytro Jul 12 '22 at 04:52
1

Ignore this answer if you do not care about the tail recursion

As noted, the question initially talked about tail recursion, and I have provided regular recursion rather than tail recursion.

For completeness, the actual tail recursive functions are below.

The array version:

/**
 * @file test1_tail.c
 */

int my_array_sum_tail(const int n, const int * const xs, const int accumulator) {
    if (n == 1) {
        return accumulator + xs[n - 1];
    } else {
        return my_array_sum_tail(n - 1, xs, accumulator + xs[n - 1]);
    }
}

int my_array_sum(const int n, const int * const xs) {
    return my_array_sum_tail(n, xs, 0);
}

int main(int argc, char **argv)
{    
    const int xs[] = {1, 2, 3, 4, 5, 6, 7, 8};
    const int n = 8;    
    const int sum = my_array_sum(n, xs); 

    return sum;
}

producing the following object code (gcc test1_tail.c -o test1_tail.obj -c -O3, objdump -D test1_tail.obj):

In MinGW/MSys 64bit:

0000000000000000 <main>:
   0:   48 83 ec 28             sub    $0x28,%rsp
   4:   e8 00 00 00 00          callq  9 <main+0x9>
   9:   b8 24 00 00 00          mov    $0x24,%eax
   e:   48 83 c4 28             add    $0x28,%rsp
  12:   c3                      retq

In Linux Mint 64 bit:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   b8 24 00 00 00          mov    $0x24,%eax
   9:   c3                      retq 

The list version:

/**
 * @file test2_tail.c
 */

typedef struct Cons {
    const int x;
    const struct Cons * const next;
} Cons;

static inline int cons_sum_tail(const Cons c, const int accumulator) {
    if (c.next == 0) {
        return accumulator + c.x;
    } else {
        return cons_sum_tail(*(c.next), accumulator + c.x);
    }
}

static inline int cons_sum(const Cons c) {
    return cons_sum_tail(c, 0);
}

int main(int argc, char **argv)
{    
    // | To build the stack-local linked list, with tail s0 and head h.
    const Cons s0 = {8, 0};
    const Cons s1 = {7, &s0};
    const Cons s2 = {6, &s1};
    const Cons s3 = {5, &s2};
    const Cons s4 = {4, &s3};
    const Cons s5 = {3, &s4};
    const Cons s6 = {2, &s5};
    const Cons h = {1, &s6};    
    
    // | To produce a return value(expect 36).
    const int sum = cons_sum(h); 

    return sum;
}

producing the following object code (gcc test2_tail.c -o test2_tail.obj -c -O3, objdump -D test2_tail.obj):

In MinGW/MSys 64bit:

0000000000000000 <main>:
   0:   48 81 ec 88 00 00 00    sub    $0x88,%rsp
   7:   e8 00 00 00 00          callq  c <main+0xc>
   c:   48 8d 44 24 20          lea    0x20(%rsp),%rax
  11:   48 8d 54 24 70          lea    0x70(%rsp),%rdx
  16:   41 b8 01 00 00 00       mov    $0x1,%r8d
  1c:   48 89 44 24 38          mov    %rax,0x38(%rsp)
  21:   48 8d 44 24 30          lea    0x30(%rsp),%rax
  26:   b9 02 00 00 00          mov    $0x2,%ecx
  2b:   48 89 44 24 48          mov    %rax,0x48(%rsp)
  30:   48 8d 44 24 40          lea    0x40(%rsp),%rax
  35:   48 89 44 24 58          mov    %rax,0x58(%rsp)
  3a:   48 8d 44 24 50          lea    0x50(%rsp),%rax
  3f:   48 89 44 24 68          mov    %rax,0x68(%rsp)
  44:   48 8d 44 24 60          lea    0x60(%rsp),%rax
  49:   c7 44 24 20 08 00 00    movl   $0x8,0x20(%rsp)
  50:   00
  51:   48 c7 44 24 28 00 00    movq   $0x0,0x28(%rsp)
  58:   00 00
  5a:   48 c7 44 24 30 07 00    movq   $0x7,0x30(%rsp)
  61:   00 00
  63:   48 c7 44 24 40 06 00    movq   $0x6,0x40(%rsp)
  6a:   00 00
  6c:   48 c7 44 24 50 05 00    movq   $0x5,0x50(%rsp)
  73:   00 00
  75:   48 c7 44 24 60 04 00    movq   $0x4,0x60(%rsp)
  7c:   00 00
  7e:   48 c7 44 24 70 03 00    movq   $0x3,0x70(%rsp)
  85:   00 00
  87:   48 89 44 24 78          mov    %rax,0x78(%rsp)
  8c:   e8 00 00 00 00          callq  91 <main+0x91>
  91:   48 81 c4 88 00 00 00    add    $0x88,%rsp
  98:   c3                      retq

In Linux Mint 64 bit:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   48 83 ec 78             sub    $0x78,%rsp
   8:   b9 01 00 00 00          mov    $0x1,%ecx
   d:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  14:   00 00 
  16:   48 89 44 24 68          mov    %rax,0x68(%rsp)
  1b:   31 c0                   xor    %eax,%eax
  1d:   48 89 e0                mov    %rsp,%rax
  20:   48 8d 54 24 50          lea    0x50(%rsp),%rdx
  25:   c7 04 24 08 00 00 00    movl   $0x8,(%rsp)
  2c:   48 89 44 24 18          mov    %rax,0x18(%rsp)
  31:   48 8d 44 24 10          lea    0x10(%rsp),%rax
  36:   48 89 44 24 28          mov    %rax,0x28(%rsp)
  3b:   48 8d 44 24 20          lea    0x20(%rsp),%rax
  40:   48 89 44 24 38          mov    %rax,0x38(%rsp)
  45:   48 8d 44 24 30          lea    0x30(%rsp),%rax
  4a:   48 c7 44 24 08 00 00    movq   $0x0,0x8(%rsp)
  51:   00 00 
  53:   c7 44 24 14 00 00 00    movl   $0x0,0x14(%rsp)
  5a:   00 
  5b:   c7 44 24 10 07 00 00    movl   $0x7,0x10(%rsp)
  62:   00 
  63:   c7 44 24 24 00 00 00    movl   $0x0,0x24(%rsp)
  6a:   00 
  6b:   c7 44 24 20 06 00 00    movl   $0x6,0x20(%rsp)
  72:   00 
  73:   c7 44 24 34 00 00 00    movl   $0x0,0x34(%rsp)
  7a:   00 
  7b:   c7 44 24 30 05 00 00    movl   $0x5,0x30(%rsp)
  82:   00 
  83:   c7 44 24 44 00 00 00    movl   $0x0,0x44(%rsp)
  8a:   00 
  8b:   c7 44 24 40 04 00 00    movl   $0x4,0x40(%rsp)
  92:   00 
  93:   c7 44 24 54 00 00 00    movl   $0x0,0x54(%rsp)
  9a:   00 
  9b:   c7 44 24 50 03 00 00    movl   $0x3,0x50(%rsp)
  a2:   00 
  a3:   48 89 44 24 48          mov    %rax,0x48(%rsp)
  a8:   48 8d 44 24 40          lea    0x40(%rsp),%rax
  ad:   48 89 44 24 58          mov    %rax,0x58(%rsp)
  b2:   b8 02 00 00 00          mov    $0x2,%eax
  b7:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  be:   00 00 
  c0:   89 c6                   mov    %eax,%esi
  c2:   8b 02                   mov    (%rdx),%eax
  c4:   48 8b 52 08             mov    0x8(%rdx),%rdx
  c8:   01 f1                   add    %esi,%ecx
  ca:   48 85 d2                test   %rdx,%rdx
  cd:   75 f1                   jne    c0 <main+0xc0>
  cf:   01 c8                   add    %ecx,%eax
  d1:   48 8b 7c 24 68          mov    0x68(%rsp),%rdi
  d6:   64 48 33 3c 25 28 00    xor    %fs:0x28,%rdi
  dd:   00 00 
  df:   75 05                   jne    e6 <main+0xe6>
  e1:   48 83 c4 78             add    $0x78,%rsp
  e5:   c3                      retq   
  e6:   e8 00 00 00 00          callq  eb <main+0xeb>

The tail recursive version for the other answer using c++/constexpr:

/**
 * @file test3_tail.cpp
 */

typedef struct Cons {
    const int x;
    const struct Cons * const next;
} Cons;

constexpr int cons_sum_tail(const Cons c, const int accumulator) {
    if (c.next == 0) {
        return accumulator + c.x;
    } else {
        return cons_sum_tail(*(c.next), accumulator + c.x);
    }
}

constexpr int cons_sum(const Cons c) {
    return cons_sum_tail(c, 0);
}

int main(int argc, char **argv)
{    
    // | To build the stack-local linked list, with tail s0 and head h.
    static constexpr const Cons s0 {8, 0};
    static constexpr const Cons s1 {7, &s0};
    static constexpr const Cons s2 {6, &s1};
    static constexpr const Cons s3 {5, &s2};
    static constexpr const Cons s4 {4, &s3};
    static constexpr const Cons s5 {3, &s4};
    static constexpr const Cons s6 {2, &s5};
    static constexpr const Cons h {1, &s6};    
    
    // | To produce a return value(expect 36).
    constexpr int sum = cons_sum(h); 

    return sum;
}

producing the following object code (g++ test3_tail.c -o test3_tail.obj -c -O3, objdump -D test3_tail.obj):

In MinGW/MSys 64bit:

0000000000000000 <main>:
   0:   48 83 ec 28             sub    $0x28,%rsp
   4:   e8 00 00 00 00          callq  9 <main+0x9>
   9:   b8 24 00 00 00          mov    $0x24,%eax
   e:   48 83 c4 28             add    $0x28,%rsp

In Linux Mint 64 bit:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   b8 24 00 00 00          mov    $0x24,%eax
   9:   c3                      retq  
Jens
  • 69,818
  • 15
  • 125
  • 179
Dmytro
  • 5,068
  • 4
  • 39
  • 50