How to stop icc from eliminating function called from inline assembly

Viewed 125

Background

I'm making an app that needs to run several tasks concurrently. I can't use threads and such because the app should work without any OS (i.e. straight from the bootsector). Using x86 tasks looks like an overkill (both logically and performance-wise). Thus, I decided to implement a task-switching utility myself. I would save processor state, make a call to the task code and then restore the previous state. So I have to make the call from inline assembly.

Problem

Here's some example code:

#include <stdio.h>

void func() {
    printf("Hello, world!\n");
}

void (*funcptr)();

int main() {
    funcptr = func;
    asm(
        "call *%0;"
        :
        :"r"(funcptr)
    );
    return 0;
}

It compiles perfectly under icc with no options, gcc and clang and yields "Hello, world!" when run. However, if I compile it with icc main.c -ipo, it segfaults.

I disassembled the code that was generated by icc main.c and got the following:

0000000000401220 <main>:
  401220:   55                      push   %rbp
  401221:   48 89 e5                mov    %rsp,%rbp
  401224:   48 83 e4 80             and    $0xffffffffffffff80,%rsp
  401228:   48 81 ec 80 00 00 00    sub    $0x80,%rsp
  40122f:   bf 03 00 00 00          mov    $0x3,%edi
  401234:   33 f6                   xor    %esi,%esi
  401236:   e8 45 00 00 00          callq  401280 <__intel_new_feature_proc_init>
  40123b:   0f ae 1c 24             stmxcsr (%rsp)
  40123f:   48 c7 05 f6 78 00 00    movq   $0x401270,0x78f6(%rip)        # 408b40 <funcptr>
  401246:   70 12 40 00 
  40124a:   b8 70 12 40 00          mov    $0x401270,%eax
  40124f:   81 0c 24 40 80 00 00    orl    $0x8040,(%rsp)
  401256:   0f ae 14 24             ldmxcsr (%rsp)
  40125a:   ff d0                   callq  *%rax
  40125c:   33 c0                   xor    %eax,%eax
  40125e:   48 89 ec                mov    %rbp,%rsp
  401261:   5d                      pop    %rbp
  401262:   c3                      retq   
  401263:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  401268:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40126f:   00 

0000000000401270 <func>:
  401270:   bf 04 40 40 00          mov    $0x404004,%edi
  401275:   e9 e6 fd ff ff          jmpq   401060 <puts@plt>
  40127a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

On the other hand, icc main.c -ipo yields:

0000000000401210 <main>:
  401210:   55                      push   %rbp
  401211:   48 89 e5                mov    %rsp,%rbp
  401214:   48 83 e4 80             and    $0xffffffffffffff80,%rsp
  401218:   48 81 ec 80 00 00 00    sub    $0x80,%rsp
  40121f:   bf 03 00 00 00          mov    $0x3,%edi
  401224:   33 f6                   xor    %esi,%esi
  401226:   e8 25 00 00 00          callq  401250 <__intel_new_feature_proc_init>
  40122b:   0f ae 1c 24             stmxcsr (%rsp)
  40122f:   81 0c 24 40 80 00 00    orl    $0x8040,(%rsp)
  401236:   48 8b 05 cb 2d 00 00    mov    0x2dcb(%rip),%rax        # 404008 <funcptr_2.dp.0>
  40123d:   0f ae 14 24             ldmxcsr (%rsp)
  401241:   ff d0                   callq  *%rax
  401243:   33 c0                   xor    %eax,%eax
  401245:   48 89 ec                mov    %rbp,%rsp
  401248:   5d                      pop    %rbp
  401249:   c3                      retq   
  40124a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

So, while -ipo didn't remove funcptr variable (see address 401236), it did remove assignment. I guess that icc noticed that func is not called from C code so it can be safely removed, so funcptr is allowed to contain garbage. However, it didn't notice that I'm calling func indirectly via assembly.

What I tried

  1. Replacing "r"(funcptr) with "r"(func) works but I can't hardcode a specific function (see background).
  2. Calling funcptr and/or func before and/or after inline assembly block don't help because icc just inlines printf("Hello, world!\n");.
  3. I can't get rid of inline assembly because I have to do low-level register, flags and stack manipulation before and after call.
  4. Making funcptr volatile yields the following warning but still segfaults:
a value of type "void (*)()" cannot be assigned to an entity of type "volatile void (*)()"
  1. Adding volatile to almost every other word doesn't help either.
  2. Moving func and/or funcptr to other source files and then linking them together doesn't help.
  3. Moving inline assembly to a separate function doesn't work.

Am I doing something wrong or is it an icc bug? If the former, how do I fix the code? If the latter, is there any workaround and should I report the bug?

$ icc --version
icc (ICC) 19.1.0.166 20191121
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.
0 Answers
Related