Lowering `OpaqueValueExpr`-based abstraction as a closure

I'm processing @kavon's feedback in https://github.com/swiftlang/swift/pull/84789.

I'm trying to lower CollectionUpcastConversionExpr::ConversionPair as a closure with correct abstraction pattern. There is SILGenFunction::emitConvertedRValue() that can handle ClosureExpr, but cannot handle ConversionPair. My first attempt was to convert CollectionUpcastConversionExpr::ConversionPair into a short-lived ClosureExpr that exists only to be fed to emitConvertedRValue() and is detached from the rest of the AST.

Now based on @kavon's feedback, I'm trying to avoid AST generation in SILGen.

Options I'm considering:

  1. Use ClosureExpr as a representation of KeyConversion and ValueConversion - it was easy to adapt previous solution into this one, but I'm concerned that I don't fully understand the role of OpaqueValueExpr-based abstractions.
  2. Create override of SILGenFunction::emitConvertedRValue() that accepts ConversionPair - this sounds like the best approach to me, but seems to be a lot of work and may require more refactoring to avoid code duplication.
  3. Move logic for needsCustomConversion() into Sema, and re-write entire CollectionUpcastConversionExpr into ApplyExpr - sounds simple to implement, but this sounds like a SILGen logic, unrelated to Sema.

So far, I've tried the first approach, and I've faced an assertion when trying to create a ParamDecl of PrimaryArchetypeType in the following test case:

private func arrayUpCast<Ct: Base>(_ arr: [Ct]) -> [Base] {
  return arr
}
(collection_upcast_expr implicit type="[Base]" location=foo.swift:21:10 range=[foo.swift:21:10 - line:21:10]
  (declref_expr type="[Ct]" location=foo.swift:21:10 range=[foo.swift:21:10 - line:21:10] decl="MyCLI.(file).arrayUpCast(_:).arr@foo.swift:19:38" function_ref=unapplied)
  (value_conversion=archetype_to_super_expr implicit type="Base" location=foo.swift:21:10 range=[foo.swift:21:10 - line:21:10]
    (opaque_value_expr implicit type="Ct" location=foo.swift:21:10 range=[foo.swift:21:10 - line:21:10] 0x8304eadc0)))

What is the difference between PrimaryArchetypeType and GenericTypeParamType?
What is the correct way to transform one into another? Would usage of GenericTypeParamType instead of PrimaryArchetypeType inside key/value conversions lead to los of information? Aside from saving memory, does ConversionPair have other advantages over ClosureExpr?

1 Like

I've found my answers in https://download.swift.org/docs/assets/generics.pdf.

I've found TypeBase::mapTypeOutOfEnvironment().

No, and PrimaryArchetypeType is still used inside FunctionType of the closure.

This one I'm still very much interested to discuss. Why would it be a bad idea to replace all usages of OpaqueValueExpr with closures?

2 Likes

I've got option 1 to work (MR updated). One of the challenges compared to OpaqueValueExpr-based abstractions was dealing with preconcurrency diagnostics. Entering a closure normally resets state accumulated in AST walkers. I had to add an extra bit to ClosureExpr to mark conversion closures as such, and pass preconcurrency information through.

I've also looked a bit into option 2 and faced a challenge that closure needs some identity to generate a stable unique name for it. For normal closures it is solved by having a pointer to parent DeclContext plus discriminator. ConversionPair does not have it. So either it needs to be evolved into something that is more similar to ClosureExpr, or DeclContext +discriminator need to be tracked inside SILGen data structures. Actually I was already doing the latter as part of the original solution. So it is definitely doable, but I'm not sure if allocating discriminators from DeclContext in SILGen is allowed from architecture perspective.

2 Likes