Вызов процедуры с неверным количеством параметров

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Re: Вызов процедуры с неверным количеством параметров

Сообщение MysticCoder » 22.10.2018 20:52:14

xdsl писал(а):В большинстве остальных постов - набор крайне полезных для ЧСВ словесных кружев от службы психологической разгрузки: "Это никому не надо. Аминь.", "Так делать нельзя, потому-что нельзя.", "Если получается, значит можно.".


В следующий раз не буду стараться для того, чтобы ответить тебе, а буду отвечать кратко как в тех. поддержке "Читай инструкцию" ^^.
MysticCoder
постоялец
 
Сообщения: 154
Зарегистрирован: 14.09.2013 00:20:28

Re: Вызов процедуры с неверным количеством параметров

Сообщение xdsl » 23.10.2018 07:35:29

Vapaamies писал(а):Дайте угадаю. Архитектура x64? Скорее всего, вы используете побочный эффект соглашения вызова System V fastcall на x64, когда первые 6 или 13 (?) параметров передаются через регистры, а чистку жизнедеятельности делает вызывающая сторона, по максимуму в вашем случае. Смотрите ассемблерный подстрочник, компилируйте и проверяйте под x86 32 бита.

Архитектуры ARM (armel) и x86-64. Объединяет только Linux.
UPD: Только-что проверил x86 - не работает ни под Windows, ни под Linux.

Добавлено спустя 6 минут 15 секунд:
zub писал(а):Ты бы не пердалировал, а сказал спасибо что выше мягко выражались

MysticCoder писал(а):В следующий раз не буду стараться для того, чтобы ответить тебе, а буду отвечать кратко как в тех. поддержке "Читай инструкцию" ^^.

