Thoughts on fixing a SIL verification error and/or bad codegen involving default arguments & implicitly-opened existentials

If you call foo(bar) say, the structure of the OEE is like a let binding, so we have:

OpenExistentialExpr
  CallExpr <-- first sub-expression; the call to foo() itself
    DeclRefExpr foo()
    OpaqueValueExpr <-- this is the opened value, it's the "real" argument of the call
  DeclRefExpr [bar] <-- the second sub-expression; the existential being opened

The opened existential archetype is scoped to the OOE. It's not the result of the OOE.

I think that's probably the easiest way, yeah.

If someone wants to try it: change SILGenFunction::emitOpenExistentialExprImpl() to unconditionally record the expression in OpaqueValueExprs instead of only when it is an lvalue, and then move the rest of the function to RValueEmitter::visitOpaqueValueExpr(), with suitable adjustments. (In the lvalue case, we force the existential in SILGenLValue::visitOpaqueValueExpr()).

thanks for the feedback. a couple further questions:

can you clarify the distinction between "formal evaluation" and "initiation of formal accesses". today, it seems that side effects observable from evaluating the formal access's storage reference occur after those observable from "explicit" argument evaluation, even if the formal access argument occurs before the explicit argument in source. e.g.

func call(
  _ two: inout Int,
  _ one: Int,
) {}

class C {
  var _one: Int = 0
  var one: Int {
    get {
      print("get one")
      return _one
    }
    set { _one = newValue }
  }

  var _two: Int = 0
  var two: Int {
    get {
      print("get two")
      return _two
    }
    set { _two = newValue }
  }
}

func eval_order() {
  let c = C()
  call(&c.two, c.one)
  // prints:
  // get one
  // get two
}
eval_order()

is that behavior something you'd want/expect to be changed? personally i think it would make more sense, but have little grasp on how plausible that would be to do (setting aside the fact that i also currently have no idea how it'd be implemented).

would you mind elaborating on what this sort of thing would look like more concretely?

Formal evaluation of an l-value means, generally, evaluating the r-values in it. In SILGen terms, it means producing the LValue.

To be clear, this is a longstanding special case, not something you’d need to do.

thanks, but i feel i still may be missing something regarding the distinction between "formal evaluation" and "initiation of formal accesses" of explicit arguments. my assumption based on the description of things in the ownership manifesto is that the evaluation phase to procure the storage reference must occur first, and should happen left-to-right with respect to other explicit arguments. i'd like confirmation that that is not how things are actually handled today, so the outline you proposed above would have to change evaluation order of formal access arguments as well as implicitly-opened existentials to achieve the desired behavior. does that sound right?

edit: and maybe to make my point of confusion more clear: in which of the phases – evaluation or initiation – are side-effects of the access observable?

Well, the ownership manifesto says this:

The evaluation of a storage reference expression is divided into two phases: it is first formally evaluated to a storage reference, and then a formal access to that storage reference occurs for some duration. The two phases are often evaluated in immediate succession, but they can be separated in complex cases, such as when an inout argument is not the last argument to a call. The purpose of this phase division is to minimize the duration of the formal access while still preserving, to the greatest extent possible, Swift's left-to-right evaluation rules.

Formally evaluating to a storage reference generally means everything short of opening exclusivity scopes or calling accessors; those things are done in the formal access phase.

2 Likes