Enum order

I was looking at swift enums (with value) and noticed they're stored as
{ , } where integer is the enum value, and is sized by the max enum cases.

As far as I could detect, they're all in order of definition, however Optional<T> does not seem to match this pattern:

@_frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
  case none
  case some(Wrapped)

Why is "none" stored as 1 and some as 0?


var x: Int!
x = 15
x = 13
x = nil


  %8 = bitcast %TSiSg* %0 to i64*
  store i64 15, i64* %8, align 8
  %9 = getelementptr inbounds %TSiSg, %TSiSg* %0, i32 0, i32 1
  %10 = bitcast [1 x i8]* %9 to i1*
  store i1 false, i1* %10, align 8

  %11 = bitcast %TSiSg* %0 to i64*
  store i64 13, i64* %11, align 8
  %12 = getelementptr inbounds %TSiSg, %TSiSg* %0, i32 0, i32 1
  %13 = bitcast [1 x i8]* %12 to i1*
  store i1 false, i1* %13, align 8
  
  %14 = bitcast %TSiSg* %0 to i64*
  store i64 0, i64* %14, align 8
  %15 = getelementptr inbounds %TSiSg, %TSiSg* %0, i32 0, i32 1
  %16 = bitcast [1 x i8]* %15 to i1*
  store i1 true, i1* %16, align 8

can anyone shed some light on this?

Update:
if I read this correctly; There are two cases, if T is a value type, enum values use logical ordering for the cases with associated data, then if there are cases with non associated data, they use the following numeric value, those then are numbered separately from 0 and are stored in the data itself.

For reference types, the same idea goes, but if there's only 2 options, 1 with and 1 without, it uses nil as a marker for the without associated data case, and stores it as just 1 single field.

Can anyone confirm this is the case?

This section of the docs for type layout may help you.

2 Likes

The document @duan linked is a good place to start. In general, payload cases are separated from no-payload cases before tag assignment, and type layout uses properties of the payload cases to try to optimize the tag representation when possible. Class references are just one example of a type that has invalid "extra inhabitant" bit patterns. Optional uses the null pointer for none when the type is a class (or a struct containing a class reference) because null is not a valid value of the type. This isn't restricted to classes, though; for instance, if you defined an enum Foo { case a, b, c }, then the type Foo? would use the bit pattern for 3 as nil because it is unused by the type.

You'll also want to be mindful of non-@_frozen public enums, which use a more abstract access pattern to allow for the type to add new cases in future.

1 Like

so for public non-frozen enums (Since I'm talking to Swift from another programming language, the two cases I need to deal with are public frozen and public non frozen); though I can't seem to spot much of a difference between the two? Do I need to do something special to make it use the more abstract access pttern for this?

Actually better question: Can I always do something like:

var m = alloca typeinfo->size
var tmp = alloca typeinfo->size
destructiveInjectEnumTag(tmp, $ss21_StringRepresentationV5_FormO6_smallyA2DmFWC)
assignWithTake(m, tmp, typeinfo)
...
typeinfo->destroy(m);

and for the case with payload data:

var m = alloca typeinfo->size
var tmp = alloca typeinfo->size
(*(void**)tmp) = someObject
destructiveInjectEnumTag(tmp, $ss21_StringRepresentationV5_FormO6_cocoayADyXl_tcADmFWC)
assignWithTake(m, tmp, typeinfo)
...
typeinfo->destroy(m);

?

Ie can I always use the non frozen public logical path for doing this, even if it IS frozen?

Yeah, I think that should work for all public enums in library-evolution-enabled binaries.

Terms of Service

Privacy Policy

Cookie Policy