Позволю себе процитировать: "Talk is cheap. Show me the code." (https://lkml.org/lkml/2000/8/25/132)
А так как "Show me" здесь никто не обязан, то зачем разводить пустопорожние "Talk"? Для этого есть прекрасная ветка форума viewforum.php?f=2

Добавлено спустя 3 часа 23 минуты 33 секунды:
Вообщем, проанализировал ассемблерный код и нашел, почему все работает на x86-64 и ARM. В обоих случаях push и pop не применяется для работы с параметрами процедуры. Соответственно, если при вызове в стек положено больше параметров, чем заявлено в процедуре, они просто игнорируются, а адрес возврата из процедуры лежит в фиксированной точке стека, от количества параметров не зависящей. Таким образом, все будет прекрасно работать с любыми типами и любым количеством параметров.

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

Резюме: мне неизвестно, на основании чего разработчики компилятора используют или не используют pop и push для различных платформ и не решат-ли они сменить подход в новой версии компилятора. Поэтому, как не жаль, в целях стабильности кода, придется отказаться от такой заманчивой и сокращающей код фишки и вернуться на кучу перегруженных процедур.
xdsl
постоялец
 
Сообщения: 131
Зарегистрирован: 15.01.2009 13:49:03

Re: Вызов процедуры с неверным количеством параметров

Сообщение Vapaamies » 23.10.2018 17:46:54

xdsl писал(а):Резюме: мне неизвестно, на основании чего разработчики компилятора используют или не используют pop и push для различных платформ и не решат-ли они сменить подход в новой версии компилятора.

Как разработчик компилятора могу сказать, что архитектуры типа RISC, к которым относится ARM, и x64 располагают большим количеством регистров, позволяющим передавать параметры без использования стека. Разработчики компиляторов используют эту возможность, превращая все соглашения вызова в единую разновидность fastcall. Это общий подход, он меняться не будет.

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

xdsl писал(а):Поэтому, как не жаль, в целях стабильности кода, придется отказаться от такой заманчивой и сокращающей код фишки и вернуться на кучу перегруженных процедур.

Можно использовать перегруженные процедуры только на x86, ограничив их условными символами.
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 291
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: Вызов процедуры с неверным количеством параметров

Сообщение Mirage » 24.10.2018 00:16:31

xdsl писал(а):Позволю себе процитировать: "Talk is cheap. Show me the code."

Код это хорошо, но документация еще лучше.

Если почитать вот тут, то можно найти такую фразу:
If the callee is a variadic function, then the number of floating point arguments passed to the function in vector registers must be provided by the caller in the AL register.


Так что можно и с переменным кол-вом параметров вызвать.

xdsl писал(а):Таким образом, все будет прекрасно работать с любыми типами и любым количеством параметров.


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

Тут важно что стек очищает вызывающий, т.е. если обеспечить, что параметров будет не больше, чем думает вызывающий, то стек не будет испорчен и твой способ будет работать.
Также он будет работать и в x86, если использовать соглашение о вызове не по умолчанию (register), а cdecl.

Vapaamies писал(а):что архитектуры типа RISC, к которым относится ARM, и x64


Давно x64 RISC'ом стала? И чем оно от x86 отличается в этом смысле? И как на кол-во регистров это влияет?
Mirage
энтузиаст
 
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia

Re: Вызов процедуры с неверным количеством параметров

Сообщение xdsl » 25.10.2018 15:16:06

Изменил программу на рекурсивный вариант, ввел более 10 параметров, чтобы исчерпать регистры и учел некоторые другие предложения форумчан:
Код: Выделить всё
type tx=procedure(a:integer; b:double; c:extended; c1,c2,c3,c4,c5,c6,c7,c8,c9,a1,a2,a3,a4,a5:integer);cdecl;

var cnt:integer=3;
procedure x(a:integer; b:double; c:extended);cdecl;
begin
if cnt=0 then exit;
dec(cnt);
writeln(a,b,c);
tx(@x)(a+1,b+1,c+1,5,6,7,8,9,10,11,12,13,14,15,16,17,18);
end;

begin
tx(@x)(1,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18);
end.
Теперь работает на всех платформах, до которых у меня есть доступ, а именно: Linux ARMel, Linux x86, Linux x86-64, Windows x86.
Исходник и ассемблерный выхлоп со всех платформ прикрепляю.

Понял, что был не прав, утверждая, что местоположение адреса возврата из процедуры зависит от кол-ва параметров. Это не так, адрес возврата всегда в одном и том-же месте. А вот порядок передачи параметров значение имеет. При этом по умолчанию тип вызова "cdecl" на ARMel и x86-64, а "pascal" игнорируется. На x86 дефолтный тип вызова - "pascal". Тип вызова "register" имеет смысл использовать только на x86 - это расширение для "pascal". На ARMel и x86-64 "register" игнорируется. Вот и получается, если тип вызова не указывать, то пример будет работать на x86-64 и ARMel, а на x86 - нет. Если указать cdecl - будет работать на всех указанных платформах.
У вас нет необходимых прав для просмотра вложений в этом сообщении.
xdsl
постоялец
 
Сообщения: 131
Зарегистрирован: 15.01.2009 13:49:03

Re: Вызов процедуры с неверным количеством параметров

Сообщение скалогрыз » 25.10.2018 22:34:32

xdsl писал(а):На x86 дефолтный тип вызова - "pascal".

дефолтный тип вызова это "register". (документация)
Соглашение по вызову имеют смысл только для x86, платформы, для x86-64, арм-ов и других они просто игнорируются.
тип вызова "pascal" использовать не стоит, ибо он устаревший.

Самое безопасное это использовать вызов "cdecl", потому что очищение параметров лежит на коде, который делает вызов. (А это место вполне знает количество переданных параметров). Более того в Си, есть механизм произвольного количества переданных параметров. (на самом деле это дополнительный скрытый параметр о количестве переданных параметров, + сами параметры)

Т.к. на платформах отличных от x86, параметры передаются не только через стэк, но и через регисты, произвльная передача параметров реализована сложнее. (и чуть больше походит на array of const)

FPC частично поддерживает "произвольное количество": https://www.freepascal.org/docs-html/ref/refsu87.html
но только для внешних функций. Т.е. объявить таким образом функцию самому не получится.

Можно попробовать сделать полухак: сделать объявление такой процедуры, а потом сделать её же реализацю в другом модуле, и указать есть имя на "экспорт". Тогда компоновщик сможет соединить два куска кода.

Ещё можно пойти более честным путём и использовать libffi, для производства произвольных вызовов. (поддержка разных платформ и разных типов вызовов в наличии)

Но вообще меня немного удивляет сама задача:
Потому-что процедуру объявляет конечный пользователь моего проекта, а я ее только вызываю

а процедура как попадает в проект? через .dll (.so)?
потому что, если через dll, то вызов, как указано изначально - вполне уместен, главное соблюсти количество и тип параметров.
Полно библиотек, которые подгружаются в момент исполнения (тот же openGL)

если же процедура компилируется в проект, то вообще никаких проблем быть не должно.
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48

Re: Вызов процедуры с неверным количеством параметров

Сообщение xdsl » 26.10.2018 10:10:41

скалогрыз писал(а):
xdsl писал(а):На x86 дефолтный тип вызова - "pascal".

дефолтный тип вызова это "register". (документация)

Это так, каюсь ;). Сам не знаю, почему такую ересь написал, имел в виду именно "register"
скалогрыз писал(а):Соглашение по вызову имеют смысл только для x86, платформы, для x86-64, арм-ов и других они просто игнорируются.

