LINUX.ORG.RU

C calling convention and register arguments

 ,


0

1

Я так понимаю что есть два стандартных способа вызывать C-функции: параметры через стек и параметры (частично) через регистры (fastcall).

Как понять какой метод нужно использовать для конкретной либы? Или шаред либы всегда получают параметры через стэк?

UP спрашиваю потому что пишу свой недоязык с использованием llvm и там при вызове функций просят указать calling convention типа ccc, fastcc итп. Автоматом, оно, похоже, ни о чём не догадается. Что-ж, оставлю дефолт :)

★★★★★

Последнее исправление: true_admin (всего исправлений: 1)

у функции должен быть __attribute__((fastcall)). Иначе через стэк. Это работа компилятора, не думай об этом.

anonymous
()

Договорённость как передавать аргументы есть часть ABI.

Помимо названых есть ещё pascalcall (или как он там называеться) - это когда через стек, только аргументы в обратном порядке лежат. Под оффтопиком есть ещё stdcall, этого когда callee чистит стек перед тем как сделать ret.

А вообще на асме можно запилить какой хочешь. Даже runtime defined. Ядрёный интерфейс вообще исключительно через регистры все 6 возможных параметров передаёт и никаких стеков.

nanoolinux ★★★★
()
Последнее исправление: nanoolinux (всего исправлений: 2)

это всё виндузячье наследие для 32 битной x86 архитектуры, забей на это. На x86_64 везде (оффтопик, онтопик) используется одно стандартное соглашение для вызова функций

Harald ★★★★★
()
Ответ на: комментарий от anonymous

Это работа компилятора, не думай об этом.

я просто не уверен что llvm об этом знает. Он понимает различные способы вызова функций, но вряд ли он пойдёт смотреть что там в атрибутах либы.

true_admin ★★★★★
() автор топика

лучше не лазить в эту ерунду, пусть компилятор сам думает, как оно ему быстрее и проще на данной архитектуре. При этом ИМХО, если компилятор на архитектуре XYZ выбрал способ ABC, то его же использует и llvm, ибо она-то тоже про архитектуру свою в курсе, и знает, что способ ABC самый лучший. Потому совместимость осуществляется автоматически.

И да, сейчас ИМХО через регистры таки не быстрее, а ИМХО медленнее.

drBatty ★★
()

Я так понимаю что есть два стандартных способа вызывать C-функции

Всё несколько сложнее. См. 7 Function calling conventions.

Relan ★★★★★
()
Ответ на: комментарий от true_admin

Он понимает различные способы вызова функций, но вряд ли он пойдёт смотреть что там в атрибутах либы.

вообще-то при объявлении функции из либы в заголовочном файле можно и соглашение о вызове указать. Тогда при компиляции кода, вызывающего функции из этой либы, будет использовано указанное соглашение

Harald ★★★★★
()

Если речь о си-функциях, не очевидно ли, что там cdecl? Или я отстал от жизни?

anonymous
()
Ответ на: комментарий от Harald

вообще-то при объявлении функции из либы в заголовочном файле можно и соглашение о вызове указать.

Хм, я думал на это так же влияет -On. Т.е. просто посмотреть на заголовочный файл не достаточно. Но я могу и ошибаться, поэтому вот спрашиваю :)

true_admin ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Хм, я думал на это так же влияет -On. Т.е. просто посмотреть на заголовочный файл не достаточно. Но я могу и ошибаться, поэтому вот спрашиваю :)

Если функцию тебе слепил Си компилятор, то calling convention зависит только от архитектуры, mkey.

Waterlaz ★★★★★
()
Ответ на: комментарий от Waterlaz

операции работы со стеком хорошо оптимизированны, при push/pop данные в стек даже и не пишуться, и как я понял, это дукументированная фича. В любом случае, верхушка стека заведомо лежит в кеше данных, и время доступа к ней около нуля. А вот имён регистров в x86 традиционно кот наплакал, потому какой-нить eax или edx представляют собой очень ценный ресурс, часто уникальный(т.е. если ты заюзал edx для передачи данных в функцию, то перед умножением придётся твой edx восстановить из памяти, ибо умножение только с edx и работает). Ну и да, данные 32х битные, тип int 32х битный, в стек тоже можно 32х битные числа вносить, а вот регистры уже 64х битные, и половина из них будет считать мусор.

Это всё конечно ИМХО, IRL разница похоже если и есть, то небольшая. Во всяком случае от «fast» заметного выигрыша нет. Это точно.

drBatty ★★
()
Ответ на: комментарий от drBatty

при push/pop данные в стек даже и не пишутся, и как я понял, это дукументированная фича.

То есть ты утверждаешь, что рядом находящееся ядро при попытке чтения прочитает старые данные?

потому какой-нить eax или edx представляют собой очень ценный ресурс, часто уникальный

Начиная с i80386 (1985-й год выпуска) уже не такой уникальный.

69 c1 20 80 00 00    	imul   $0x8020,%ecx,%eax
69 94 24 30 01 00 00 	imul   $0x647,0x130(%esp),%edx
6b 8c 24 c8 00 00 00 	imul   $0x2c,0xc8(%esp),%ecx

Ну и да, данные 32х битные, тип int 32х битный, в стек тоже можно 32х битные числа вносить, а вот регистры уже 64х битные, и половина из них будет считать мусор.

Ты забыл о +8 к количеству. Это больше чем в два раза больше. К тому же, когда параметров много, используется стек.

Это всё конечно ИМХО

В твоём ИМХО слишком мало Х. ☺

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

То есть ты утверждаешь, что рядом находящееся ядро при попытке чтения прочитает старые данные?

оно и должно прочитать свои данные, что-бы пара push/pop была атомарной. И у второго ядра вообще-то свой стек должен быть. Не?

drBatty ★★
()
Ответ на: комментарий от i-rinat

То есть ты утверждаешь, что рядом находящееся ядро при попытке чтения прочитает старые данные?

Легко, кэш в общем случае у каждого проца свой и может содержать устаревшие данные.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от i-rinat

А вот у меня кстати ещё вопрос по поводу передачи аргументов в регистрах. Насколько оно быстрее в реальных приложениях (именно прикладной код, а не какое-нибудь кодирование/раскодирование)?

Ведь если я передаю из функции a() в функцию b() аргумент в регистре ebx/rbx, то если функция b() захочет вызвать функцию c() с одним аргументом (передающимся, очевидно, в этом же регистре), ей придётся сохранить предыдущее значение ebx/rbx для дальнейшего использования внутри себя, и сохранить его либо в стеке (тогда никаких плюсов по сравнению с передачей через регистр и не будет), либо в другом регистре, скажем esi/rsi, но тогда ей также придётся думать, где сохранить этот самый esi/rsi.

pv4 ★★
()
Последнее исправление: pv4 (всего исправлений: 1)
Ответ на: комментарий от drBatty

оно и должно прочитать свои данные, что-бы пара push/pop была атомарной.

Нет такого требования атомарности. Можно сделать call $+5, а затем pop eax и получить eip. Не обязательно push/pop должны идти парой.

И у второго ядра вообще-то свой стек должен быть. Не?

Не обязан. Вполне может читать/писать данные. Память общая, в этом суть SMP/NUMA.

Другое дело, обычно стеки разные, даже у нитей, но это просто соглашения. (Мульти)процессор должен работать в любых ситуациях. Более того, должна обеспечиваться когерентность кешей.

i-rinat ★★★★★
()
Ответ на: комментарий от pv4

А вот у меня кстати ещё вопрос по поводу передачи аргументов в регистрах. Насколько оно быстрее в реальных приложениях (именно прикладной код, а не какое-нибудь кодирование/раскодирование)?

У меня недостаточно знаний, чтобы посчитать выгоду/проигрыш.

ей придётся сохранить предыдущее значение ebx/rbx для дальнейшего использования внутри себя

http://en.wikipedia.org/wiki/X86_calling_conventions#Intel_ABI

В ABI описываются регистры, которые считаются «временными» — их не надо сохранять, о них заботится вызывающая функция. Но вообще да, есть такая проблема. К счастью, об этом беспокоится компилятор.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Другое дело, обычно стеки разные, даже у нитей, но это просто соглашения. (Мульти)процессор должен работать в любых ситуациях. Более того, должна обеспечиваться когерентность кешей.

должен конечно. И работает. Однако ты сам прекрасно понимаешь, под какой код оно оптимизировано. Я не проверял, но не удивлюсь вороху штрафных тактов на каждую команду, если сделать коммунальный стек. Проверь, возможно и ты прав, а может и нет. ИМХО стеки _должны_ быть изолированны, как кеши и ядра. А когда процессы сталкиваются, это особый случай, в котором всё должно тормозиться. И очень сильно. Тоже ИМХО конечно.

drBatty ★★
()
Ответ на: комментарий от i-rinat

У меня недостаточно знаний, чтобы посчитать выгоду/проигрыш.

Допускаю :) но, может, у вас есть информация об уже кем-то посчитанных?

их не надо сохранять, о них заботится вызывающая функция

Дык тогда вызывающая функция всё равно должна их сохранить в стеке.

Вопрос не в том, кто об этом должен беспокоиться, а именно о приросте производительности и о том, стоит ли оно того в среднем случае.

pv4 ★★
()
Ответ на: комментарий от true_admin

я просто не уверен что llvm об этом знает

ты по факту не уверен, что llvm генерит рабочий код. gcc вроде как тоже понимает различные способы, не?

x0r ★★★★★
()
Ответ на: комментарий от i-rinat

Начиная с i80386 (1985-й год выпуска) уже не такой уникальный.

ЕМНИП эти команды сильно тормозили, по сравнению с обычными. Не знаю, как оно сейчас. Знаю, что компилятор этих команд не любит почему-то, предпочитает обычные.

ы забыл о +8 к количеству. Это больше чем в два раза больше. К тому же, когда параметров много, используется стек.

ничего я не забыл. Всё равно часто там воздух перемалывается. Хотя на SSE2 конечно можно и по 4 int32 сразу молоть. И передавать в функции. Если компилятор конечно осилит такое.

drBatty ★★
()
Ответ на: комментарий от drBatty

ИМХО стеки _должны_ быть изолированны, как кеши и ядра.

http://en.wikipedia.org/wiki/MESI_protocol

Я не проверял, но не удивлюсь вороху штрафных тактов на каждую команду, если сделать коммунальный стек

Скорее не штрафные такты, а инвалидация команд по конвейеру. А ещё задержки при чтении из кеша (нужно проверить, есть ли данные в L1, затем в L2, затем собственно читать) и записи в кеш (запись в новую линию означает, что старую надо вытеснить, потом загрузить линию из памяти). Даже попадание в кэш стоит единиц тактов. С учётом того, что Core до 6 команд за такт может в отставку отправить, это дорого. А регистров море: 16 общего назначения, 16 xmm.

i-rinat ★★★★★
()
Ответ на: комментарий от pv4

есть информация об уже кем-то посчитанных?

В AMD решили, что с большим числом регистров уже можно передавать параметры не в стеке. Я не слышал, чтобы Intel были особо против.

i-rinat ★★★★★
()
Ответ на: комментарий от true_admin

Мало того, что новая архитектура практически повторяет старую, что сохранили свой идиотский CISC даже в новом режиме, так они еще умудрились не договориться с ведущими производителями компиляторов по поводу собственного ABI. Уступили маркетингу и в спешке просрали единственный переходный момент, когда можно было все сделать красиво.

По треду в целом: кэш обязан в когерентность и не знает о стеке, т.к. стек это сегментный/страничный уровень, а кэш — физический. Теории ваши ни о чем, стек всегда будет медленнее.

anonymous
()
Ответ на: комментарий от drBatty

