Розуміння "спотворення" (mangling) імен в Ada

Зміст

Розуміння "спотворення" (mangling) імен в Ada#

Ця стаття про "спотворення" імен в Ada, по суті, є документацією, витягнутою з вихідного коду пакета exp_dbug.ads (commit 4da027d), переформатованою в Markdown для легшого читання.

Версія англіїскою.

Примітка:

Відповідно до специфікації DWARF, певні імена типів також можуть бути закодовані для надання додаткової інформації налагоджувальнику, але ця практика застаріла, і деякі кодування, описані нижче, більше не генеруються за замовчуванням (вони позначені як OBSOLETE ).

Кодування та кваліфікація назв сутностей#

У цьому розділі описано, як назви сутностей кодуються у згенерованій налагоджувальній інформації.

Сутність в Ada має ім'я виду X.Y.Z ... E, де X, Y, Z -- це області видимості, що її охоплюють (не враховуючи Standard на початку).

Кодування імені відповідає цій базовій схемі кваліфікованого найменування, де кодування окремих імен сутностей відповідає описаному в Namet (тобто окремі імена, присутні в оригінальному джерелі, записуються в нижньому регістрі, а "верхня половинна" та широкі (wide) символи кодуються, як описано в Namet). Великі літери використовуються лише для сутностей, згенерованих компілятором.

Існує два випадки: глобальні сутності та локальні сутності. У більш формальних термінах, локальні сутності – це ті, що мають динамічну охоплюючу область видимості, а глобальні сутності знаходяться на рівні бібліотеки, за винятком того, що ми завжди вважаємо процедури глобальними сутностями, навіть якщо вони вкладені (це тому, що на рівні налагоджувача ім'я процедури посилається на код, а код дійсно є глобальною сутністю, включаючи випадок вкладених процедур). Крім того, ми також вважаємо всі типи глобальними сутностями, навіть якщо вони визначені всередині процедури.

Причина трактування всіх імен типів як глобальних сутностей полягає в тому, що низка наших кодувань типів працює з пов'язаними іменами типів, і нам потрібна повна кваліфікація, щоб зберегти цю унікальність.

Для глобальних сутностей закодована назва включає всі компоненти повністю розгорнутої назви (але без Standard на початку). Наприклад, якщо дочірній пакет бібліотечного рівня PQ має вбудований пакет R, і в цьому вбудованому пакеті є сутність з назвою S, закодована назва включатиме компоненти p.q.r.s.

Для локальних сутностей закодоване ім'я включає лише компоненти до динамічної області видимості, що її оточує (окрім блоку). Під час виконання така динамічна область видимості є підпрограмою, а формати налагодження знають про локальні змінні процедур, тому повна кваліфікація для таких сутностей не потрібна. Зокрема, це означає, що прямі локальні змінні процедури не кваліфікуються.

Для сутностей Ghost кодування додає префікс ___ghost_, щоб допомогти виявити витоки ігнорованих сутностей Ghost у «життєвому» просторі. Ігноровані сутності Ghost та будь-який код, пов'язаний з ними, повинні бути видалені компілятором під час проходження пост-обробки. Як результат, об'єктні файли не повинні містити жодних екземплярів цього префікса.

Як приклад локальної домовленості про імена, розглянемо процедуру V.W з локальною змінною X та вкладеним блоком Y, що містить сутність Z. Повні імена сутностей X та Z:

    V.W.X
    V.W.Y.Z

але оскільки V.W є підпрограмою, закодовані імена зрештою будуть закодовані лише

    x
    y.z

Роздільні крапки перетворюються на подвійне підкреслення

Обробка перевантаження імен#

Наведена вище схема є неповною для перевантажених підпрограм, оскільки перевантаження може законно призвести до виникнення двох сутностей з абсолютно однаковими повними іменами. Щоб розрізнити записи в наборі перевантажених підпрограм, закодовані імена серіалізуються шляхом додавання суфікса:

    __nn  (two underscores)

де nn –- порядковий номер (2 для другої перевантаженої функції, 3 для третьої тощо). Суфікс __1 завжди опускається (тобто відсутність суфікса означає перший екземпляр).

Ці назви починаються зі звичайного повного уточнення. Наприклад, третій екземпляр підпрограми qrs у пакеті yz матиме назву:

    yz__qrs__3

Більш тонкий випадок виникає з сутностями, оголошеними в перевантажених підпрограмах. Якщо у нас є дві перевантажені підпрограми, і обидві оголошують сутність xyz , то повністю розгорнуте ім'я двох xyz буде однаковим. Щоб розрізнити їх, ми додаємо однаковий суфікс __n в кінці внутрішніх імен сутностей.

У складніших випадках ми можемо мати кілька рівнів перевантаження, і ми повинні переконатися, що розрізняємо, про яку кінцеву декларативну область йдеться. Для цього ми використовуємо складніший суфікс, який має вигляд:

    __nn_nn_nn ...

де значення nn – це номери омонімів, необхідні для будь-якої з кваліфікаційних сутностей, розділені одним символом підкреслення. Якщо всі значення nn дорівнюють 1, суфікс опускається. В іншому випадку суфікс присутній (включаючи будь-які значення 1). Наведений нижче приклад показує, як працює це додавання суфіксів.

  package body Yz is
    procedure Qrs is               -- Name is yz__qrs
      procedure Tuv is ... end;    -- Name is yz__qrs__tuv
    begin ... end Qrs;

    procedure Qrs (X: Int) is      -- Name is yz__qrs__2
      procedure Tuv is ... end;    -- Name is yz__qrs__tuv__2_1
      procedure Tuv (X: Int) is    -- Name is yz__qrs__tuv__2_2
      begin ... end Tuv;

      procedure Tuv (X: Float) is  -- Name is yz__qrs__tuv__2_3
        type m is new float;       -- Name is yz__qrs__tuv__m__2_3
      begin ... end Tuv;
    begin ... end Qrs;
  end Yz;

Імена операторів#

Застосування вищезазначених правил до імен операторів призведе до імен у лапках, які зазвичай не дозволені асемблерами та лінкерами, і навіть якщо вони дозволені, це буде дивно та важко для обробки. Щоб уникнути цієї проблеми, імена операторів кодуються наступним чином:

Ім'я

Оператор

Ім'я

Оператор

Oabs

abs

Ole

<=

Oand

and

Ogt

>

Omod

mod

Oge

>=

Onot

not

Oadd

+

Oor

or

Osubtract

-

Orem

rem

Oconcat

&

Oxor

xor

Omultiply

*

Oeq

=

Odivide

/

One

/=

Oexpon

**

Olt

<

Ці імена починаються зі звичайної повної кваліфікації та доповнюються суфіксом перевантажувального ідентифікатора. Наприклад, другий оператор «=», визначений у пакеті Extra.Messages, матиме назву:

    extra__messages__Oeq__2

Вирішення інших конфліктів імен#

Можна вважати, що наведена вище схема є завершеною, але в Ada 95 повної кваліфікації недостатньо для однозначної ідентифікації сутності в програмі, навіть якщо вона не є перевантаженою підпрограмою. Існують дві можливі плутанини:

   a.b

     interpretation 1: entity b in body of package a
     interpretation 2: child procedure b of package a

   a.b.c

     interpretation 1: entity c in child package a.b
     interpretation 2: entity c in nested package b in body of a

В обох випадках обидві інтерпретації цілком законні в межах однієї програми. Це дещо дивно, оскільки, звичайно, в Ada 83 повної кваліфікації було достатньо, але не в Ada 95. В результаті вищезгадана схема може призвести до дублювання імен. Це не було б так погано, якби ефект обмежувався лише налагоджувальною інформацією, але насправді в обох вищезгаданих випадках обидва символи можуть бути зовнішніми іменами, і тому ми маємо реальну проблему конфлікту імен.

Щоб вирішити цю ситуацію, ми пропонуємо два додаткові правила кодування імен:

  • По-перше: усім іменам підпрограм бібліотек передує рядок _ada_ (що не призводить до дублювання, оскільки звичайні імена Ada ніколи не можуть починатися з символу підкреслення). Це не тільки вирішує перший випадок дублювання, але й вирішує іншу прагматичну проблему, яка полягає в тому, що в іншому випадку процедури Ada можуть генерувати імена, які конфліктують з існуючими іменами системних функцій. Найбільш помітно, що ми можемо мати конфлікти у випадку процедури Main з C main, яка в деяких системах завжди присутня.

  • По-друге, для випадку, коли вкладені пакети, оголошені в тілах пакетів, можуть спричинити проблеми, ми додаємо суфікс, який показує, які сутності у списку є вкладеними в тіло пакета пакетами, тобто пакетами, специфікація яких знаходиться всередині тіла пакета. Правила такі: для заданого списку імен у кваліфікованому імені name1.name2....

    • Якщо жодна з них не є вкладеною в тіло пакетною сутністю, то суфікс відсутній

    • Якщо хоча б одна є вкладеною в тіло сутністю пакета, то суфікс –- це X, за яким йде рядок b та n (b = сутність вкладеного в тіло пакета, n = не вкладений в тіло пакет).

    • У цьому рядку є один елемент для кожної сутності в закодованому розширеному імені, окрім першого (правила такі, що перший елемент закодованого розширеного імені ніколи не може бути вкладеним у тіло пакетом). Завершальні n пропускаються, як і останній b (повинен бути принаймні один b, інакше ми взагалі не генеруватимемо суфікс).

Наприклад, припустимо, що у нас є

  package x is
     pragma Elaborate_Body;
     m1 : integer;                                    -- #1
  end x;

  package body x is
    package y is m2 : integer; end y;                 -- #2
    package body y is
       package z is r : integer; end z;               -- #3
    end;
    m3 : integer;                                     -- #4
  end x;

  package x.y is
     pragma Elaborate_Body;
     m2 : integer;                                    -- #5
  end x.y;

  package body x.y is
     m3 : integer;                                    -- #6
     procedure j is                                   -- #7
       package k is
          z : integer;                                -- #8
       end k;
     begin
        null;
     end j;
  end x.y;

  procedure x.m3 is begin null; end;                  -- #9

Тоді кодування будуть такими:

  #1.  x__m1             (no BNPE's in sight)
  #2.  x__y__m2X         (y is a BNPE)
  #3.  x__y__z__rXb      (y is a BNPE, so is z)
  #4.  x__m3             (no BNPE's in sight)
  #5.  x__y__m2          (no BNPE's in sight)
  #6.  x__y__m3          (no BNPE's in signt)
  #7.  x__y__j           (no BNPE's in sight)
  #8.  k__z              (no BNPE's, only up to procedure)
  #9   _ada_x__m3        (library-level subprogram)

Зверніть увагу, що тут є випадки обох видів потенційних конфліктів імен, і наведені вище приклади показують, як кодування уникають конфлікту наступним чином:

  • Рядки #4 та #9 посилаються на сутність x.m3, але #9 є підпрограмою бібліотечного рівня, тому перед нею стоїть рядок _ada_, який відрізняє її від сутності тіла пакета.

  • Рядки #2 та #5 посилаються на сутність xym2, але перший екземпляр знаходиться всередині вкладеного в тіло пакета y, тому є суфікс X, щоб відрізнити його від сутності дочірньої бібліотеки.

Зверніть увагу, що літерали перерахування ніколи не потребують суфіксів типу Xb, оскільки на них ніколи не посилаються за допомогою глобальних зовнішніх імен.

Назви інтерфейсів#

Примітка: якщо ім'я інтерфейсу присутнє, то зовнішнє ім'я береться з вказаного імені інтерфейсу. Враховуючи поточні обмеження gcc backend, це означає, що ім'я налагодження також встановлюється як ім'я інтерфейсу, але концептуально було б можливо (і навіть бажано), щоб інформація про налагодження все ще використовувала ім'я Ada, як зазначено вище, щоб ми все ще повністю кваліфікували ім'я у фронтенді.

Кодування, пов'язані з задачами#

Кожен об'єкт-задача, визначений одним оголошенням задачи, пов'язаний з префіксом, який використовується для кваліфікації процедур, визначених у цієї задачі.

  package body P is
    task body TaskObj is
      procedure F1 is ... end;
    begin
      B;
    end TaskObj;
  end P;

Ім'я підпрограми TaskObj.F1 закодовано як p__taskobjTK__f1. Тіло підпрограми, B, міститься в підпрограмі з ім'ям p__taskobjTKB.

Кодування, пов'язані із захищеними типами#

Кожен захищений тип має пов'язаний тип-запис, який описує фактичне розташування приватних даних. На додаток до приватних компонентів типу, Corresponding_Record_Type містить один компонент типу Protection, який фактично є структурою блокування. Розмір захищеного типу під час виконання дорівнює розміру відповідного запису.

Для захищеного типу prot, Corresponding_Record_Type кодується як protV.

Операції захищеного типу кодуються наступним чином: кожна операція призводить до створення двох підпрограм: блокувальної, яка викликається ззовні об'єкта, та неблокувальної, яка використовується для викликів інших операцій на тому ж об'єкті. Операція блокування просто отримує блокування, а потім викликає неблокувальну версію. Назви всіх цих операцій мають префікс, побудований з назви типу, та суфікс P або N, залежно від того, чи є це блокувальною/неблокувальною версією операції.

Операції, що генеруються для захищених входів, дотримуються тогож кодування. Кожен опис входу призводить до створення двох підпрограм: процедури, яка містить тіло входу, та функції, яка містить обчислення бар'єру. Назви цих підпрограм містять префікс _E або _B відповідно. Назви також містять числовий суфікс, щоб зробити їх унікальними за наявності перевантажених записів.

Для декларації:

  protected type Lock is
     function  Get return Integer;
     procedure Set (X: Integer);
     entry Update  (Val : Integer);
  private
     Value : Integer := 0;
  end Lock;

створюються такі операції:

  lock_getN
  lock_getP

  lock_setN
  lock_setP

  lock_update_E1s
  lock_udpate_B2s

Якщо захищений тип реалізує хоча б один інтерфейс, створюються такі додаткові операції:

  lock_get

  lock_set

Ці операції використовуються для забезпечення перевизначення підпрограм рівня інтерфейсу та належної диспетчеризації об'єктів інтерфейсу на рівні класу. Тіла цих операцій містять виклики до відповідних захищених версій:

  function lock_get return Integer is
  begin
     return lock_getP;
  end lock_get;

  procedure lock_set (X : Integer) is
  begin
     lock_setP (X);
  end lock_set;

Обробка числових значень#

Усі числові значення тут кодуються як рядки десяткових цифр. Кодувати потрібно лише цілі значення. Від'ємне значення кодується як відповідне додатне значення, за яким слідує мала літера m для мінуса, щоб вказати, що значення від'ємне (наприклад, 2m для -2).

Інкапсульовані типи#

У деяких випадках компілятор може інкапсулювати тип, обгорнувши його в запис. Наприклад, це використовується, коли специфікація розміру або вирівнювання вимагає більшого типу. Розглянемо:

  type x is mod 2 ** 64;
  for x'size use 256;

У цьому випадку компілятор генерує запис типу x___PAD, який має одне поле з іменем F. Це одне поле має довжину 64 біти та містить фактичне значення. Такий тип доповнення використовується, коли логічне значення, яке потрібно зберегти, коротше за об'єкт, в якому воно розміщено.

Подібна інкапсуляція виконується для деяких типів упакованих масивів, у цьому випадку тип запису –- x___JM, а назва поля –- OBJECT. Це використовується у випадку упакованого масиву, що зберігається з використанням модульного представлення (див. розділ про представлення об'єктів упакованих масивів). У цьому випадку обгортання використовується для досягнення правильного позиціонування значення упакованого масиву (вирівнювання вліво/вправо в його полі залежно від порядка байтів).

Коли налагоджувач бачить об'єкт типу, ім'я якого має суфікс ___PAD або ___JM, цей тип буде записом, що містить одне поле, а ім'я цього поля буде повністю у верхньому регістрі. У цьому випадку, щоб отримати значення внутрішнього поля, слід шукати всередину, і ні ім'я зовнішньої структури, ні ім'я поля не повинні з'являтися під час виведення значення.

Аналогічно, коли налагоджувач бачить запис з іменем REP, що є типом поля всередині іншого типу запису, він повинен розглядати поля всередині REP як частину зовнішнього запису (це поле REP присутнє лише для цілей генерації коду). Запис REP не повинен відображатися в значеннях, що виводяться налагоджувачем.

Неявні типи#

Компілятор створює неявні імена типів у багатьох ситуаціях, коли тип присутній семантично, але немає конкретної назви. Наприклад:

   S : Integer range M .. N;

Тут підтип S не є цілочисельним, а радше анонімним підтипом Integer. Де можливо, компілятор генерує імена для таких анонімних типів, які пов'язані з типом, з якого отримано підтип, наступним чином:

   T name suffix

де name – це назва, з якої отримано підтип, використовуючи малі літери та символи підкреслення, а suffix починається з великої літери. Наприклад, назва для вищезазначеного оголошення може бути такою:

   TintegerS4b

Якщо налагоджувачу пропонується вказати тип сутності, а тип має суфікс імені форми T, ймовірно, доречно просто використовувати ім'я у відповіді, оскільки саме це має значення для програміста.

Модульні типи#

Оголошений тип

  type x is mod N;

кодується як піддіапазон беззнакового базового типу з нижньою межею нуль та верхньою межею N - 1. Таким чином, ми надаємо цим типам дещо нестандартну інтерпретацію: стандартна інтерпретація загалом не означатиме, що арифметичні операції над типом x виконуються за модулем N (особливо коли N не є степенем 2).

Тегові типи та розширення типів#

Тип D, похідний від тегового типу P, має поле з назвою _parent типу P, яке містить успадковані від нього поля. Тип цього поля зазвичай P, але може бути більш віддаленим предком, якщо P є нульовим розширенням цього типу.

Тег типу тегованого типу – це поле з назвою _tag типу вказівника. Якщо тип походить від іншого тегового типу, його поле _tag знаходиться в його батьківському полі _parent.

Кодування назв типів ( ЗАСТАРІЛЕ )#

У наступному типі typ – це назва типу, як її зазвичай кодують правила налагоджувача, тобто некваліфікована назва, написана малими літерами зі стандартним кодуванням символів верхньої половини та розширених символів.

Типи з фіксованою комою#

Типи з фіксованою комою кодуються за допомогою суфікса, який вказує на значення delta та Small. Сам тип є звичайним цілочисельним типом.

   typ___XF_nn_dd
   typ___XF_nn_dd_nn_dd

Перша форма використовується, коли small = delta. Значення delta (і small) задається раціональним числом nn/dd, де nn та dd – десяткові цілі числа.

Друга форма використовується, якщо small значення відрізняється від delta. У цьому випадку перше раціональне значення nn/dd відповідає delta, а друге значення – small.

Дискретні типи#

Дискретні типи кодуються суфіксом, що вказує на діапазон, у випадку, коли одна або обидві межі є дискримінантами або змінними.

Примітка: наразі ми також кодуємо відомі межі часу компіляції, якщо вони не відповідають природним межам машинного типу, але це може бути видалено в майбутньому, оскільки це є надлишковим для більшості форматів налагодження. Однак нам ніколи не потрібне XD-кодування для базових типів перерахувань, оскільки тут завжди зрозуміло, які будуть межі виходячи з загальної кількісті літералів перерахування.

   typ___XD
   typ___XDL_lowerbound
   typ___XDU_upperbound
   typ___XDLU_lowerbound__upperbound

Якщо дискретний тип є природним машинним типом (тобто його межі природним чином відповідають його розміру), то він залишається некодованим. Вищезазначені форми кодування використовуються, коли існує обмежений діапазон, який не відповідає розміру, або який має дискримінантні посилання чи інші відомі під час компіляції межі.

Перша форма використовується, якщо обидві межі є динамічними, і в цьому випадку присутні два константні об'єкти, імена яких typ___L та typ___U в тій самій області видимості, що й typ, а значення цих констант вказують на межі. З точки зору налагоджувача, це просто змінні, до яких можна отримати доступ, як і до будь-яких інших змінних. У випадку перерахування ці значення відповідають значенням Enum_Rep для нижньої та верхньої меж.

Друга форма використовується, якщо верхня межа динамічна, але нижня межа є або постійною, або залежить від дискримінанта запису, з яким пов'язаний тип. Верхня межа зберігається в константному об'єкті з іменем typ___U, як описано раніше, але нижня межа кодується безпосередньо в ім'я як десяткове ціле число або як ім'я дискримінанта.

Третя форма аналогічно використовується, якщо нижня межа динамічна, але верхня межа відома під час компіляції або є дискримінантним посиланням, і в цьому випадку нижня межа зберігається в константному об'єкті з іменем typ___L, а верхня межа кодується безпосередньо в ім'я як десяткове ціле число або як дискримінантне ім'я.

Четверта форма використовується, якщо обидві межі є дискримінантними посиланнями або значеннями, відомими під час компіляції, при цьому кодування виконується спочатку для нижньої межі, потім для верхньої межі, як описано раніше.

Зсунуті (biased) типи#

Тільки дискретні типи можуть бути зміщені, і той факт, що вони зміщені, позначається суфіксом виду:

   typ___XB_lowerbound__upperbound

Тут нижня та верхня межі – це десяткові цілі числа зі звичайним кодуванням (постфікс m) для від'ємних чисел. Упереджені типи можливі лише тоді, коли межі відомі під час компіляції, а значення представлені як беззнакові зміщення відносно заданої нижньої межі. Наприклад:

   type Q is range 10 .. 15;
   for Q'size use 3;

Блок size змушує значення типу Q у пам'яті зберігатися у зміщеній формі (наприклад, 11 буде представлено бітовим шаблоном 001).

Типи записів із полями змінної довжини#

Формати налагодження не повністю підтримують ці типи, і деякі формати взагалі не генерують жодної корисної інформації для таких типів. Щоб надати інформацію налагоджувачу, gigi створює паралельний тип у тій самій області видимості з одним із назв

  type___XVE
  type___XVU

Перше ім'я використовується для запису, а друге – для об'єднання, створеного для варіантного запису (див. нижче), якщо цей запис або об'єднання має поле змінного розміру або якщо сам запис або об'єднання має змінний розмір. Ці кодування додають суфікс до будь-яких інших кодувань, які можуть бути додані до імені типу.

Ідея полягає в тому, щоб надати всю необхідну інформацію для інтерпретації об'єктів вихідного типу у формі «виправленого» типу, який можна представити за допомогою звичайної налагоджувальної інформації.

Є три випадки, які потрібно розглянути. По-перше, деякі поля можуть мати змінні позиції, оскільки вони з'являються після полів змінної довжини. Щоб вирішити цю проблему, ми кодуємо всі позиції бітів поля спеціального типу ___XV нестандартним способом.

Ідея полягає в тому, щоб кодувати не позицію, а інформацію, яка дозволяє обчислити позицію поля з позиції попереднього поля. Алгоритм обчислення фактичних позицій усіх полів та довжини запису такий. У цьому описі нехай P представляє поточну позицію біта в записі.

  1. Ініціалізувати P рівним 0

  2. Для кожного поля в записі:

    a. Якщо задано вирівнювання (див. нижче), то за потреби округліть P до наступного кратного цього вирівнювання.

    a. Якщо задано позицію біта, то збільшити P на цю величину (тобто розглядати її як зміщення від кінця попереднього запису).

    c. Призначте P як фактичну позицію поля

    d. Обчисліть довжину L представленого поля (див. нижче) та обчисліть P'=P+L. Якщо поле не представляє варіантну частину (див. нижче, а також Variant Record Encoding), встановіть P на P'.

Вирівнювання, якщо воно присутнє, кодується в назві поля запису, яка має суфікс:

  fieldname___XVAnn

де nn після XVA вказує значення вирівнювання в одиницях пам'яті. Це кодування присутнє лише за наявності вирівнювання.

Розмір входу, описаного типом, закодованим XVE (у бітах), зазвичай дорівнює максимальному значенню, досягнутому P' на кроці 2d вище, округленому відповідно до вирівнювання запису.

По-друге, самі поля змінної довжини представлені шляхом заміни типу спеціальним типом доступу. Призначений тип цього типу доступу є вихідним типом змінної довжини, і той факт, що це поле було перетворено таким чином, сигналізується кодуванням імені поля як:

  field___XVL

де поле – це оригінальна назва поля. Якщо поле має змінну довжину та потребує кодування вирівнювання, то кодування об'єднуються за допомогою:

  field___XVLnn

Примітка: причина, чому ми змінюємо тип, полягає в тому, щоб результуючий тип не мав полів змінної довжини. Принаймні деякі формати, що використовуються для налагоджувальної інформації, просто не можуть терпіти поля змінної довжини, тому закодована інформація буде втрачена.

По-третє, у випадку запису варіантів, спеціальне об'єднання, яке містить варіанти, замінюється звичайним об'єднанням C. У цьому випадку всі позиції дорівнюють нулю.

Дискримінанти з'являються перед будь-якими полями змінної довжини, які від них залежать, за одним винятком. У деяких випадках дискримінант, що керує вибором варіантного речення, може з'явитися у списку полів типу XVE після запису для самого варіантного речення (це може статися за наявності речення представлення для типу запису у вихідній програмі). Однак, коли це трапляється, позицію дискримінанта можна визначити, спочатку застосувавши правила, описані в цьому розділі, ігноруючи варіантне речення. В результаті дискримінанти завжди можна розташувати незалежно від полів змінної довжини, які від них залежать.

Розмір запису або об'єднання ___XVE або ___XVU встановлюється на вирівнювання (у байтах) оригінального об'єкта, щоб налагоджувач міг обчислити розмір оригінального типу.

Як приклад такого кодування, розглянемо такі оголошення:

  type Q is array (1 .. V1) of Float;       -- alignment 4
  type R is array (1 .. V2) of Long_Float;  -- alignment 8

  type X is record
     A : Character;
     B : Float;
     C : String (1 .. V3);
     D : Float;
     E : Q;
     F : R;
     G : Float;
  end record;

Закодований тип виглядає так:

  type anonymousQ is access Q;
  type anonymousR is access R;

  type X___XVE is record
     A        : Character;               -- position contains 0
     B        : Float;                   -- position contains 24
     C___XVL  : access String (1 .. V3); -- position contains 0
     D___XVA4 : Float;                   -- position contains 0
     E___XVL4 : anonymousQ;              -- position contains 0
     F___XVL8 : anonymousR;              -- position contains 0
     G        : Float;                   -- position contains 0
  end record;

Будь-які розряди, записані для полів, відмінних від динамічних полів та варіантів, враховуються як для звичайних записів.

Примітка:

  1. Поле B також можна було закодувати, використовуючи нульову позицію та вирівнювання за 4, але в такому випадку перевага надається кодуванню за позицією (оскільки воно займає менше місця). У наведеному вище прикладі ми використали (недопустиме) позначення access xxx як типи полів.

  2. Поле E насправді не потребує індикації вирівнювання, але в цьому випадку процедури перетворення можуть не виявити це.

  3. Наші домовленості не охоплюють усі записи, закодовані XVE, в яких деякі, але не всі, поля мають речення представлення. Тому такі записи можуть відображатися налагоджувачами неправильно. Така ситуація не є поширеною.

Базові типи записів#

За певних обставин налагоджувачам потрібні два описи типу запису: один, який надає фактичні деталі структури базового типу (як описано в інших розділах цих коментарів), а інший, який може бути використаний для отримання інформації про конкретний підтип та розмір типізованих об'єктів. У таких випадках компілятор підставлятиме тип, ім'я якого зазвичай генерується компілятором і не має значення, окрім як ключа для отримання фактичного типу.

Зокрема, якщо це ім'я x, то ми створюємо тип запису з ім'ям x___XVS, що складається з одного поля. Ім'я цього поля відповідає імені фактичного типу, що кодується, який ми назвемо y. Тип цього єдиного поля може бути як довільним непосилальним типом, наприклад, цілочисельним типом, так і посилальним типом; в останньому випадку посилальний тип також є фактичним типом, що кодується y. Як x,так і y можуть мати відповідні типи ___XVE .

Розмір об'єктів, типізованих як x, слід отримувати зі структури x (та x___XVE, якщо це можливо), як і для звичайних типів, окрім випадків, коли існує змінна з іменем x___XVZ, яка, якщо присутня, міститиме розмір (у байтах) x. В останньому випадку розмір типу x___XVS буде не константою, а посиланням на x___XVZ.

Тип x буде або підтипом y (див. також "Підтипи варіантних записів" нижче), або міститиме одне поле типу y, або взагалі не міститиме полів. Макет, типи та положення цих полів будуть точними, якщо вони присутні. (Однак наразі налагоджувач GDB не використовує x, окрім як для визначення його розміру).

Серед іншого, типи XVS використовуються для кодування необмежених типів. Наприклад, враховуючи:

   subtype Int is INTEGER range 0..10;
   type T1 (N: Int := 0) is record
      F1: String (1 .. N);
   end record;
   type AT1 is array (INTEGER range <>) of T1;

Тип елемента для AT1 може мати тип, визначений так, ніби він був написаний:

   type at1___PAD is record F : T1; end record;
   for at1___PAD'Size use 16 * 8;

а також було б:

   type at1___PAD___XVS is record t1: reft1; end record;
   type t1 is ...
   type reft1 is <reference to t1>

Якби підтип Int був динамічним:

   subtype Int is INTEGER range 0 .. M;  -- M a variable

Тоді компілятор також згенерує оголошення, ефект якого буде таким

   at1___PAD___XVZ: constant Integer := 32 + M * 8 + padding term;

Не всі необмежені типи кодуються таким чином; конвенція XVS може бути непотрібною для необмежених типів фіксованого розміру. Однак, це кодування завжди необхідне, коли тип підкомпонента (тип елемента масиву або тип поля запису) є необмеженим типом запису, деякі компоненти якого залежать від дискримінантних значень.

Типи масивів#

Оскільки налагоджувач не має можливості отримати підтипи індексів для типу масиву, ми створюємо тип, який має назву типу масиву, за якою йде ___XA, і є типом запису, типи полів якого є відповідними типами для меж (а імена полів якого є іменами цих типів).

Для економії місця ми не створюємо цей тип, якщо один з індексних типів не є типом перелічення, не має нижньої або верхньої межі змінної або не є упередженим типом.

З огляду на повне кодування цих типів (див. опис кодування дискретних типів вище), це означає, що вся необхідна інформація для адресації масивів доступна. У деяких форматах налагодження деяка або вся інформація про межі може бути доступна надлишково, особливо у випадку з фіксованою комою, але ця інформація в будь-якому випадку може бути проігнорована налагоджувачем.

Перейменування#

Інформація для налагодження генерується для перейменування винятків, об'єктів, пакетів та підпрограм (загальні перейменування не є суттєвими, оскільки загальні шаблони не є актуальними під час налагодження).

Розглянемо оголошення перейменування форми

  x : typ renames y;

Існує один випадок, коли не потрібна спеціальна інформація для налагодження, а саме випадок перейменування об'єкта, коли серверна частина виділяє посилання для перейменованої змінної, а сутність x є цим посиланням. Налагоджувач може обробити цей випадок без будь-якої спеціальної обробки чи кодування (він не знатиме, що це було перейменування, але це не має значення).

Усі інші випадки перейменування генерують фіктивну змінну для сутності, ім'я якої має вигляд:

  x___XR_...    for an object renaming
  x___XRE_...   for an exception renaming
  x___XRP_...   for a package renaming

і де ... представляє суфікс, який описує структуру імені об'єкта, заданого під час перейменування (див. деталі нижче).

Ім'я повністю кваліфікується звичайним чином, тобто кваліфікується так само, як і сутність x. У випадку перейменування пакета, де x є дочірнім елементом, кваліфікація включає ім'я батьківського елемента, щоб усунути неоднозначність дочірніх елементів з однаковим простим ім'ям та (обов'язково) різними батьківськими елементами.

Примітка:

Перейменування підпрограм наразі не закодовано

Суфікс імені змінної, що описує перейменований об'єкт, визначено для використання такого кодування:

  • Для випадку простої сутності, де y – це просто ім'я сутності, суфікс має вигляд:

    • y___XE

      тобто суфікс має одне поле, перша частина якого відповідає імені y, за яким йде роздільник ___, що закінчується послідовністю XE. Частина імені сутності повністю кваліфікується звичайним чином. Ця ж схема іменування дотримується для всіх форм закодованих перейменувань, які перейменовують просту сутність.

  • У випадку перейменування об'єкта, де y є вибраним компонентом або індексованим компонентом, ім'я змінної доповнюється додатковими полями, які надають детальну інформацію про компоненти. Ім'я починається, як і вище, з імені y___XE, що вказує на сутність об'єкта зовнішнього рівня. Потім можна вказати серію операцій вибору та індексації наступним чином:

    • Індексований компонент

      Серія значень нижнього індексу з'являється послідовно, число відповідає кількості вимірів масиву. Нижні індекси мають одну з двох наступних форм:

      • XSnnn

        Тут nnn – це константа, закодована як десяткове ціле число (значення pos для випадку типу перелічення). Від'ємні значення, як завжди, мають завершальну літеру m.

      • XSe

        Тут e – це (некваліфікована) назва константної сутності в тій самій області видимості, що й перейменування, яка містить значення нижнього індексу.

    • Зріз

      Для випадку зрізу ми маємо два записи. Перший стосується нижньої межі зрізу та має вигляд:

      • XLnnn

      • XLe

        Визначає нижню межу, використовуючи точно таке ж кодування, як і для нижнього індексу XS, як описано вище.

      Тоді верхня межа має звичайну форму XSnnn/ XSe

    • Вибраний компонент Для вибраного компонента у нас є один вхід

      • XRf

        Тут f – це назва поля для вибору

      Для явного розіменування ( .all) у нас є один вхід

      • XA

    • Як приклад, розглянемо такі декларації:

       package p is
          type q is record
             m : string (2 .. 5);
          end record;
    
          type r is array (1 .. 10, 1 .. 20) of q;
    
          g : r;
    
          z : string renames g (1,5).m(2 ..3)
       end p;
    

    Згенерована змінна виглядатиме так

      p__z___XR_p__g___XEXS1XS5XRmXL2XS3 : _renaming_type;
                p__g___XE--------------------outer entity is g
                         XS1-----------------first subscript for g
                            XS5--------------second subscript for g
                               XRm-----------select field m
                                  XL2--------lower bound of slice
                                     XS3-----upper bound of slice
    

    Зверніть увагу, що тип змінної – це спеціальний внутрішній тип з назвою _renaming_type. Цей тип є довільним типом нульового розміру, створеним у пакеті Standard (див. cstand.adb), і ігнорується налагоджувачем.

Представлення упакованого масиву в пам'яті#

Упаковані масиви представлені в щільно упакованій формі, без зайвих бітів між компонентами. Це вірно навіть тоді, коли розмір компонента не є фактором розміру одиниці пам'яті, тому компоненти можуть перетинати межі одиниць пам'яті.

Макет у сховищі ідентичний, незалежно від того, чи є тип реалізації модульним типом, чи масивом байтів. Див. Exp_Pakd для отримання детальної інформації про те, як використовуються ці типи реалізації, але для цілей налагоджувача важлива лише початкова адреса об'єкта в пам'яті.

Наступний приклад має чітко показати, як працює пакування у випадках little-endian та big-endian:

   type B is range 0 .. 7;
   for B'Size use 3;

   type BA is array (0 .. 5) of B;
   pragma Pack (BA);

   BV : constant BA := (1,2,3,4,5,6);

Випадок Little endian:

      BV'Address + 2   BV'Address + 1    BV'Address + 0
   +-----------------+-----------------+-----------------+
   | ? ? ? ? ? ? 1 1 | 0 1 0 1 1 0 0 0 | 1 1 0 1 0 0 0 1 |
   +-----------------+-----------------+-----------------+
     <---------> <-----> <---> <---> <-----> <---> <--->
     unused bits  BV(5)  BV(4) BV(3)  BV(2)  BV(1) BV(0)

Віпадок Big endian:

      BV'Address + 0  BV'Address + 1    BV'Address + 2
   +-----------------+-----------------+-----------------+
   | 0 0 1 0 1 0 0 1 | 1 1 0 0 1 0 1 1 | 1 0 ? ? ? ? ? ? |
   +-----------------+-----------------+-----------------+
     <---> <---> <-----> <---> <---> <-----> <--------->
     BV(0) BV(1)  BV(2)  BV(3) BV(4)  BV(5)  unused bits

Зверніть увагу, що якщо для представлення масиву використовується модульний тип, розподіл пам'яті відрізняється від звичайного модульного типу. Різниця виникає, коли розподілений об'єкт більший за розмір масиву. Для звичайного модульного типу ми розширюємо значення ліворуч нулями.

Наприклад, у звичайному модульному випадку, якщо у нас є 6-бітний модульний тип, оголошений як mod 2**6, і ми виділяємо 8-бітний об'єкт для цього типу, тоді ми розширюємо значення двома бітами на старшому кінці, і у випадку ведення від малого до великого порядка байтів або від великого до великого порядка байтів значення 63 представляється як 00111111 у двійковому вигляді в пам'яті.

Для модульного типу, що використовується для представлення упакованого масиву, правило інше. У цьому випадку, якщо нам потрібно розширити значення, ми робимо це за допомогою невизначених бітів (які не ініціалізовані та значення яких не має значення для будь-якого згенерованого коду). Крім того, ці біти знаходяться праворуч (молодші біти) у випадку ведення від старшого до старшого байта, і ліворуч (старші біти) у випадку ведення від молодшого до старшого байта.

Наприклад, якщо у нас є упакований булевий масив із 6 бітів, усі встановлені на True, що зберігається у 8-бітному об'єкті, тоді значення в пам'яті у двійковому вигляді буде ??111111 у випадку little-endian та 111111?? у випадку big-endian.

Це зроблено для того, щоб представлення упакованих масивів не залежало від того, чи використовуємо ми модульне представлення, чи масив байтів, як описано раніше. Це гарантує, що ми можемо передавати такі значення за посиланням у випадку, коли підпрограма повинна мати можливість обробляти значення, що зберігаються в будь-якій формі.

Зверніть увагу, що коли ми витягуємо значення такого модульного упакованого масиву, ми очікуємо отримати лише відповідні біти, тому в цьому ж прикладі, коли ми витягуємо значення, ми отримуємо 111111 в обох випадках, і код, згенерований фронтендом, припускає це, хоча він не передбачає, що визначено будь-які старші біти.

Існують можливості для оптимізації, засновані на знанні того, що невикористані біти не мають значення для цих типів упакованих масивів. Наприклад, якщо у нас є два таких значення розміром 6-біт-у-8-біт, і ми виконуємо присвоєння:

   a := b;

Тоді логічно, що ми витягуємо 6 бітів і зберігаємо лише 6 бітів у результаті, але серверна частина може просто призначити всі 8 бітів у цьому випадку, оскільки нам насправді байдуже до невизначених бітів. Однак у випадку рівності важливо переконатися, що невизначені біти не беруть участі в перевірці рівності.

Якщо значення модульного упакованого масиву присвоюється регістру, то логічно, що його завжди можна вирівняти по правому краю, щоб уникнути необхідності зсуву, наприклад, під час порівнянь. Але, ймовірно, це поганий вибір, оскільки це означатиме, що присвоєння, таке як := вище, вимагатиме зсувів, коли одне значення знаходиться в регістрі, а інше - в пам'яті.

Кодування імен упакованих масивів ( ЗАСТАРІЛЕ )#

Для кожного обмеженого упакованого масиву створюється два типи, і обидва відображаються у виводі налагодження:

  • Оригінально оголошений тип масиву є цілком звичайним типом масиву, а його індексні межі вказують на оригінальні межі масиву.

  • Відповідний тип упакованого масиву, який може бути модульним типом або масивом байтів (див. Exp_Pakd для отримання повної інформації). Це тип, який фактично використовується у згенерованому коді та для налагоджувальної інформації для всіх об'єктів упакованого типу.

Назва відповідного типу упакованого масиву:

  • ttt___XPnnn

де

  • ttt – це ім'я оригінального оголошеного масиву

  • nnn – розмір компонента в бітах (1-31)

Зверніть увагу, що якщо упакований масив не є бітово-упакованим, назва буде просто tttP.

Коли налагоджувач бачить, що об'єкт має тип, закодований таким чином, він може використовувати вихідний тип для визначення меж та типу компонента, а також розмір компонента для визначення деталей упаковки.

Для необмеженого упакованого масиву відповідний тип упакованого масиву не використовується ні в згенерованому коді, ні для налагоджувальної інформації, використовується лише оригінальний тип. Щоб передати упаковку в налагоджувальній інформації, компілятор генерує пов'язані типи fat- та thin-вказівників (див. розділ "Вказівники на необмежений масив" нижче), використовуючи назву відповідного типу упакованого масиву як базову назву, тобто ttt___XPnnn___XUP та ttt___XPnnn___XUT відповідно.

Коли налагоджувач бачить, що об'єкт має тип, закодований таким чином, він може використовувати тип полів для визначення меж та типу компонента, а також розмір компонента для визначення деталей упаковки.

Вказівники на необмежені масиви#

Існує два типи вказівників на необмежені масиви. Налагоджувач може визначити, який формат використовується, за формою типу вказівника.

Жирні (Fat) вказівники#

Жирні вказівники представлені як структура з двома полями. Ця структура має два окремі назви полів:

  • P_ARRAY – це вказівник на тип масиву. Ім'я цього типу – це необмежений тип, за яким йде ___XUA. Межі цього масиву будуть отримані шляхом розіменування P_BOUNDS нижче.

  • P_BOUNDS – це вказівник на структуру. Ім'я цього типу – це ім'я необмеженого масиву, за яким йде ___XUB, і він має поля вигляду:

    • LBn (десяткове ціле число na) нижня межа n -го виміру

    • UBn (десяткове ціле число na) n -го виміру

    Межі можуть бути будь-якого цілочисельного типу. У випадку перераховуваних типів використовуються значення Enum_Rep.

Для заданого необмеженого типу масиву компілятор генерує тип товстого вказівника, ім'я якого є ім'ям типу масиву, та використовує його для представлення самого типу масиву в налагоджувальній інформації.

Історично за цією назвою йшло ___XUP ( ЗАСТАРІЛЕ ).

Для кожного вказівника на цей необмежений тип масиву компілятор генеруватиме typedef, який вказує на вищезгаданий тип fat-вказівника. Як наслідок, коли справа доходить до типів fat-вказівників:

  1. Ім'я типу задається typedef, якщо такий є

  2. Якщо налагоджувачу пропонується вивести тип, відповідна форма – «access arr», якщо є typedef, інакше це визначення масиву.

Тонкі (Thin) вказівники#

Значення тонкого вказівника – це вказівник на друге поле структури з двома полями. Перше поле структури має тип ___XUB, описаний вище для типів товстих вказівників. Друге поле структури містить власне масив.

Тонкі вказівники представлені як звичайний вказівник на масив у налагоджувальній інформації. Межами цього масиву буде вміст першого поля вище, отриманого за допомогою (зміщених) розіменувань.

Тонкі (Thin) вказівники ( ЗАСТАРІЛЕ )#

Значення тонкого вказівника – це вказівник на друге поле структури з двома полями. Назва типу цієї структури – arr___XUT, де arr – назва типу необмеженого масиву. Навіть якщо він вказує на середину цієї структури, тип у налагоджувальній інформації – це вказівник на структуру.

Перше поле структури називається BOUNDS і має тип ___XUB, описаний вище для типів жирних вказівників.

Друге поле структури називається ARRAY і містить власне масив. Оскільки цей масив має динамічний розмір, що визначається полем BOUNDS, що передує йому, вся інформація про arr___XUT закодована в паралельному типі з назвою arr___XUT___XVE з полями BOUNDS та ARRAY___XVL. Як і для раніше описаних типів ___XVE, ARRAY___XVL має тип вказівника на масив. Однак тип масиву в цьому випадку називається arr___XUA, і лише тип його елемента має значення, як описано для товстих вказівників.

Кодування запису з варіантами#

Варіантна частина варіантного запису кодується як одне поле в записі, що його містить, ім'я якого:

   discrim___XVN

де discrim – це некваліфікована назва варіанта. Ця назва поля створюється gigi (а не кодом у цьому юніті). Для запису Unchecked_Union цей дискримінант не відображатиметься в записі (див. Неперевірені об'єднання нижче).

Тип, що відповідає цьому полю, має ім'я, яке отримується шляхом об'єднання імені типу з наведеним вище рядком і подібне до об'єднання C, в якому кожен член об'єднання відповідає одному варіанту. Однак, на відміну від об'єднання C, розмір типу може бути змінним, навіть якщо кожен з компонентів має фіксований розмір, оскільки це включає обчислення того, який варіант присутній.

Ім'я члена об'єднання закодовано для позначення варіантів вибору та є рядком, заданим наступною граматикою:

  member_name ::= {choice} | others_choice
  choice ::= simple_choice | range_choice
  simple_choice ::= S number
  range_choice  ::= R number T number
  number ::= {decimal_digit} [m]
  others_choice ::= O (upper case letter O)

Літера m у числі вказує на від'ємне значення. Як приклад цієї схеми кодування, вибір 1 .. 4 | 7 | -10 буде представлений як

  R1T4S7S10m

У випадку значень перелічення, значення, що використовуються, є фактичними значеннями представлення у випадку, коли тип перелічення має специфікацію представлення перелічення (тобто це значення, що відповідають використанню атрибута Enum_Rep).

Тип внутрішнього запису визначається назвою типу об'єднання (як зазначено вище), об'єднаною з вищенаведеним рядком.

Як приклад, розглянемо:

  type Var (Disc : Boolean := True) is record
     M : Integer;

     case Disc is
       when True =>
         R : Integer;
         S : Integer;

       when False =>
         T : Integer;
     end case;
  end record;

  V1 : Var;

У цьому випадку тип var представлений як структура з трьома полями. Перші два – disc та m, що представляють значення цих компонентів запису. Третє поле – це об'єднання двох типів з іменами полів S1 та O. S1 це структура з полями rта s, а O – це структура з полем t.

Неперевірені об'єднання (Unchecked Unions)#

Кодування для варіантних записів дещо змінюється під впливом pragma Unchecked_Union:

  1. Дискримінант не буде присутній у записі, хоча його назва все ще використовується в кодуваннях.

  2. Варіанти, що містять один компонент з назвою x типу T, можуть бути закодовані, як у звичайних об'єднаннях C, як одне поле типу об'єднання, що охоплює, з назвою x типу T, обходячи без охоплюючої структури. У цьому випадку, звичайно, дискримінантні значення, що відповідають варіанту, недоступні.

Наприклад, тип Var у попередньому розділі, якщо за ним слідує прагма Unchecked_Union (Var);, може бути закодований як структура з двома полями. Перше поле – m. Друге поле – це об'єднання двох типів з іменами полів S1 та t. Як і раніше, S1 – це структура з полями r та s.t– це поле типу Integer.

Підтипи варіантних записів#

Підтип варіантного запису представлений типом, у якому поле об'єднання з базового типу замінюється одним із можливих значень. Наприклад, якщо ми маємо:

  type Var (Disc : Boolean := True) is record
     M : Integer;

     case Disc is
       when True =>
         R : Integer;
         S : Integer;

       when False =>
         T : Integer;
     end case;

  end record;
  V1 : Var;
  V2 : Var (True);
  V3 : Var (False);

Тут , наприклад, V2 представлено підтипом, назва якого щось на кшталт TvarS3b, що є структурою з трьома полями. Перші два поля – disc та m, як для базового типу, а третє поле – S1, яке містить поля r та s.

Налагоджувач повинен просто ігнорувати структури з іменами виду, що відповідає варіантам, та вважати поля всередині такими, що належать до запису, що їх містить.

Додаткові перейменування для екземплярів підпрограм#

Налагоджувач має труднощі з відновленням значення фактичних значень елементарного типу з тіла екземпляра підпрограми. Це пояснюється тим, що такі фактичні значення генерують оголошення об'єкта, яке розміщується в пакеті-обгортці екземпляра, а сутність у цих оголошеннях закодована складним способом, який GDB погано обробляє. Ці нові оголошення перейменування з'являються в тілі підпрограми та є надлишковими з точки зору видимості, але вони не повинні мати вимірного впливу на продуктивність і не вимагають спеціального декодування в налагоджувачі.

Символьні літерали в типах символів#

Символьні типи – це перелічувальні типи, принаймні один з перелічувальних літералів яких є символьним літералом. Перелічувальні літерали зазвичай просто представлені за допомогою їхніх ідентифікаторів. Якщо перелічувальний літерал є символьним літералом, ім'я кодується, як описано в наступному абзаці.

Символи 'a'..'z' та '0'..'9' представлені як Qc, де c означає сам символ. Ім'я QUhh, де кожна h – це нижня шістнадцяткова цифра, означає символ, кодування Unicode якого – hh, а QWhhhhh аналогічно означає широкий символ, кодування якого – hhhh. Значення представлення кодуються так само, як і для звичайних літералів перерахування (і не мають обов'язкового зв'язку зі значеннями, закодованими в іменах).

Наприклад, враховуючи оголошення типу

  type x is (A, 'C', 'b');

Другий літерал перелічення мав би назву QU43, а значення, присвоєне йому, дорівнювало б 1, а третій літерал перелічення мав би назву Qb, а значення, присвоєне йому, дорівнювало б 2.

Таблиці вторинної диспетчеризації тегових типів#

Внутрішнє правило, яке використовується для генерації зовнішнього імені:

  • Випадок 1. Якщо вторинне відправлення не було успадковано від якогось предка Typ, то зовнішнє ім'я складається наступним чином:

    External_Name (Typ) + Suffix_Number + 'P'
    
  • Випадок 2. Якщо вторинна таблиця відправлення була успадкована від деякого предка, то зовнішнє ім'я складається наступним чином:

    External_Name (Typ) + '_' + External_Name (Ancestor_Typ)
               + Suffix_Number + 'P'
    

Примітка:

Нам потрібно використовувати зовнішні імена (а не просто їхні імена), щоб захистити фронтенд від програм, які надають однакові імена всім інтерфейсам та використовують розширені імена для посилання на них. Suffix_Number використовується для розрізнення всіх вторинних таблиць відправлення заданого типу.

Приклади:

package Pkg1 is | package Pkg2 is | package Pkg3 is
  type Typ is   |   type Typ is   |   type Typ is
    interface;  |     interface;  |     interface;
end Pkg1;       | end Pkg;        | end Pkg3;
with Pkg1, Pkg2, Pkg3;
package Case_1 is
  type Typ is new Pkg1.Typ and Pkg2.Typ and Pkg3.Typ with ...
end Case_1;

with Case_1;
package Case_2 is
  type Typ is new Case_1.Typ with ...
end Case_2;

Це зовнішні імена, згенеровані для Case_1.Typ (зверніть увагу, що Pkg1.Typ пов'язаний з таблицею первинної відправки, оскільки він є батьківським елементом цього типу, і тому для нього не генерується зовнішнє ім'я).

    case_1__typ0P   (associated with Pkg2.Typ)
    case_1__typ1P   (associated with Pkg3.Typ)

Це зовнішні імена, згенеровані для Case_2.Typ:

    case_2__typ_case_1__typ0P
    case_2__typ_case_1__typ1P

Вплив оптимізації#

Якщо програму компілюється з увімкненою оптимізацією (наприклад, задано перемикач -O1), то у виводі можуть бути відмінності від наведеної вище специфікації. Зокрема, об'єкти можуть зникнути з виводу. Це включає не лише константи та змінні, які програма оголошує на рівні вихідного коду, але й константи x___L та x___U, створені для опису нижньої та верхньої меж підтипів з динамічними межами. Це означає, наприклад, що межі масиву можуть зникнути, якщо ввімкнено оптимізацію. Очікується, що налагоджувач розпізнає відсутність цих констант та найкраще обробляє доступну обмежену інформацію.

Розширення GNAT для DWARF ( ЗАСТАРІЛІ )#

  • DW_AT_use_GNAT_descriptive_type, закодовано значенням 0x2301

    Це розширення ніколи не було реалізовано в компіляторі.

  • DW_AT_GNAT_descriptive_type, закодований зі значенням 0x2302

    Будь-який запис налагоджувальної інформації, що представляє тип, може мати атрибут DW_AT_GNAT_descriptive_type, значенням якого є посилання, що вказує на запис налагоджувальної інформації, що представляє інший тип, пов'язаний з цим типом.