cdecl для x86-64 и ARMel не игнорируется, он дефолтный и единственно возможный.
скалогрыз писал(а):Самое безопасное это использовать вызов "cdecl", потому что очищение параметров лежит на коде, который делает вызов. (А это место вполне знает количество переданных параметров).

Судя по ассемблерному коду, очистка стека от параметров - это вообще проблема уровня x86. По крайней мере на x86-64 и ret без параметров, и после call никаких действий с регистром rsp не производится. А если нигде не чистится, значит что? Значит не марается ;)
скалогрыз писал(а):Более того в Си, есть механизм произвольного количества переданных параметров. (на самом деле это дополнительный скрытый параметр о количестве переданных параметров, + сами параметры)

Очень любопытно, ничего не слышал о неком скрытом параметре о количестве параметров. Можно конкретный пруф? А то всегда явно в первом параметре информацию о количестве параметров передавал в том или ином виде.
скалогрыз писал(а):Т.к. на платформах отличных от x86, параметры передаются не только через стэк, но и через регисты, произвльная передача параметров реализована сложнее. (и чуть больше походит на array of const)

Судя по ассемблерному коду (да и по документации), на x86 регистры тоже задействуются вперемешку со стеком (при cdecl и register - точно), поэтому разности в сложности реализации не вижу.
скалогрыз писал(а):FPC частично поддерживает "произвольное количество": https://www.freepascal.org/docs-html/ref/refsu87.html
но только для внешних функций. Т.е. объявить таким образом функцию самому не получится.
Можно попробовать сделать полухак: сделать объявление такой процедуры, а потом сделать её же реализацю в другом модуле, и указать есть имя на "экспорт". Тогда компоновщик сможет соединить два куска кода.
Возможно, но тогда придется создавать внешнюю библиотеку, причем не мне, а конечному юзеру. Это никак не вписывается в концепцию моего проекта, у меня юзер подключает использует мой проект в виде набора модулей. Причем юзера - по большей части еще дети.
скалогрыз писал(а):Ещё можно пойти более честным путём и использовать libffi, для производства произвольных вызовов. (поддержка разных платформ и разных типов вызовов в наличии)
Не очень понял как это прикрутить к FreePascal, но это не важно, т.к. в концепцию не вписывается.
скалогрыз писал(а):Но вообще меня немного удивляет сама задача:
Потому-что процедуру объявляет конечный пользователь моего проекта, а я ее только вызываю

а процедура как попадает в проект? через .dll (.so)?
потому что, если через dll, то вызов, как указано изначально - вполне уместен, главное соблюсти количество и тип параметров.
Полно библиотек, которые подгружаются в момент исполнения (тот же openGL)
если же процедура компилируется в проект, то вообще никаких проблем быть не должно.

