Cmm для внешнего примитива (пример integer-gmp)

Я проверил integer-gmp исходный код, чтобы понять, как иностранные примитивы могут быть реализованы с точки зрения cmm, как описано на Страница GHC Primops. Я знаю о методах их реализации, используя llvm hack или fvia-C/gcc - это больше для изучения, чтобы понять этот третий подход, который interger-gmp библиотека использует.

Итак, я просмотрел учебник CMM на странице MSFT (pdf-ссылка), прошел страницу GHC CMM, и все еще есть некоторые неотвеченные вопросы (трудно держать все эти концепции в голове, не копаясь в CMM, что я и делаю сейчас). Этот фрагмент кода существует из файла cmm integer-bmp:

integer_cmm_int2Integerzh (W_ val)
{
   W_ s, p; /* to avoid aliasing */

   ALLOC_PRIM_N (SIZEOF_StgArrWords + WDS(1), integer_cmm_int2Integerzh, val);

   p = Hp - SIZEOF_StgArrWords;
   SET_HDR(p, stg_ARR_WORDS_info, CCCS);
   StgArrWords_bytes(p) = SIZEOF_W;

   /* mpz_set_si is inlined here, makes things simpler */
   if (%lt(val,0)) {
        s  = -1;
        Hp(0) = -val;
   } else {
     if (%gt(val,0)) {
        s = 1;
        Hp(0) = val;
     } else {
        s = 0;
     }
  }

   /* returns (# size  :: Int#,
                 data  :: ByteArray#
               #)
   */
   return (s,p);
}

Как определено в ghc cmm header:

W_ is alias for word.
ALLOC_PRIM_N is a function for allocating memory on the heap for primitive object.
Sp(n) and Hp(n) are defined as below (comments are mine):
#define WDS(n) ((n)*SIZEOF_W) //WDS(n) calculates n*sizeof(Word)
#define Sp(n)  W_[Sp + WDS(n)]//Sp(n) points to Stackpointer + n word offset?
#define Hp(n)  W_[Hp + WDS(n)]//Hp(n) points to Heap pointer + n word offset?

Я не понимаю строки 5-9 (строка 1 - это начало, если у вас есть путаница 1/0). Более конкретно:

  • Почему формат вызова функции ALLOC_PRIM_N (bytes, fun, arg) таким образом?
  • Почему p управляется таким образом?

Функция, как я ее понимаю (от поиска сигнатуры функции в Prim.hs) принимает int и возвращает массив (int, byte) (хранится в s, p соответственно в коде).

Для тех, кто интересуется встроенным вызовом в if block, это реализация cmm gmp mpz_init_si. Я предполагаю, что если вы вызываете функцию, определенную в объектном файле через ccall, она не может быть встроена (что имеет смысл, поскольку это объектный код, а не промежуточный код). Подход LLVM более подходит для встраивания через LLVM IR). Таким образом, оптимизация заключалась в том, чтобы определить cmm-представление функции, которая должна быть встроена. Пожалуйста, исправьте меня, если это предположение неверно.

Пояснение строк 5-9 будет очень оценено. У меня больше вопросов о других макросах, определенных в файле integer-gmp, но в одном сообщении может быть слишком много вопросов. Если вы можете ответить на вопрос с помощью вики-страницы Haskell или блога (вы можете опубликовать ссылку в качестве ответа), это было бы высоко оценено (и если вы это сделаете, я также хотел бы пошаговое прохождение целого числа -gmp cmm, например GMP_TAKE2_RET1).

Ответ 1

Эти строки выделяют новый ByteArray # в куче Haskell, поэтому, чтобы понять их, вам сначала нужно немного узнать о том, как обрабатывается куча GHC.

  • Каждая возможность (= поток ОС, которая выполняет код Haskell) имеет свой собственный выделенный ясли, область кучи, в которую он делает обычные, небольшие выделения, подобные этой. Объекты просто выделяются последовательно в эту область от низких адресов до высоких адресов, пока не попытается выполнить выделение, которое превышает оставшееся место в детской, которое запускает сборщик мусора.

  • Все объекты кучи выравниваются с кратным размеру слова, т.е. 4 байта в 32-битных системах и 8 байтам в 64-битных системах.

  • Регистр уровня Cmm Hp указывает на (начало) последнее слово, которое было выделено в детском саду. HpLim указывает на последнее слово, которое может быть выделено в детскую. (HpLim также может быть установлен на 0 другим потоком, чтобы остановить мир для GC или отправить асинхронное исключение.)

  • https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage/HeapObjects содержит информацию о компоновке отдельных объектов кучи. В частности, каждый объект кучи начинается с указателя информации, который (среди прочего) идентифицирует, какой он тип кучи.

Тип Haskell ByteArray # реализуется с помощью типа объекта кучи ARR_WORDS. Объект ARR_WORDS состоит только из (указателя информации, за которым следует) размера (в байтах), за которым следуют произвольные данные (полезная нагрузка). Полезная нагрузка не интерпретируется GC, поэтому она не может сохранять указатели на объекты кучи Haskell, но она может хранить что-нибудь еще. SIZEOF_StgArrWords - это размер заголовка, общего для всех объектов кучи ARR_WORDS, и в этом случае полезная нагрузка - всего лишь одно слово, поэтому SIZEOF_StgArrWords + WDS (1) - это объем пространства, который нам нужно выделить.

ALLOC_PRIM_N (SIZEOF_StgArrWords + WDS (1), integer_cmm_int2Integerzh, val) расширяется до чего-то вроде

Hp = Hp + (SIZEOF_StgArrWords + WDS(1));
if (Hp > HpLim) {
    HpAlloc = SIZEOF_StgArrWords + WDS(1);
    goto stg_gc_prim_n(integer_cmm_int2Integerzh, val);
}

