Как GHC обрабатывает класс типов и экземпляр в ядре? - PullRequest
0 голосов
/ 04 февраля 2019

Я скомпилировал следующий код Haskell для ядра:

class FunClass a where
  functionInClass :: a -> ()

data MyData = MyData
data YourData = YourData

instance FunClass MyData where
  functionInClass a = ()
instance FunClass YourData where
  functionInClass a = ()

valueA :: ()
valueA = functionInClass MyData

valueB :: ()
valueB = functionInClass YourData

и получил следующие привязки ядра (я удалил некоторые шаблоны, которые не имеют отношения к делу):

 $cfunctionInClass :: MyData -> ()
 [LclId]
 $cfunctionInClass = \ _ [Occ=Dead] -> break<3>() ()

 $fFunClassMyData [InlPrag=INLINE (sat-args=0)] :: FunClass MyData
 $fFunClassMyData
   = $cfunctionInClass
     `cast` (Sym (N:FunClass[0] <MyData>_N)
             :: Coercible (MyData -> ()) (FunClass MyData))

 $cfunctionInClass :: YourData -> ()
 [LclId]
 $cfunctionInClass = \ _ [Occ=Dead] -> break<2>() ()

 $fFunClassYourData [InlPrag=INLINE (sat-args=0)] :: FunClass YourData
 $fFunClassYourData
   = $cfunctionInClass
     `cast` (Sym (N:FunClass[0] <YourData>_N)
             :: Coercible (YourData -> ()) (FunClass YourData))

 valueA :: ()
 [LclIdX]
 valueA
   = break<1>() functionInClass @ MyData $fFunClassMyData MyData

 valueB :: ()
 [LclIdX]
 valueB
   = break<0>()
     functionInClass @ YourData $fFunClassYourData YourData

Мои вопросы:

  1. Почему два cfunctionInClass имеют одно и то же имя?Как мы можем отличить их друг от друга?

  2. Что точно делает cast?

  3. Есть ли что-нибудь связанное с классом / экземпляром типов вне mg_binds ModGuts?

1 Ответ

0 голосов
/ 14 апреля 2019

Не зная (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 для записи в файл интерфейса.

...