Ссылку на свой проект я уже здесь давал.
Процедура компилируется в проект, проблем на целевом ARMel не было. Проблемы были на x86, но это не моя целевая платформа + вариант с cdecl решил проблемыи на x86. Вопрос был поднят только из-за опасения осложнений в будущем, т.к. используется недокументированые возможности. И еще не поздно откатить назад, т.к. задуманные фичи еще в релиз не пошли.
xdsl
постоялец
 
Сообщения: 131
Зарегистрирован: 15.01.2009 13:49:03

Re: Вызов процедуры с неверным количеством параметров

Сообщение скалогрыз » 26.10.2018 17:13:53

xdsl писал(а):Ссылку на свой проект я уже здесь давал.
Процедура компилируется в проект

как/чем? ничего такого в указанной библиотекте не нашёл. Хотя это не важно.

всё работает, потому что все параметры попадают в стэк.
т.е. пользовательская процедура работает на стэке, который чуть "толще", чем необходимо при "законном" вызове.
Но, с другой стороны, "пользовательская процедура" этим лишним стеком и не интересуется.

xdsl писал(а):По крайней мере на x86-64 и ret без параметров, и после call никаких действий с регистром rsp не производится. А если нигде не чистится, значит что? Значит не марается

добавь побольше параметров, а лучше локальных переменных! тогда он будет пытаться чистить стек перед выходом.
Сейчас всего 3 параметра, и как показывает x86-64_linux.s, они всего хорошо помещаются в регистры.
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48

Re: Вызов процедуры с неверным количеством параметров

Сообщение Mirage » 27.10.2018 03:01:14

Вместо
type tx=procedure(a:integer; b:double; c:extended; c1,c2,c3,c4,c5,c6,c7,c8,c9,a1,a2,a3,a4,a5:integer);cdecl;
Я бы сделал как-то так.
Код: Выделить всё
const
  ARGS_NUM = 100;
type
  TArgs = array[1..ARGS_NUM] of integer;
  tx = procedure(args: TArgs); cdecl;
Mirage
энтузиаст
 
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia

Re: Вызов процедуры с неверным количеством параметров

Сообщение скалогрыз » 28.10.2018 08:55:43

Mirage писал(а):Я бы сделал как-то так.

там тип вариант, а не Integer.
Но паскаль зачищает все переменные вариант, на стороне вызова (очевидно это важно, при передаче строк и/или ещё чего-нить динамического).

Т.е. опять же, если вызывать процедуру, которая осознаёт меньшее количество параметров, а все параметры переданы через стэк, то вреда не будет.
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48

Re: Вызов процедуры с неверным количеством параметров

Сообщение Mirage » 29.10.2018 01:13:11

Где вариант не понял?
Я про то, что так проще и понятнее задать макс. кол-во параметров. Точнее их размер.
Mirage
энтузиаст
 
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia

Re: Вызов процедуры с неверным количеством параметров

Сообщение скалогрыз » 29.10.2018 03:01:47

Mirage писал(а):Где вариант не понял?
Я про то, что так проще и понятнее задать макс. кол-во параметров. Точнее их размер.

на предыдущей странице:
xdsl писал(а):Поэтому максимум что я фиксирую, это устанавливаю верхнюю границу кол-ва параметров и требую для них всех тип variant.


ноги растут от сюда:
xdsl писал(а):Говорить юзеру "Объяви процедуру со 100500 параметрами, потомучто" - не есть правильно.

в целом, это проблема построения архитектуры библиотеки, и автор пытается найти её решение на техническом уровне.
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48

Re: Вызов процедуры с неверным количеством параметров

Сообщение xdsl » 30.10.2018 14:34:34

