-
Notifications
You must be signed in to change notification settings - Fork 0
/
fwc-abi-standard.tex
155 lines (90 loc) · 39.4 KB
/
fwc-abi-standard.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
% chapter included in forwardcom.tex
\documentclass[forwardcom.tex]{subfiles}
\begin{document}
\chapter{Стандартизация ABI и программной экосистемы} \label{StandardizationOfAbi}
Цель ForwardCom --- вертикальное перепроектирование, определяющее новые стандарты, не только для набора команд, но также и для использующего его программного обеспечения, что будет иметь следующие преимущества.
\begin{itemize}
\item Будут совместимы разные компиляторы. Одни и те же библиотеки функций смогут использоваться с разными компиляторами.
\item Будут совместимы разные языки программирования. Будет возможной компиляция разных частей программы на разных языках программирования. Будет возможной компиляция библиотеки функций на языке программирования, отличающемся от использующей её программы.
\item Будут совместимы отладчики, профилировщики, и другие средства разработки.
\item Будут совместимы разные операционные системы. Будет возможным использование одних и тех же библиотек функций в разных операционных системах, за исключением случая использования специфичных для системы функций.
\end{itemize}
В предыдущей главе была описана стандартизация системных вызовов, системных функций, и сообщений об ошибках. В настоящей главе обсуждается стандартизация следующих аспектов программной экосистемы.
\begin{itemize}
\item Поддержка компилятором.
\item Представление двоичных данных.
\item Соглашения вызова для функций.
\item Соглашения об использовании регистров.
\item Искажение имён для перегрузки функций.
\item Двоичный формат объектных файлов и исполняемых файлов.
\item Формат и методы компоновки для библиотек функций.
\item Обработка исключений и раскрутка стека.
\item Отладочная информация.
\item Синтаксис языка ассемблера.
\end{itemize}
\section{Поддержка компилятором} \label{compilerSupport}
Компилятор может иметь три разных уровня поддержки векторных регистров переменной длины.
\subsubsection{Уровень 1}
Компилятор не будет использовать векторы переменной длины. Компилятор может вызывать векторную функцию, находящуюся в библиотеке функций, со скалярным параметром, если в скалярной версии функция недоступна.
\subsubsection{Уровень 2}
Компилятор может вызывать векторные функции, но не порождать такие функции. Компилятор может автоматически векторизовать цикл и вызывать векторную библиотечную функцию из такого цикла.
\subsubsection{Уровень 3}
Полная поддержка. Компилятор поддерживает типы данных для векторов переменной длины. Эти типы данных могут использоваться для переменных, параметров функций, и возвращаемых функциями значений. Векторы переменной длины не могут включаться в структуры, классы, или объединения, поскольку такие составные типы должны иметь известные размеры. Поддержка векторов переменной длины в статических и глобальных переменных --- необязательна. Общие операции над векторами переменной длины могут указываться явно, включая опции для применения булевых векторных масок.
\subsubsection{Другие качества компилятора}
Компилятор может поддерживать арифметику указателей для указателей на функции, чтобы явно писать компактные таблицы вызовов с относительными адресами. Разность между двумя указателями на функции следует умножать на размер слова кода, равный 4 байтам. Без этой возможности указатели на функции должны приводиться к типу целочисленных указателей, и обратно.
Компилятор может иметь поддержку обнаружения целочисленного и вещественного переполнений, и других численных ошибок, в блоках try--catch, используя один из методов, обсуждённых на с.~\pageref{integerOverflowDetection}.
Компилятор может поддерживать проверку границ массивов, используя индексный адресный режим с границами или команду условной ловушки.
\section{Представление двоичных данных} \label{binaryDataRepresentation}
Данные в ОЗУ хранятся в формате с прямым порядком байтов. По поводу обоснования см. с.~\pageref{endianness}.
Целочисленные переменные представляются 8, 16, 32, 64, и, необязательно, 128 разрядами, знаковые и беззнаковые. Знаковые целые числа используют представление в виде дополнения до двух. Целочисленное переполнение приводит к заворачиванию, за исключением команд для арифметики с насыщением.
Вещественные числа кодируются с одинарной (32 разряда), двойной (64 разряда), и, необязательно, четырёхкратной (128 разрядов) точностью, следуя стандарту IEEE Standard 754-2008 или более позднему. Половинная точность (16 разрядов) необязательна, и используется в непосредственно заданных константах. Вычисления с половинной точностью не поддерживаются, но преобразования между половинной и одинарной точностью --- (необязательно) поддерживаются.
Как обсуждалось на с.~\pageref{nanPropagation}, вещественные переменные со значениями NAN могут содержать диагностические сведения о том, что вызвало ошибку.
Логические переменные хранятся как целые числа, по меньшей мере 8--разрядные, со значениями 0 и 1 для FALSE и TRUE. У логической переменной используется только разряд №0, тогда как другие разряды игнорируются. Это правило делает возможным использование логических переменных в качестве масок, и для реализации логических функций, таких, как И, ИЛИ, ИСКЛЮЧАЮЩЕЕ ИЛИ, и НЕ, с помощью простых поразрядных команд, а не с помощью метода, используемого во многих нынешних системах, имеющих ветвление для каждой проверки переменной, если всё целое число --- не ноль. Команда ветвления нужна при компиляции выражений вида (A \&\& B) и (A \textbar\textbar{} B), только если вычисление B имеет побочные эффекты.
Все переменные, размер которых не более 8 байт, следует хранить, используя их естественное выравнивание.
Массивы, размер которых не меньше 8 байт, обязаны храниться по адресам, кратным 8. Можно рекомендовать выравнивать большие массивы на размер строки кэша.
Многомерные массивы хранятся по строкам, за исключением тех языков программирования, которые делают это невозможным.
Текстовые строки могут храниться в зависящей от языка форме, но для системных функций и для функций, предназначенных для совместимости со всеми языками программирования, необходима стандартизированная форма. Предлагаемый стандарт использует кодировку UTF-8. Длина строки может определяться завершающим нулём, спецификатором длины, или и тем, и другим. Обоснование таково. Для текстовых строк, подходящих для чтения человеком, время обработки процессором в большинстве случаев несущественно, хотя менее компактно для некоторых азиатских языков. UTF-8 --- наиболее широко используемая в интернете кодировка.
\section{Дальнейшие соглашения для объектно--ориентированных языков}
Объектно--ориентированные языки требуют дальнейших стандартов, для двоичного представления специальных черт, таких, как таблицы виртуальных функций, определение типа во время выполнения, и т.п.
Эти детали обязательно должны быть стандартизированы в рамках каждого языка программирования, ради совместимости разных компиляторов, и, если возможно, также разных языков программирования, имеющих совместимые свойства.
Указатели на члены следует реализовывать способом, для которого важнее хорошая производительность в общем случае, когда требуется лишь простое смещение (для данных) или простой указатель (для функции), тогда как дополнительные сведения для запутанного случая множественного наследования добавляются только тогда, когда это необходимо.
\section{Соглашения о вызовах функций} \label{functionCallingConventions}
Вызовы функций будут использовать регистры для параметров, насколько это вообще возможно. Целые числа размером до 64 разрядов, указатели, ссылки, и булевы скаляры передаются в регистрах общего назначения. Векторные параметры могут иметь переменную длину. Вещественные скаляры, векторы с элементами любого типа, имеющие фиксированную длину, вплоть до 16 байт, и векторы переменной длины передаются в векторных регистрах.
Первые 16 параметров функции, помещающиеся регистры общего назначения, передаются в регистрах r0--r15. Первые 16 параметров, помещающиеся в векторные регистры, передаются в регистрах v0--v15. Длина векторного параметра переменной длины содержится в том же векторном регистре, который содержит данные.
Составные типы передаются в векторных регистрах, если их можно рассматривать как \glqq простые кортежи\grqq, размером не более 16 байт. Простой кортеж --- это структура или класс, или инкапсулированный массив, для которого все элементы имеют один и тот же тип, не являющийся указателем. Объединение трактуется как структура, соответствующая своему первому элементу.
Параметры, не помещающиеся в один регистр, передаётся через указатель на находящийся в памяти объект, память под который выделена вызывающим. Это применимо к структура и классам с элементами разных типов, или имеющим размер, больший 16 байт, а также применимо к объектам, требующим специальной обработки, такой, как нестандартные копирующий конструктор или деструктор, и к объектам, требующим дополнительной неявной памяти, таким, как виртуальные функции--члены. Вызов любого копирующего конструктора или деструктора --- дело вызывающего.
Если для всех параметров не хватает регистров, то дополнительные параметры предоставляются в виде списка, который может храниться где--то в памяти. Указатель на этот список параметров передаётся в регистре общего назначения. Такой список также используется, если список параметров имеет переменную длину. Более одного списка параметров быть не может, поскольку один и тот же список используется для всех целей.
Правила для списка параметров следующие. Список параметров используется, если имеется более 16 параметров, помещающихся в регистры общего назначения; если имеется более 16 параметров, помещающихся в векторные регистры; или список параметров имеет переменную длину. Если имеется менее 16 параметров, помещающихся в регистрах общего назначения, то эти параметры помещаются в регистры общего назначения, а следующий свободный регистр общего назначения используется как указатель на список параметров. Если имеется 16 или более параметров, помещающихся в регистрах общего назначения, и по какой--либо причине нужен список параметров, то первые 15 параметров, помещающихся в регистрах общего назначения, помещаются в r0--r14, указатель на список --- в r15, а оставшиеся параметры, помещающиеся в регистры общего назначения, --- помещаются в список. Если имеется более 16 векторных параметров, то первые 16 векторных параметров помещаются в v0-v15, а оставшиеся векторные параметры --- помещаются в список. Все параметры в список помещаются в том порядке, в каком появляются в определении функции, независимо от типа. Переменные аргументы помещаются в список последними, поскольку в определении функции всегда появляются последними.
Список состоит из элементов, каждый из которых имеет размер 8 байт. Параметр, помещающийся в регистр общего назначения, использует один элемент. Векторный параметр, имеющий постоянную длину, не превосходящую 8 байт, использует один элемент. Векторный параметр, имеющий постоянную длину, большую 8 байт, или имеющий переменный размер, использует два элемента. Первый элемент --- длина (в байтах), а второй --- указатель на массив, содержащий вектор. Параметр, который не поместился бы в регистр, если бы таковой был свободен, передаётся в виде указателя на список, согласно тем же правилам, что и указатель, передаваемый в регистре.
Список параметров принадлежит вызванной функции, в том смысле, что ей разрешается модифицировать параметры списка, если они не описаны как константные. То же применимо к массивам и объектам с указателем в списке. Вызывающий может полагаться на неизменность параметров списка только в том случае, если они описаны как константные. Вызывающий не должен помещать список в месте, в котором он не может модифицироваться другими потоками.
Возвращаемое функцией значение находится в r0 или v0, используя те же правила, что и для параметров функций. Множественные возвращаемые значения (если они допускаются языком программирования), если это возможно, трактуются как кортежи, и возвращаются в v0. Множественные возвращаемые значения разных типов могут возвращаться в нескольких регистрах, но, как правило, лучше трактовать множественные возвращаемые значения как структуру, ради совместимости с другими языками программирования, не позволяющими множественные возвращаемые значения.
Возвращаемое значение, не помещающееся в регистр, возвращается в области, выделенном вызывающей функцией, через указатель, переданный вызывающей функцией в r0, и возвращаемый в r0. Любые конструкторы вызываются вызванной функцией.
Указатель \glqq this\grqq\ для функции, являющейся членом класса, передаётся в r0, кроме случая, когда r0 используется для возвращаемого объекта. Тогда \glqq this\grqq\ передаётся в r1.
\subsubsection{Обоснование}
Намного эффективнее передавать параметры в регистрах, чем через стек. Настоящее предложение позволяет вплоть до 32 параметров, включая векторы переменной длины, передавать в регистрах, оставляя 15 регистров общего назначения и 16 векторных регистров для использования функцией в других целях. Это охватывает почти все практические случаи, так что параметры редко потребуется хранить в памяти.
Всё же у нас обязательно должны иметься точные правила для охвата неограниченного количества параметров, если в языке программирования нет ограничения на количество параметров. Мы помещаем любые дополнительные параметры в список, а не в стек, как делает большинство других систем. Основная причина этого состоит в том, чтобы сделать программное обеспечение независящим от того, есть ли отдельный стек вызова, или один и тот же стек используется и для адресов возврата, и для локальных переменных. Адреса параметров, расположенных в стеке, зависели бы от того, имеется ли в том же самом стеке адрес возврата, или нет. Метод списка имеет и другие преимущества. Не будет рассогласования порядка параметров в стеке и с тем, очищается ли стек вызывающей функцией или вызываемой. Список может повторно использоваться вызывающей функцией для многократных вызовов, если параметры являются константами, а вызываемая функция может повторно использовать список параметров переменной длины, передавая его в другую функцию. Гарантируется, что функция выполняет возврат управления должным образом, без порчи стека, даже если вызывающая и вызываемая функции не согласованы по количеству параметров. Хвостовые вызовы возможны во всех случаях, независимо от количества и типов параметров.
\section{Соглашения об использовании регистров} \label{registerUsageConvention}
У большинства систем имеются правила, гласящие, что некоторые регистры имеют статус сохраняемых вызываемой функцией. Это означает, что функция должна сохранять эти регистры и восстанавливать их перед возвратом управления, если они используются. Вызывающая функция может тогда полагаться на то, что эти регистры остаются неизменными после вызова функции.
У нынешних систем имеется проблема с назначением статуса сохраняемых при вызове для векторных регистров. Последующие версии ЦП могут иметь более длинные векторные регистры, а команды сохранения более длинных регистров ещё не были определены. У некоторых систем теперь, из--за неудачного прогноза, есть статус сохраняемой при вызове для части векторного регистра. В имеющихся системах невозможно сохранять векторные регистры способом, который был бы совместим с последующими расширениями.
В дизайне ForwardCom эта проблема решается с помощью векторов переменной длины. Можно сохранять и восстанавливать векторный регистр любой длины, даже если эта длина не поддерживалась на момент компиляции кода. Также можно узнать, насколько длинные векторные регистры используются на самом деле, поскольку длина вектора сохраняется в самом регистре, так что нам нужно сохранять лишь ту часть регистра, которая действительно используется. Для этой цели спроектированы команды save\_cp и restore\_cp (см. с.~\pageref{saveRestoreVectorRegisters}). Неиспользуемые векторные регистры для сохранения будут использовать лишь небольшое пространство.
Для сохранения векторных регистров, если они длинны, всё ещё требуется много места в кэше. Следовательно, мы хотим минимизировать необходимость сохранения регистров. Предлагается иметь, на выбор, два разных метода сохранения, описываемых ниже.
\subsubsection{Метод 1}
Этот метод используется по умолчанию, может использоваться во всех случаях, но не самый эффективный. Правило простое: регистры r16--r31 и v16--v31 имеют статус сохраняемых при вызове.
Функция может свободно использовать регистры r0--r15 и v0--v15. Шестнадцать регистров каждого типа --- достаточно для большинства функций. Если функции нужны дополнительные регистры, то она обязана их сохранять.
Все системные регистры и специальные регистры имеют статус сохраняемых при вызове, исключая функции, предназначенные для манипулирования с этими регистрами.
\subsubsection{Метод 2}
Он будет более эффективным, если мы действительно знаем, какие регистры используются в каждой функции. Если функция A вызывает функцию B, и A знает, какие регистры использует B, то A может просто выбрать некоторые регистры, которые не используются функцией B, для любых данных, которые не должны изменяться после вызова функции B. Даже для длинной цепочки вложенных вызовов функций можно избежать необходимости в сохранении каких--либо регистров, до тех пор, пока регистров достаточно.
Если функции A и B компилируются совместно, в одном и том же процессе, то компилятор легко может управлять этими сведениями. Но если A и B компилируются раздельно, то нам необходимо сохранять необходимые сведения о том, какие используются регистры, что возможно для формата объектных файлов, описанного на с.~\pageref{objectFileFormat}. Сведения об используемых регистрах обязательно должны сохраняться в откомпилированном объектном файле или файле библиотеки, а не в каком--либо ином файле, который мог бы быть и рассинхронизирован.
Функцию B предпочтительнее откомпилировать в объектном файле первой. Этот объектный файл обязательно должен содержать сведения о том, какие регистры модифицируются функцией B. Необходимые сведения --- просто 64--разрядное число, с одним разрядом на каждый модифицируемый регистр (разряды №№0--31 --- для r0--r31, и разряды №№32--63 --- для v0--v31). Любые регистры, используемые для передачи параметров и возврата значения из функции, также помечаются, если они модифицируются функцией.
Когда затем компилируется функция A, то компилятор просмотрит объектный файл для функции B, чтобы посмотреть, какие регистры та модифицирует. Компилятор выберет некоторые из регистров, не модифицируемых функцией B, для данных, которые не должны измениться после вызова функции B. Регистры, используемые функцией B, в функции A могут преимущественно использоваться для временных переменных, которые не нужно сохранять перед вызовом функции B. Подобным образом, будет выгодно использовать те же самые регистры для многих временных переменных, если их времена жизни не перекрываются, чтобы модифицировать как можно меньше регистров. Объектный файл для A будет содержать список регистров, модифицируемых функцией A, включая все регистры, модифицируемые функцией B, и любой другой функцией, которую может вызвать A. Объектный файл для A содержит ссылку на функцию B. Эта ссылка обязательно должна содержать сведения о том, какие регистры функция A ожидает модифицируемыми функцией B. Если B позднее перекомпилируется, и новая версия функции B модифицирует больше регистров, то компоновщик обнаружит нестыковку, и сообщит о перекомпиляции функции A.
Если, по какой--то причине, функция A компилируется перед функцией B, либо во время компиляции функции A сведения о B недоступны, то компилятор сделает предположения об использовании регистров функцией B. Предположение, принятое по умолчанию, указано в методе 1. Функция A может позднее перекомпилирована, если функция B не соответствует этим допущениям, или просто для улучшения эффективности.
Если две функции, A и B, взаимно вызывают друг друга, то самым лёгким решением является положится на метод 1. Функциям, всё ещё, следует включать сведения об использовании регистров в свои объектные файлы.
Компилятору следует предпочесть выделение сначала меньшего числа регистров, чтобы минимизировать проблему, заключающуюся в использовании разных регистров разными библиотечными функциями. Можно (не обязательно) в вызывающем пропустить r7 и v7, чтобы использовать для масок.
Головной функции программы разрешается использовать метод 2, и модифицировать все регистры, если она включает необходимые сведения в свой объектный файл.
Объектные файлы, содержащиеся в библиотеке функций, должны включать сведения об использовании регистров.
К системным функциям и драйверам устройств обращаться так же, как к нормальным библиотечным функциям, нельзя (см. с.~\pageref{systemCallIDSystem}). Системные функции обязаны подчиняться правилам метода 2, но системе следует предоставить метод получения сведений об использовании регистров каждой системной функцией, что может быть полезно для компиляции \glqq на лету\glqq.
\section{Искажение имён для перегрузки функций} \label{nameMangling}
Языки программирования, поддерживающие перегрузку функций, используют внутренние имена, с добавленными к именам функций префиксами и суффиксами, чтобы различать функции с одним и тем же именем, но разными параметрами или в разных классах, или в разных пространствах имён. Используется много разных схем искажения имён, и некоторые из них --- недокументированы. Необходимо стандартизировать схему искажения имён, чтобы сделать возможным смешивание разных компиляторов или разных языков программирования.
Наиболее распространённые схемы искажения имён --- схемы Microsoft и Gnu. Схема Microsoft использует символы, которые не могут встречаться в именах функций (?@\$), что предотвращает конфликты имён, но делает невозможным прямой вызов функции с искажённым именем, или трансляцию, например, C++ в C. Схема Gnu порождает искажённые имена, выглядящие неуклюже, но не содержащие специальных символов, мешающих непосредственному вызову искажённого имени. Следовательно, предложение состоит в использовании схемы искажения имён Gnu (версии 4 или более поздней), с необходимыми добавлениями для векторов переменной длины и т.п.
Функции с искажёнными именами могут (не обязательно) дополнять искажённое имя простым (неискажённым) именем, в виде слабого публичного псевдонима в объектном файле. Это облегчит вызов функции из других языков программирования, в которых искажения имён нет. Слабая компоновка псевдонима предотвращает выдачу компоновщиком сообщений о дублирующихся именах, разве что вызов по имени неоднозначен.
\end{document}