Writing a simple Core evaluator, having trouble with name lookups

Christopher Done chrisdone at gmail.com
Sat Dec 1 15:03:00 UTC 2018


Regarding classes,

Csaba, did you have to deal with dictionaries at the STG level? I'm
finding that at the Core level, methods don't generate code, so I have
to generate them myself. But then I have to know more about classes
than I might ideally not want to.

For example, I've noticed that if a class has only one method, then
the dictionary for an instance doesn't actually seem to construct a
record, but makes a special case and just refers to the single method.
So my evaluator failed on this. If I add one more method to the class,
then things work properly. Example:

module Demo (demo) where
class Person a where
  person :: a -> Int
  wibble :: a -> Int
instance Person X where
  person unusedarg = 5
  wibble _ = 9
data X = X
demo = person X

This produces

chris at precision:~/Work/chrisdone/prana$ sh scripts/compiledemo.sh
[1 of 1] Compiling Demo             ( Demo.hs, Demo.o )
Writing main_Demo.prana
Eval: ((main:Demo.person main:Demo.$fPersonX) main:Demo.X)
  Eval: (main:Demo.person main:Demo.$fPersonX)
    Eval: main:Demo.person
    Done: main:Demo.person[Method]0
    Eval: main:Demo.$fPersonX   <---- here is the dictionary, and you
can see what it refers to below:
      Eval: ((main:Demo.C:Person main:Demo.$cperson)
main:Demo.$cwibble) <-- this is the two methods
        Eval: (main:Demo.C:Person main:Demo.$cperson)
          Eval: main:Demo.C:Person
          Done: (main:Demo.C:Person[Con] )
        Done: (main:Demo.C:Person[Con] main:Demo.$cperson)
      Done: (main:Demo.C:Person[Con] main:Demo.$cpersonmain:Demo.$cwibble)
    Done: (main:Demo.C:Person[Con] main:Demo.$cpersonmain:Demo.$cwibble)
    Eval: main:Demo.$cperson
      Eval: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
      Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
    Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
  Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
  Eval: (ghc-prim:GHC.Types.I# 5)
    Eval: ghc-prim:GHC.Types.I#
    Done: (ghc-prim:GHC.Types.I#[Con] )
  Done: (ghc-prim:GHC.Types.I#[Con] 5)
Done: (ghc-prim:GHC.Types.I#[Con] 5)
ConWHNF (Id {idStableName = "ghc-prim:GHC.Types.I#", idUnique = Unique
3891110078048108563, idCategory = DataCat}) [LitE (Int 5)]

That's great. But if I delete the wibble method:

Eval: ((main:Demo.person main:Demo.$fPersonX) main:Demo.X)
  Eval: (main:Demo.person main:Demo.$fPersonX)
    Eval: main:Demo.person
    Done: main:Demo.person[Method]0
    Eval: main:Demo.$fPersonX <- the dictionary
      Eval: main:Demo.$cperson  <-- evaluates to simply the person
method, instead of a data constructor
        Eval: main:Demo.$cperson
          Eval: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
          Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
        Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
      Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
    Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))

Which results in a runtime type error:

prana: TypeError (NotAnInstanceDictionary (Id {idStableName =
"main:Demo.person", idUnique = Unique 8214565720323785170, idCategory
= ValCat}) (LamWHNF (Id {idStableName = "main:Demo.unusedarg",
idUnique = Unique 6989586621679011036, idCategory = ValCat}) (AppE
(VarE (Id {idStableName = "ghc-prim:GHC.Types.I#", idUnique = Unique
3891110078048108563, idCategory = DataCat})) (LitE (Int 5)))))

I could ignore the fact that I got a function instead of a dictionary,
and then evaluation proceeds OK:

Eval: ((main:Demo.person main:Demo.$fPersonX) main:Demo.X)
  Eval: (main:Demo.person main:Demo.$fPersonX)
    Eval: main:Demo.person
    Done: main:Demo.person[Method]0
    Eval: main:Demo.$fPersonX
      Eval: main:Demo.$cperson
        Eval: main:Demo.$cperson
          Eval: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
          Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
        Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
      Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
    Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
  Done: (\main:Demo.unusedarg -> (ghc-prim:GHC.Types.I# 5))
  Eval: (ghc-prim:GHC.Types.I# 5)
    Eval: ghc-prim:GHC.Types.I#
    Done: (ghc-prim:GHC.Types.I#[Con] )
  Done: (ghc-prim:GHC.Types.I#[Con] 5)
Done: (ghc-prim:GHC.Types.I#[Con] 5)
ConWHNF (Id {idStableName = "ghc-prim:GHC.Types.I#", idUnique = Unique
3891110078048108563, idCategory = DataCat}) [LitE (Int 5)]

But this feels a bit less structured. I want, for example, to be able
to update definitions at runtime, so runtime type-errors will be a
thing sometimes. I feel like this kind of thing would make some
confusing runtime type errors. And what other class-specific oddities
should I have to handle?

I haven't gotten to dictionaries with superclasses yet, that'll
require more handling so I'll probably need more knowledge of classes
anyway.

So I'm wondering whether at the STG phase all classes have been
abstracted away and we really do only deal with lambdas, lets and
cases? I didn't see any class-specific code in your project.

I only chose Core because I wanted to stay as close to the original
Haskell source as possible, and have a very simple evaluation model,
but perhaps STG is the easier choice.

Cheers
On Sat, 1 Dec 2018 at 12:42, Csaba Hruska <csaba.hruska at gmail.com> wrote:
>
> The package name + module name is always unique in every Haskell program, module names can not be duplicated in a package and package names are unique also.
> There are 2 kinds of identifiers, local and exported.
> You can construct a whole program unique name for:
>
> exported identifier with combining the package name + module name + occurence name (without the unique value)
> local identifier with combining the package name + module name + occurence name + unique (what is unique per invocation)
>
> It is safe because only the exported names can appear in an external expression and those do not contain the GHC's unique value.
> Just think about how the object code linker deals with GHC symbols.
>
> Cheers,
> Csaba
>
> On Sat, Dec 1, 2018 at 1:23 PM Christopher Done <chrisdone at gmail.com> wrote:
>>
>> I think what Csaba means is that we can have e.g.
>>
>> * GHC invocation 1
>>   * ghc-prim:
>>     * MyModule.foo has Unique 123
>>     * OtherModule.bar has Unique 124
>> * GHC invocation 2
>>   * base:
>>     * MyMod.mu has Unique 123
>>     * OtherMod.zot has Unique 124
>>
>> For a unique reference then, we just need:
>>
>> * ghc-prim:MyMobile.foo+123
>> * ghc-prim:OtherModule.bar+124
>> * base:MyMod.mu+123
>> * base:OtherMod.zot+124
>>
>> For any local lookup this is reliable. If the lookup fails, a lookup
>> without the Unique works for cross-module lookups.


More information about the ghc-devs mailing list