Часть, где нужна озвученная фишка, еще не в библиотеке, ее внедрение я планирую на ноябрь.
Поэтому вкратце: даю возможность юзеру, использующему библиотеку RubiRobotLib, параллельно выполнять отдельные процедуры. Это бывает нужно, когда требуется асинхронно манипулировать различными моторами, в параллель к движению робота отображать что-либо на экране, издавать звуковые сигналы при возникновении событий на датчиках, не трогая при этом основную программу и т.п. Вся инфраструктура для этого создана, все объекты библиотеки сделаны потокобезопасными. Дополнительно, для упрощения работы с параметрами процедур, создан единый механизм манипуляций вариантными переменными и перемeнными TVarRec, определены правила перевода и созданы функции гарантированного перевод таких переменных в ограниченный набор типов (integer,string,double,boolean,object,class).

Т.к. целевая аудитория библиотеки - дети класса с пятого и выше, то стараюсь максимально упростить механизм параллелизации.
Например:

Юзер определяет процедуры:
Код: Выделить всё
procedure test1(a,b,c:variant);
begin ... end;

procedure test2();
begin ... end;

procedure test3(a,b,c,d:variant);
begin ... end;

procedure test4(a:array of const);
begin ... end;


и затем в любое время может вызвать их параллельное исполнение, например так:
Код: Выделить всё
...
parallel(@test1,7,'hello',true);
...
id2:=parallel(@test2);
...
parallel(@test3,'one','two','three',4);
...
id3:=parallel(@test4,[1.2,TObject.Create,-123456]);
...


и даже когда-нибудь потом подождать завершения некоторых из них
Код: Выделить всё
...
parallelWait(id2);
...
parallelWait(id3);
...


В принципе есть даже совершенно рабочий parallelStop для гарантированного "убиения" параллельной процедуры (режим PTHREAD_CANCEL_ASYNCHRONOUS), однако его использование практикую только при завершении программы, ибо убить нить можно и при захваченном мьютексе, что оставит его в заблокированном состоянии со всеми отсюда вытекающими.

И вот чтобы не определять кучу процедурных типов для вызова пользовательских процедур, мне и нужен был сабж. А пока универсально получилось только с array of const, все остальное - костыли.
xdsl
постоялец
 
Сообщения: 131
Зарегистрирован: 15.01.2009 13:49:03

Re: Вызов процедуры с неверным количеством параметров

Сообщение Cheb » 01.11.2018 12:37:09

А чем array of const не устраивает?
А может, требовать чтобы параметр был один и загонять всё в массив?
Зачем извращаться-то?
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Вызов процедуры с неверным количеством параметров

Сообщение скалогрыз » 01.11.2018 16:52:05

Cheb писал(а):А чем array of const не устраивает?
Зачем извращаться-то?

xdsl писал(а):Вариант с константным массивом у меня уже давно реализован, но это менее удобно, чем прямой доступ к параметрам. Поэтому ищу альтернативу.

очевидно, что он не хочет заставлять детей писать код для работы с массивом:
Код: Выделить всё
procedure test3(const args: array of variant);
var
  i : integer;
  s : string;
begin
  i := args[0];
  s:=arts[1];
end;


xdsl писал(а):и затем в любое время может вызвать их параллельное исполнение, например так:
parallel(@test3,'one','two','three',4)

Самая большая проблема начнёться, когда количество параметров будет меньше, чем в ожидаемой test3.
ты как-то будешь проверять, что количество параметров не меньше, чем в test3? (Как, имхо, не проверять правильность вызова нельзя.)
(потому что опций у тебя не так много - либо использовать run-time type info информацию, либо отладочную (dwarf))

Было бы удобно, если бы компилятор мог проверять параметры на соответствие вызываемой через parallel() процедуры.
Требуется, чтобы поточность была частью языка.

Правильно ли я понимаю, что процедуры test1, test2 (и т.д.), это не произвольные процедуры, а процедуры смысл и список параметров которых диктуются самой библиотекой?

Добавлено спустя 13 часов 26 минут 14 секунд:
кстати! насчёт RTTI (почему "да") - потому что в него (давно) завезли Invoke().
вопрос только в том, ограничен ли этот ввоз trunk-ом.
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48

Пред.

Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 18

Рейтинг@Mail.ru