г cc рука оптимизирует параметры до системного вызова - PullRequest
1 голос
/ 29 марта 2020

Я пытаюсь реализовать некоторые "OSEK-Services" на arm7tdmi-s, используя g cc arm. К сожалению, повышение уровня оптимизации приводит к «неправильному» генерированию кода. Главное, чего я не понимаю, это то, что компилятор, похоже, игнорирует стандарт вызова процедуры, например, передает параметры в функцию, перемещая их в регистры r0-r3. Я понимаю, что вызовы функций могут быть встроенными, но все же параметры должны быть в регистрах для выполнения системного вызова.

Рассмотрим следующий код, чтобы продемонстрировать мою проблему:

unsigned SysCall(unsigned param)
{
    volatile unsigned ret_val;
    __asm __volatile
    (
        "swi 0          \n\t"    /* perform SystemCall */
        "mov %[v], r0   \n\t"    /* move the result into ret_val */
        : [v]"=r"(ret_val) 
        :: "r0" 
    );

    return ret_val;              /* return the result */
}

int main()
{
    unsigned retCode;
    retCode = SysCall(5); // expect retCode to be 6 when returning back to usermode
}

Я написал программный обработчик прерываний верхнего уровня в сборке выглядит следующим образом:

.type   SWIHandler, %function
.global SWIHandler
SWIHandler:

    stmfd   sp! , {r0-r2, lr}        @save regs

    ldr     r0  , [lr, #-4]          @load sysCall instruction and extract sysCall number
    bic     r0  , #0xff000000

    ldr     r3  , =DispatchTable     @load dispatchTable 
    ldr     r3  , [r3, r0, LSL #2]   @load sysCall address into r3 

    ldmia   sp, {r0-r2}              @load parameters into r0-r2
    mov     lr, pc
    bx      r3 

    stmia   sp ,{r0-r2}              @store the result back on the stack
    ldr     lr, [sp, #12]            @restore return address
    ldmfd   sp! , {r0-r2, lr}        @load result into register
    movs    pc  , lr                 @back to next instruction after swi 0

Таблица диспетчеризации выглядит следующим образом:

DispatchTable:
    .word activateTaskService
    .word getTaskStateService

Функция SystemCall выглядит следующим образом:

unsigned activateTaskService(unsigned tID)
{
    return tID + 1; /* only for demonstration */
}

работает без оптимизации, все работает нормально, а параметры находятся в регистрах, как и ожидалось: см. Следующий код с оптимизацией -O0:

00000424 <main>:
 424:   e92d4800    push    {fp, lr}
 428:   e28db004    add fp, sp, #4
 42c:   e24dd008    sub sp, sp, #8
 430:   e3a00005    mov r0, #5          @move param into r0
 434:   ebffffe1    bl  3c0 <SysCall>

000003c0 <SysCall>:
 3c0:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)
 3c4:   e28db000    add fp, sp, #0
 3c8:   e24dd014    sub sp, sp, #20
 3cc:   e50b0010    str r0, [fp, #-16]
 3d0:   ef000000    svc 0x00000000
 3d4:   e1a02000    mov r2, r0
 3d8:   e50b2008    str r2, [fp, #-8]
 3dc:   e51b3008    ldr r3, [fp, #-8]
 3e0:   e1a00003    mov r0, r3
 3e4:   e24bd000    sub sp, fp, #0
 3e8:   e49db004    pop {fp}        ; (ldr fp, [sp], #4)
 3ec:   e12fff1e    bx  lr

Компиляция того же кода с -O3 приводит к следующей сборке код:

00000778 <main>:
 778:   e24dd008    sub sp, sp, #8
 77c:   ef000000    svc 0x00000000         @Inline SystemCall without passing params into r0
 780:   e1a02000    mov r2, r0
 784:   e3a00000    mov r0, #0
 788:   e58d2004    str r2, [sp, #4]
 78c:   e59d3004    ldr r3, [sp, #4]
 790:   e28dd008    add sp, sp, #8
 794:   e12fff1e    bx  lr

Обратите внимание, как systemCall вставляется без присваивания значения 5 t0 r0.

Мой первый подход состоит в том, чтобы вручную переместить эти значения в регистры, адаптировав функцию SysCall сверху следующим образом:

unsigned SysCall(volatile unsigned p1)
{
    volatile unsigned ret_val;
    __asm __volatile
    (
        "mov r0, %[p1]      \n\t"
        "swi 0              \n\t"
        "mov %[v], r0       \n\t" 
        : [v]"=r"(ret_val) 
        : [p1]"r"(p1)
        : "r0"
    );
    return ret_val;
}

В этом минимальном примере это работает, но я не совсем уверен является ли это наилучшей возможной практикой. Почему компилятор считает, что он может опустить параметры при вставке функции? Есть ли у кого-нибудь предложения по поводу того, подходит ли этот подход или что нужно делать по-другому?

Заранее спасибо

1 Ответ

6 голосов
/ 29 марта 2020

Вызов функции в C исходном коде не указывает компилятору вызывать функцию в соответствии с ABI. Он указывает компилятору вызывать функцию в соответствии с моделью в стандарте C, что означает, что компилятор должен передать аргументы функции способом ее выбора и выполнить функцию способом, который имеет то же самое наблюдаемые эффекты , как определено в стандарте C.

Эти наблюдаемые эффекты не включают установку каких-либо регистров процессора. Когда компилятор C указывает на функцию, нет необходимости устанавливать какие-либо конкретные регистры процессора. Если он вызывает функцию с использованием ABI для внешних вызовов, то ему придется устанавливать регистры. Встроенные вызовы не должны подчиняться ABI.

Так что простое размещение вашего системного запроса внутри функции, построенной из C исходного кода, не гарантирует, что будут установлены какие-либо регистры.

Для ARM , что вам нужно сделать, это определить переменные регистра, назначенные требуемому регистру (ам), и использовать их в качестве входных и выходных данных для инструкций по сборке:

unsigned SysCall(unsigned param)
{
    register unsigned Parameter __asm__("r0") = param;
    register unsigned Result    __asm__("r0");
    __asm__ volatile
    (
        "swi 0"
        : "=r" (Result)
        : "r"  (Parameter)
        : // "memory"    // if any inputs are pointers
    );
    return Result;
}

(Это серьезный шаг в GCC; это ужасно и документация плохая. Но см. также { ссылка } для некоторых ссылок. G CC для некоторых ISA имеет удобные спецификации c -регистрации, которые вы можете использовать вместо r, но не для ARM.) Переменные регистра не обязательно должны быть переменными; компилятор знает, что они будут использоваться как ввод и вывод для инструкций по сборке.

Сам оператор asm должен быть volatile, если он имеет побочные эффекты, отличные от получения возвращаемого значения. (например, getpid() не обязательно должно быть volatile.)

Не-1026 * asm оператор с выходами может быть оптимизирован, если выход не используется, или поднят из циклов, если он используется с тем же входом (как чистый вызов функции). Это почти никогда не то, что вам нужно для системного вызова.

Вам также нужен "memory" clobber, если какой-либо из входов является указателем на память, которую ядро ​​будет читать или изменять. См. Как я могу указать, что память, на которую * указывает * встроенный аргумент ASM, может использоваться? для более подробной информации (и способ использования фиктивного ввода или вывода памяти, чтобы избежать взрыва "memory". )

A "memory" clobber в mmap / munmap или других системных вызовах, которые влияют на то, что означает память, также было бы разумно; Вы не хотите, чтобы компилятор решал делать хранилище после munmap вместо ранее.

...