То есть передать соседнему треду объект со своего стека это уже что-то сверхъестественное?

anonymous
()
Ответ на: комментарий от drBatty

В любом случае, верхушка стека заведомо лежит в кеше данных, и время доступа к ней около нуля.

но подступиться значит перенести в регистр ;)

Ну и да, данные 32х битные, тип int 32х битный, в стек тоже можно 32х битные числа вносить, а вот регистры уже 64х битные, и половина из них будет считать мусор.

so fucking what? Чтобы что-то сделать с этими твоими 32 битными числами их все равно нужно загнать в 64битный регистр. Процессор «не знает» ничего ни о каких 32 битах

Waterlaz ★★★★★
()
Ответ на: комментарий от drBatty

ну вот на x86_64 уже fastcall

а вот этого я ещё не приметил…

 $ cat tst.c 
int f(){
    return g(0x666, 0x777, 0x888);
}
 $ gcc -c tst.c
 $ objdump -d tst.o 

tst.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <f>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	ba 88 08 00 00       	mov    $0x888,%edx
   9:	be 77 07 00 00       	mov    $0x777,%esi
   e:	bf 66 06 00 00       	mov    $0x666,%edi
  13:	b8 00 00 00 00       	mov    $0x0,%eax
  18:	e8 00 00 00 00       	callq  1d <f+0x1d>
  1d:	c9                   	leaveq 
  1e:	c3                   	retq   



 $ gcc -c -O2 tst.c
 $ objdump -d tst.o 

tst.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <f>:
   0:	ba 88 08 00 00       	mov    $0x888,%edx
   5:	be 77 07 00 00       	mov    $0x777,%esi
   a:	bf 66 06 00 00       	mov    $0x666,%edi
   f:	31 c0                	xor    %eax,%eax
  11:	e9 00 00 00 00       	jmpq   16 <f+0x16>

Waterlaz ★★★★★
()
Ответ на: комментарий от nanoolinux

pascalcall (или как он там называеться) - это когда через стек, только аргументы в обратном порядке лежат.

Под оффтопиком

Под оффтопиком как раз излюбленный дефолт - pascalcall, как ты его назвал... Хотя оно просто pascal, а stdcall - «сорт его» же http://en.wikipedia.org/wiki/X86_calling_conventions#pascal

slackwarrior ★★★★★
()
Ответ на: комментарий от i-rinat

В AMD решили, что с большим числом регистров уже можно передавать параметры не в стеке. Я не слышал, чтобы Intel были особо против.

никто не против, вопрос в том, будет-ли профит, и насколько много? Это не i8080, который действительно в память всё и всегда загонял.

drBatty ★★
()
Ответ на: комментарий от anonymous

По треду в целом: кэш обязан в когерентность и не знает о стеке, т.к. стек это сегментный/страничный уровень, а кэш — физический. Теории ваши ни о чем, стек всегда будет медленнее.

во многих случаях стек таки быстрее. Это факт. К тому же, при вызове подпрограмм тебе по любому в стек лезть, нравится тебе, или нет. В 95% программ на C/C++ ещё и лезть в стек для выделения памяти под локальные переменные сразу после вызова. Потому регистры тут - левая сущность. Впрочем - всё зависит от того, как оно реализовано в железе для данного конкретного процессора.

То есть передать соседнему треду объект со своего стека это уже что-то сверхъестественное?

ну вообще-то лучше так не делать. Лучше выделить специальную память, которая и будет общей для процессов. ИМХО. хотя ты можешь делать, я разрешаю.

drBatty ★★
()
Ответ на: комментарий от Waterlaz

но подступиться значит перенести в регистр ;)

не значит. Полно команд, которые работают напрямую с памятью, в т.ч. и в [ebp±X]

so fucking what? Чтобы что-то сделать с этими твоими 32 битными числами их все равно нужно загнать в 64битный регистр. Процессор «не знает» ничего ни о каких 32 битах

ты сейчас про какую архитектуру говорил? А я про x86, которая «знает». И о байтах знает, и о словах, и о DWORD PTR тоже. И команд там полно.

