Правильный сбор возвращаемых значений из встроенной сборки - PullRequest
0 голосов
/ 30 апреля 2020

Я хочу использовать inline assembly для выполнения syscall в архитектуре PowerPC 32-bit. После выполнения syscall я также хочу вернуть возвращаемые значения syscall, взяв значения r3 и r4 и поместив их в long. Моя функция выглядит следующим образом:

constexpr auto maximum_syscall_parameter_count = 8;

long execute_system_call_with_arguments(short value, const int parameters_array[maximum_syscall_parameter_count]) {    
    char return_value_buffer[sizeof(long)];

    // syscall value
    asm volatile("mr 0, %0" : : "r" (value));

    // Pass the parameters
    asm volatile("mr 3, %0" : : "r" (parameters_array[0]));
    asm volatile("mr 4, %0" : : "r" (parameters_array[1]));
    asm volatile("mr 5, %0" : : "r" (parameters_array[2]));
    asm volatile("mr 6, %0" : : "r" (parameters_array[3]));
    asm volatile("mr 7, %0" : : "r" (parameters_array[4]));
    asm volatile("mr 8, %0" : : "r" (parameters_array[5]));
    asm volatile("mr 9, %0" : : "r" (parameters_array[6]));
    asm volatile("mr 10, %0" : : "r" (parameters_array[7]));

    // Execute the syscall
    asm volatile ("sc");

    // Retrieve the return value
    asm volatile ("mr %0, 3" : "=r" (*(int *) &return_value_buffer));
    asm volatile ("mr %0, 4" : "=r" (*(int *) &return_value_buffer[sizeof(int)]));

    return *(long *) &return_value_buffer;
}

Кажется, что это генерирует правильный код, но это выглядит странно, генерируются две избыточные инструкции:

mr        r0, r30
lwz       r9, 0(r31)
mr        r3, r9
lwz       r9, 4(r31)
mr        r4, r9
lwz       r9, 8(r31)
mr        r5, r9
lwz       r9, 0xC(r31)
mr        r6, r9
lwz       r9, 0x10(r31)
mr        r7, r9
lwz       r9, 0x14(r31)
mr        r8, r9
lwz       r9, 0x18(r31)
mr        r9, r9
lwz       r9, 0x1C(r31)
mr        r10, r9
sc
mr        r3, r3 # Redundant
mr        r9, r4 # Redundant
blr

Моя цель - просто вернуться с r3 и r4, установленные с помощью инструкции sc, но удаление возвращаемого значения или двух последних инструкций встроенной сборки из исходного кода приведет к повреждению функции до cra sh при возврате или возврате 0.

1 Ответ

2 голосов
/ 02 мая 2020

Позвольте мне начать с повторения того, что я сказал выше: я не говорю на PP C asm, и у меня нет PP C для запуска этого кода. Поэтому, хотя я считаю, что обычно , это направление, в котором вы должны действовать, не воспринимайте этот код как евангелие.

Далее, причина, по которой Джестер и я предложили использовать локальный регистр Переменные в том, что это приводит к лучшему (и, возможно, более читабельному / поддерживаемому) коду. Причиной этого является то, что эта строка в g cc документах :

G CC не анализирует сами инструкции ассемблера и не знает, что они означают или даже являются ли они допустимыми для ввода ассемблера.

Имея это в виду, что происходит, когда вы используете код, подобный описанному выше, и вызываете подпрограмму с кодом, подобным:

int parameters_array[maximum_syscall_parameter_count] = {1, 2, 3, 4, 5, 6, 7};

long a = execute_system_call_with_arguments(9, parameters_array);

компилятор не знает, что произойдет внутри этого блока asm, он должен записать все в память, которую блок asm затем считывает из памяти в регистры. При использовании кода, подобного приведенному ниже, компилятор может быть достаточно умен, чтобы пропустить когда-либо выделение памяти и загрузить регистры напрямую. Это может быть еще более полезно, если вы вызываете execute_system_call_with_arguments более одного раза с (по существу) одинаковыми параметрами.

constexpr auto maximum_syscall_parameter_count = 7;

long execute_system_call_with_arguments(const int value, const int parameters_array[maximum_syscall_parameter_count]) {    
    int return_value_buffer[2];

    register int foo0 asm("0") = value;

    register int foo1 asm("3") = parameters_array[0];
    register int foo2 asm("4") = parameters_array[1];
    register int foo3 asm("5") = parameters_array[2];
    register int foo4 asm("6") = parameters_array[3];
    register int foo5 asm("7") = parameters_array[4];
    register int foo6 asm("8") = parameters_array[5];
    register int foo7 asm("9") = parameters_array[6];

    // Execute the syscall
    asm volatile ("sc"
    : "+r"(foo3), "+r"(foo4)
    : "r"(foo0), "r"(foo1), "r"(foo2), "r"(foo5), "r"(foo6), "r"(foo7)
    );

    return_value_buffer[0] = foo3;
    return_value_buffer[1] = foo4;

    return *(long *) &return_value_buffer;
}

При вызове с приведенным выше примером выдает:

.L.main:
    li 0,9
    li 3,1
    li 4,2
    li 5,3
    li 6,4
    li 7,5
    li 8,6
    li 9,7
    sc
    extsw 3,6
    blr

Сохранение максимально возможного количества кода вне шаблона asm (ограничения считаются «внешними») позволяет оптимизаторам g cc делать все виды полезных вещей.

Несколько других моментов :

  1. Если какой-либо из элементов в parameters_array является (или может быть) указателем, вам необходимо добавить саблобер памяти . Это гарантирует, что любые значения, которые могут быть сохранены в регистрах, сбрасываются в память перед выполнением инструкции asm. Добавление clobber памяти, если он не нужен (может) замедлить выполнение с помощью пары инструкций. Отказ от этого при необходимости может привести к чтению неверных данных.
  2. Если sc изменяет любые регистры, которые здесь не перечислены, вы должны перечислить их как всплывающие подсказки. И если какие-либо регистры, перечисленные здесь (кроме foo3 и foo4), изменяются, вы должны также сделать их input + output (sc помещает код возврата в foo0?). Даже если вы «не используете их» после вызова asm, если они меняются, вы ДОЛЖНЫ сообщить об этом компилятору. Поскольку g cc docs явно предупреждает :

Не изменяйте содержимое операндов только для ввода (кроме входов, связанных с выходами). Компилятор предполагает, что при выходе из оператора asm эти операнды содержат те же значения, которые они имели до выполнения оператора.

Если не учитывать это предупреждение, код может нормально работать в один прекрасный день, затем внезапно вызывает странные сбои в некоторый момент после (иногда значительно позже) блока asm. Это «работает, а потом вдруг нет» - одна из причин, по которой я предлагаю вам не использовать встроенный asm , но если вы должны (что Вы делаете это, если вам нужно позвонить sc напрямую), оставьте его как можно меньше.

Я немного обманул, изменив maximum_syscall_parameter_count на 7. По-видимому, g cc Godbolt не оптимизирует этот код также с большим количеством параметров. Если это необходимо, могут быть способы обойти это, но вам понадобится лучший эксперт по PP C, чем я.
...