У меня странная ситуация, которая, кажется, работает хорошо для меня, но мне нужно знать, как сделать это лучше или как жить этим.
Я использую C ++ как скомпилированный язык сценариев для игровой движок. Системный вызов RIS C -V ABI аналогичен соглашению о вызове функции C, за исключением того, что вместо 8-го целочисленного аргумента или аргумента-указателя для номера системного вызова используется A7. Да, вы знаете, куда это идет. Вот:
extern "C" long syscall_enter(...);
template <typename... Args>
inline long syscall(long syscall_n, Args&&... args)
{
asm volatile ("li a7, %0" : : "i"(syscall_n));
return syscall_enter(std::forward<Args>(args)...);
}
В то время как syscall_enter - это просто символ в .text с инструкцией syscall и ret. Возвращаемое значение системного вызова также совпадает с регистром обычного возврата функции.
000103f0 <syscall_enter>:
syscall_enter():
103f0: 00000073 ecall
103f4: 00008067 ret
Перед этим мне пришлось создать более 20 функций, чтобы охватить все различные способы выполнения системных вызовов с целыми числами и указателями с барьер компилятора, и когда я хотел добавить функцию, которая принимала бы значения с плавающей точкой, он сказал бы, что вызов был неоднозначным, поскольку целые числа и числа с плавающей точкой могут быть преобразованы назад и вперед. Итак, я мог бы либо начать добавлять уникальные имена в функции, либо просто решить эту проблему лучше. Это было честно раздражать и ставить амортизатор на отличный опыт. Мне очень нравится возможность использовать C ++ на «обеих сторонах».
Инструкции, сгенерированные компилятором, кажутся нормальными. Это JAL и JALR syscall_enter, что нормально. Компилятор кажется немного сбитым с толку, но я не возражаю против одной дополнительной инструкции.
10204: 1f500793 li a5,501
10208: 00078893 mv a7,a5
1020c: 00000513 li a0,0
10210: 1e0000ef jal ra,103f0 <syscall_enter>
Как и центральная камера на позиции:
100d4: 19600793 li a5,406
100d8: 00078893 mv a7,a5
100dc: 000127b7 lui a5,0x12
100e0: 4207b587 fld fa1,1056(a5) # 12420 <_exit+0x2308>
100e4: 22b58553 fmv.d fa0,fa1
100e8: 010000ef jal ra,100f8 <syscall_enter>
Снова одна дополнительная инструкция перемещения. Выглядит хорошо. API уже интенсивно используется, и есть также API потоков, который работает с этим.
Теперь, есть ли еще лучший способ? Я не мог придумать лучшего способа загрузить a7 числом, а затем заставить компилятор установить вызов функции без фактического вызова функции. Я думал об использовании параметра шаблона для номера системного вызова, но в остальном я не уверен. Может быть, мы можем ограничить количество аргументов до 7? Это не будет правильно, когда есть целочисленные аргументы и аргументы с плавающей точкой, но это нормально. Структуры, хранящиеся в стеке, легко передать.
После некоторого тестирования я решил использовать это:
extern "C" long syscall_enter(...);
template <typename... Args>
inline long syscall(long syscall_n, Args&&... args)
{
// This will prevent some cases of too many arguments,
// but not a mix of float and integral arguments.
static_assert(sizeof...(args) < 8, "There is a system call limit of 8 integer arguments");
// The memory clobbering prevents reordering of a7
asm volatile ("li a7, %0" : : "i"(syscall_n) : "a7", "memory");
return syscall_enter(std::forward<Args>(args)...);
asm volatile("" : : : "memory");
}
Должно быть достаточно. Нет необходимости для спама функции syscall. Проверка для подсчета аргументов не является оптимальной, поскольку она должна только предотвращать использование 8-го интегрального регистра (что означает подсчет интегральных, указательных и ссылочных параметров). Но это предотвратит некоторые случаи.