вот тебе пример

0000000000400610 <f>:
  400610:   55                      push   rbp
  400611:   48 89 e5                mov    rbp,rsp
  400614:   89 7d ec                mov    DWORD PTR [rbp-0x14],edi
  400617:   c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  40061e:   eb 25                   jmp    400645 <f+0x35>
  400620:   8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
  400623:   89 c2                   mov    edx,eax
  400625:   0f af 55 ec             imul   edx,DWORD PTR [rbp-0x14]
  400629:   89 d0                   mov    eax,edx
  40062b:   c1 e0 03                shl    eax,0x3
  40062e:   29 d0                   sub    eax,edx
  400630:   89 45 ec                mov    DWORD PTR [rbp-0x14],eax
  400633:   8b 55 ec                mov    edx,DWORD PTR [rbp-0x14]
  400636:   89 d0                   mov    eax,edx
  400638:   01 c0                   add    eax,eax
  40063a:   01 d0                   add    eax,edx
  40063c:   01 c0                   add    eax,eax
  40063e:   89 45 ec                mov    DWORD PTR [rbp-0x14],eax
  400641:   83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
  400645:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  400648:   3b 45 ec                cmp    eax,DWORD PTR [rbp-0x14]
  40064b:   7c d3                   jl     400620 <f+0x10>
  40064d:   8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
  400650:   5d                      pop    rbp
  400651:   c3                      ret
да. Параметр здесь через edi передаётся, который сразу в память и пишется. В чём тут профит - непонятно. Это без оптимизации, с оптимизацией просто функцию выкидывает и раскрывает.

drBatty ★★
()
Ответ на: комментарий от slackwarrior

Под оффтопиком как раз излюбленный дефолт - pascalcall

под оффтопиком они живут в своём уютном i8080, с плоской памятью, в которой нет ни кешей, ни стеков, и 8 8и битных регистров.

drBatty ★★
()
Ответ на: комментарий от anonymous

кэш обязан в когерентность

И эти люди гонят бочку на AMD. В cortex A9 нужно чистить I-кэш вилкой^W поддерживать когерентность вручную программно.

AptGet ★★★
()
Последнее исправление: AptGet (всего исправлений: 1)
Ответ на: комментарий от Harald

ну дак ARM изначально вроде как не проектировался для многопроцессорного применения, не?

Времена изменились. Похоже что арм тоже стал обрастать архитектурными проблемами :)

true_admin ★★★★★
() автор топика
Ответ на: комментарий от drBatty

Если мы говорим о глубоком стеке вызовов, то да, так или иначе средние аргументы должны окажутся в стеке — либо с подачи вызывающего, либо с подачи вызываемого, т.к. очевидно, что регистров не хватит на несколько уровней вызова. Но в самом конце, где, собственно и проходит наш «critical path», fastcall обретает смысл. Про «все равно лезть в стек» — очень странный юмор, хотя может я недопонял чего.

соседнему треду объект со своего стека ... так не делать. Лучше выделить специальную память, которая ... хотя ты можешь делать, я разрешаю.

Ну прям отлегло :)

Серьезно, чем эта память будет отличаться от той, что на стеке? Кэш стал в целях самоускорения бегать по GDT/LDT* и смотреть где там стеки расположены? Или какой-то новый буфер появился, в который складываются диапазоны стеков, а кэш туда заглядывает?

anonymous
()
Ответ на: комментарий от AptGet

А что такого? Если под I понимается «Instruction-», то не такая уж это большая проблема, это же не данные, которые каждые 5 секунд меняются. Загрузил, очистил, запустил, работает.

> кэш обязан в когерентность

У них он это исторически обязан, хотя если бы они в новой 64-битной архитектуре (читай все и так сломалось) заставили бы самому управлять когерентностью, думаю никто бы не был против, а доктор так вообще порадовался бы.

anonymous
()

хех... ты ещё про amd64 не знаешь? И про порядок аргументов в стеке?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.