2
$\begingroup$

While implementing enums in Tyr, I just realized that enum constants are the only form of literal-like entity that is not represented like a literal in the intermediate representation.

The enum concept is pretty much that of C++. I doubt that it really matters for the question. The question is, are there any benefits from having enum members as literals in the IR?

My current approach is essentially treating them like global constants that are somewhat equal to the literal values they correspond to. There are some places in the compiler with special rules projecting the enum types away.

At the moment, I'd expect that the answer doesn't even depend on the representation of the enum concept or if instances are flat values or String literals or arbitrary Objects.

EDIT: The question is if a literal type distinct from the literal type of the enum's base type should be introduced. In contrast to projecting the instances to the base type which wasn't the intention, but is apparently a sane interpretation of the original question.

EDIT2: The IR is used to build libraries that can be linked, compiled against and executed like JAR files in Java. The question is if a dedicated EnumLiteral expression subclass akin to an IntLiteral has any obvious advantage or disadvantage over a combination of StaticUse Expression with an EnumInstance Declaration target. Both options would bear an access path to the enum type and the underlying IntLiteral.

$\endgroup$
7
  • 2
    $\begingroup$ I am not sure this question makes sense. A constant is a kind of value or variable; a literal is a kind of node in an AST. Is 123 a constant or a literal? That depends if you are talking about the integer value 123, or the source token 123. On the other hand, it really sounds like your question is not about what an enum member "is", but how it should be represented in an IR, and the answer to that depends on what you mean by "should", and what the IR is for. $\endgroup$
    – kaya3
    Sep 24, 2023 at 12:40
  • $\begingroup$ It depends on how high-level the IR is. If the IR supports reflection or analysis like exhaustiveness checking, they will probably be represented as explicit instances of the enum datatype. Otherwise, and in lower-level IRs like LLVM, they are plain numbers or strings depending on / of the backing type (e.g. int32) $\endgroup$
    – tarzh
    Sep 24, 2023 at 14:53
  • $\begingroup$ Interfacing with a debugger may be valuable to you? $\endgroup$
    – Pseudonym
    Sep 25, 2023 at 10:47
  • $\begingroup$ The debugger likely won't interact with my IR directly. I consider the IR low level and it is not the AST or the parse tree. In the IR, all literals are unified, i.e. two uses of 123 in the source code result in a pointer to the same 123 representing object. For integers, this is an IntegerLiteral. For enums, this is a use of an EnumInstanceDecl which seemed strange to me. Hence the question. The EnumInstance itself isn't a value as it behaves like a member which is a Declaration and therefore not an Expression. $\endgroup$
    – feldentm
    Sep 25, 2023 at 16:30
  • $\begingroup$ I think this question is unanswerable in its current form, because you haven't said what you need to use this IR for. An IR used for type-checking probably needs to know the difference between Colour.RED and 0, for example, but an IR used for x86 code generation does not. Likewise, if your enum members might have other underlying values depending on context, then at some compilation stages this context might not be fully known yet and hence replacing them would be impossible; consider e.g. Result<Colour, ErrorCode> which might be implemented by renumbering one enum to prevent overlap. $\endgroup$
    – kaya3
    Sep 25, 2023 at 18:02

1 Answer 1

4
$\begingroup$

If your enum members are backed by simple immutable integers or strings, and are used as simple names in lieu of these simple integers/strings, they can be represented as simple literals or constants anywhere, even in IR.

But if these enums defines a closed set of possible values or exhaustiveness checking, they are very distinct of simple literals, and should be represented differently:

enum mixed {
  zero,
  one,
  two
}

type T {
  with mixed.zero
  with mixed.one
  with mixed.two

  test "use" {
   
    val c = one + two   // compiler accepts or fails?

    switch c {
      if zero true
      if one true
      if two true
      else false        // compiler complains can never happen?
    }
  }
}

On an IR very close to code generation, after all type checking is already done, is possible to replace enums members by their direct literal values, as in this level these substitutions do not generate invalid effects.

At a very low level, code folding and constant propagation don't care if an immutable value is a member of an enum, a class constant, or a source code literal.

$\endgroup$
2
  • $\begingroup$ Honestly, I don't know if I should accept the answer or clarify the question. In my case, type checking is never done because compilation is performed against the IR of other modules in contrast to using other module's source. I wasn't aware that this would make a difference. The typing in that case makes a difference because a join between two identical literal values from different enums results in that value whereas two different values from the same enum results in an unknown value from that enum. $\endgroup$
    – feldentm
    Sep 25, 2023 at 16:36
  • $\begingroup$ You should accept, as it answer your question. "In my case, type checking is never done" -> "are used as simple names in lieu of these simple integers/strings, they can be represented as simple literals or constants anywhere, even in IR". $\endgroup$ Oct 13, 2023 at 13:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .