Не зная (i) точно, какую версию GHC, (ii) точную командную строку ghc
, которую вы использовали, и (iii) полного содержимого скомпилированного файла, сложно продублировать вывод ядра, который вы используете.спрашиваю, но вот некоторые ответы:
1) При создании вашего ядра вы, вероятно, указали флаг -dsuppress-uniques
, использовали какой-то другой флаг, который подразумевал его, или, возможно, использовали более старую версию GHC, где он былпо умолчанию.Этот флаг заставляет GHC подавлять из вывода ядра небольшие случайные суффиксы, используемые для создания уникальных имен.Если вы уберете флаг или добавите явное -dno-suppress-uniques
, вы должны увидеть уникальные имена, такие как $cfunctionInClass_r1cH
и $cfunctionInClass_r1dh
.
2) Core - это типизированный язык, и используется функция cast
(экстенсивно) для изменения типов выражений.Обратите внимание, что оно не меняет внутреннее представление самого выражения, поэтому его можно использовать только для переключения между типами, имеющими одинаковое внутреннее представление в памяти.
Вы увидите повсеместное приведение кодакоторый использует newtypes
.Например, код:
newtype MyInt = MyInt Int
inc (MyInt n) = MyInt (n + 1)
создает (неоптимизированное) ядро:
inc1 :: MyInt -> Int
inc1
= \ (ds :: MyInt) ->
+ @ Int $fNumInt (ds `cast` (N:MyInt[0] :: MyInt ~R# Int)) (I# 1#)
inc :: MyInt -> MyInt
inc
= inc1
`cast` (<MyInt>_R ->_R Sym (N:MyInt[0])
:: (MyInt -> Int) ~R# (MyInt -> MyInt))
с несколькими приведениями.
Способ cast
работает, левая рукасторона оператора `cast`
- это обычный основной термин (например, переменная или другое выражение), представляющий значение, тип которого изменяется;правая часть - это то, что называется «принуждением», что является доказательством того, что компилятор создает доказательство того, что два типа являются репрезентативно эквивалентными (т. е. имеют одинаковое представление в памяти и поэтому могут быть безопасно приведены).Например, в приведенном выше примере с новым типом приведение для первого приведения:
N:MyInt[0] :: MyInt ~R# Int
- это значение приведения N:MyInt[0]
, тип которого равен равенству (~R#
) представлений MyInt
и Int
.(Технически, N:MyInt[0]
является принуждением типа , чей kind является данным репрезентативным равенством, но это различие на самом деле не имеет значения.) Если вы знакомы с Карри-ГовардомИзоморфизм, где значения можно считать доказательством их типов, это пример этого в действии глубоко в кишках GHC - значение / тип N:MyInt[0]
доказывает свой тип / вид, а именно равенство представлений нового типа и его содержимого,который позволяет выполнять приведение.
В вашем примере приведение:
$fFunClassMyData [InlPrag=INLINE (sat-args=0)] :: FunClass MyData
$fFunClassMyData
= $cfunctionInClass
`cast` (Sym (N:FunClass[0] <MyData>_N)
:: Coercible (MyData -> ()) (FunClass MyData))
- это сложный способ сказать, что GHC представляет словари экземпляров для классов типов, имеющих только одну функцию,таким же образом он будет представлять новый тип, содержащий функцию этого типа, так же, как он представляет само значение функции.Следовательно, значение функции $cfunctionInClass
может быть непосредственно приведено к значению словаря.
Однако, если вы добавите другую функцию к вашему классу типов:
class FunClass a where
functionInClass :: a -> ()
anotherFunction :: a
приведение исчезнет из определенияиз словарей, и они будут выглядеть больше, чем вы ожидаете:
$fFunClassMyData
$fFunClassMyData = C:FunClass $cfunctionInClass $canotherFunction
Важно отметить, что cast
ничего не делает в конечном коде.Как только ядро преобразуется в нетипизированный STG и, в конечном итоге, CMM и сборку, вызовы cast
оптимизируются, так как они не влияют на значения, они только изменяют типы времени компиляции для удовлетворения проверки типов ядра.Так что, если вы не отлаживаете GHC, вы, вероятно, не заботитесь о приведениях и должны подумать о том, чтобы не делать никаких действий.Вы можете подавить некоторые детали с помощью -dsuppress-coercions
(подразумевается -dsuppress-all
):
$fFunClassYourData = $cfunctionInClass1 `cast` <Co:3>
и просто притвориться, что x `cast` <Co:xxx>
в точности эквивалентно x
.В приведенном выше примере словарь - это просто функция единственного экземпляра для класса типов, так что это действительно то же самое, что и для принудительных типов:
$fFunClassMyData = $cFunctionInClass
3) Конечно.Дополнительная информация о классе и экземпляре хранится в полях mg_tcs
и mg_insts
ModGuts
соответственно.В грубом приближении mg_binds
содержит информацию, необходимую для генерации кода, в то время как mg_tcs
и mg_insts
содержат информацию, необходимую для генерации файла интерфейса.
Полезные ссылки на код компилятора GHC
ghc/compiler/coreSyn/PprCore.hs
- Модуль для симпатичного печатного ядра.Если вы хотите знать, откуда что-то в ядре, то это оно.(Например, ppr_expr add_par (Cast expr co) = ...
- это код, отвечающий за красивую печать `cast`
операторов.
ghc/compiler/coreSyn/CoreSyn.hs
- тип Expr
является "ядром" ядра. Конструктор Cast (Expr b) Coercion
представляет собой приведение.
ghc/compiler/types/TycoRep.hs
- здесь есть определение Coercion
.
ghc/compiler/main/HscTypes.hs
- определение ModGuts
и «подмножеств» полей CgGuts
, используемых для генерации кодаи ModIface
/ ModDetails
используется для записи файла интерфейса и связывания.
ghc/compiler/main/TidyPgm.hs
- определение функции tidyGuts
, где ModGuts
информация разбивается на CgGuts
для генерации кода иModDetails
, кэшированная версия ModIface
хранится в памяти при компиляции нескольких модулей и / или используется для генерации полного ModIface
для записи в файл интерфейса.