Первая строка увеличивает Hp на сумму, которую нужно выделить. Вторая строка проверяет переполнение кучи. Третья строка записывает сумму, которую мы пытались выделить, поэтому GC может ее отменить. Четвертая строка вызывает GC.

Четвертая строка - самая интересная. Аргументы говорят GC, как перезапустить поток, как только сбор мусора завершен: он должен повторно инициализировать integer_cmm_int2Integerzh с аргументом val. "_n" в stg_gc_prim_n (и "_N" в ALLOC_PRIM_N) означает, что val является аргументом не указателя (в данном случае Int #). Если val был указателем на объект кучи Haskell, GC должен знать, что он является живым (поэтому он не собирается) и повторно вывести нашу функцию с новым адресом объекта. В этом случае мы будем использовать вариант _p. Существуют также варианты, такие как _pp для нескольких аргументов указателя, _d для аргументов Double # и т.д.

После строки 5 мы успешно выделили блок байтов SIZEOF_StgArrWords + WDS (1) и, помните, Hp указывает на его последнее слово. Итак, p = Hp - SIZEOF_StgArrWords устанавливает p в начало этого блока. Строки 8 заполняют информационный указатель p, идентифицируя вновь созданный объект кучи как ARR_WORDS. CCCS - это текущий стек МВЗ, используемый только для профилирования. Когда профилирование включено, каждый объект кучи содержит дополнительное поле, которое в основном определяет, кто несет ответственность за его распределение. В не-профилирующих сборках нет CCCS, а SET_HDR просто устанавливает указатель информации. Наконец, строка 9 заполняет поле размера ByteArray #. Остальная часть функции заполняет полезную нагрузку и возвращает значение знака и указатель объекта ByteArray #.

Итак, это больше связано с кучей GHC, чем с языком Cmm, но я надеюсь, что это поможет.

Ответ 2

enter image description here

Необходимые знания

Для выполнения операций арифметических и логических компьютеры имеют цифровую схему, называемую ALU (Арифметическая логическая единица) в своем ЦП (центральный процессор). ALU загружает данные из входных регистров. Регистр процессора память в кеш L1 (запросы данных в пределах трех тактовых импульсов ЦП), реализованные в SRAM (Static Память произвольного доступа), расположенный в чипе CPU. Процессор часто содержит несколько типов регистров, которые обычно различаются количеством бит, которое они могут удерживать.

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

 8 bit numbers = 256 unique representable values
16 bit numbers = 65 536 unique representable values
32 bit numbers = 4 294 967 296 unique representable values
64 bit numbers = 18 446 744 073 709 551 616 unique representable values

Арифметика с фиксированной точностью для этих типов реализована на аппаратном уровне. Размер слова относится к числу бит, которое может быть обработано компьютерным процессором за один раз. Для архитектуры x86 это 32 бит и x64, это 64 бит.

IEEE 754 определяет число чисел с плавающей запятой, стандартное для чисел {16, 32, 64, 128}. Например, 32-битный номер точки (с 4 294 967 296 уникальными значения) могут содержать приблизительные значения [- 3.402823e38 до 3.402823e38] с точностью не менее 7 с плавающей запятой цифры.

enter image description here

Кроме того,

Акроним GMP означает многоадресную арифметическую библиотеку GNU и добавляет поддержку программной эмулируемой арифметики произвольной точности. Компилятор Glasgow Haskell. Использование Integer использует это.

GMP стремится быть быстрее, чем любая другая библиотека bignum для всех операндов размеры. Вот некоторые важные факторы:

  • Использование полных слов в качестве основного арифметического типа.
  • Использование разных алгоритмов для разных размеров операндов; алгоритмы, которые быстрее для очень больших чисел, обычно медленнее для небольших числа.
  • Высоко оптимизированный код языка ассемблера для наиболее важных внутренних циклов, специализированных для разных процессоров.

Ответ

Для некоторых Haskell может быть немного сложно понять синтаксис, поэтому вот версия javascript

var integer_cmm_int2Integerzh = function(word) {
  return WORDSIZE == 32 
    ? goog.math.Integer.fromInt(word))
    : goog.math.Integer.fromBits([word.getLowBits(), word.getHighBits()]);
};

Где goog Используемый класс Google Closure находится в Math.Integer. Вызываемые функции:

goog.math.Integer.fromInt = function(value) {
  if (-128 <= value && value < 128) {
    var cachedObj = goog.math.Integer.IntCache_[value];
    if (cachedObj) {
      return cachedObj;
    }
  }

  var obj = new goog.math.Integer([value | 0], value < 0 ? -1 : 0);
  if (-128 <= value && value < 128) {
    goog.math.Integer.IntCache_[value] = obj;
  }
  return obj;
};
goog.math.Integer.fromBits = function(bits) {
  var high = bits[bits.length - 1];
  return new goog.math.Integer(bits, high & (1 << 31) ? -1 : 0);
};

Это не совсем правильно, так как тип возврата должен быть return (s,p); где

  • s - значение
  • p - знак

Чтобы исправить эту оболочку GMP, необходимо создать. Это было сделано в Haskell для компилятора JavaScript (ссылка источника).

Строки 5-9

ALLOC_PRIM_N (SIZEOF_StgArrWords + WDS(1), integer_cmm_int2Integerzh, val);
p = Hp - SIZEOF_StgArrWords;
SET_HDR(p, stg_ARR_WORDS_info, CCCS);
StgArrWords_bytes(p) = SIZEOF_W;

Следуют

  • выделяет пространство как новое слово
  • создает указатель на него
  • установить значение указателя
  • установить размер указателя