PType conversions

As I’ve been working on implementing PBetterLocus I’ve run into an issue with PType Inference. There currently seem to be no facilities for converting between values of two distinct PTypes that share the same virtual type.

In the course of thinking about this, I’ve developed an interface that I think seems reasonable at least for staged code:

abstract class PLocusCode {
  // Add these two methods
  // This one takes a region because it may need to allocate
  final def toCanonical(mb: EmitMethodBuilder[_], region: Value[Region]
  ): PCanonicalLocusCode = {
    val npt = PCanonicalLocus(rg, pt.required)
    this match {
      case _: PCanonicalLocusCode =>
        this
      case pc =>
        val code = EmitCodeBuilder.scopedCode[Long](mb) { cb =>
          val pv = pc.memoize(cb, "locus_val")
          // PCanonicalLocus' fields are required so this naked 
          // allocation is fine
          val addr = cb.newLocal("addr",
            npt.representation.allocate(region))
          cb += pv.contig(mb).store(mb, region, npt.contigAddr(addr))
          cb += Region.storeInt(npt.positionAddr(addr), pv.position())
          addr
        }
        new PCanonicalLocusCode(newPType, code)
    }
  }

  // This one doesn't because it doesn't need to allocate
  final def toBetter(mb: EmitMethodBuilder[_]): PBetterLocusCode = {
    val newPtype = PBetterLocusCode(pt.rg, pt.required)
    this match {
      case _: PBetterLocusCode =>
        this
      case pc =>
        val code = EmitCodeBuilder.scopedCode[Long](mb) { cb =>
          val rg = mb.getReferenceGenome(pt.rg)
          val pv = pc.memoize(cb, "locus_val")
          val contigIdx = rgc.invoke[String, Int]("contigIndex",
             pv.contig(mb).loadString())
          (contigIdx.toL << 32) | pv.position().toL
        }
        new PBetterLocusCode(newPType, code)
    }
  }
}

// And then on PCode
class PCode {
  def convert(toPt: PType,
    mb: EmitMethodBuilder[_],
    region: Value[Region]
  ): PCode {
    // FIXME requiredness??
    assert(toPt isOfType pt)
    toPt match {
       case _: PCanonicalLocus => asLocus.toCanonical(mb, region)
       case _: PBetterLocus => asLocus.toBetter(mb)
       // ...
    }
  }
}

Thoughts? This seems to be extensible in a way that won’t make it onerous, like adding cases for all the copyFrom* methods on PType.

Picking up from what Cotton was saying in the last design meeting, I think toCanonical/toBetter put the conversion in the wrong place. PTypes that can be freely converted to like PCanonicalLocus and PBetterLocus should have a constructor method

def makeCopy(from: PLocusCode, mb: EmitMethodBuilder[_], region: Value[Region]): PCanonicalLocusCode

(not sure about the name) which can make a canonical PType from an arbitrary one, using only the abstract interface to read the copied value. That seems like the right place from an encapsulation perspective, since the copy method depends on the implementation of the copied to type, but doesn’t need to know the implementation of the copied from type. Any PType implementing such a method we could call “universal” or “universally constructible”.

Actually does PType.copyFromPValue not already serve this purpose?

def copyFromPValue(mb: EmitMethodBuilder[_], region: Value[Region], pv: PCode): PCode

You could still implement the convert method using this:

class PCode {
  def convert(toPt: PType,
    mb: EmitMethodBuilder[_],
    region: Value[Region]
  ): PCode {
    // FIXME requiredness??
    assert(toPt isOfType pt)
    toPt.copyFromPValue(mb, region, this)
  }
}

Don’t want you to think this is a dead conversation – I’m digesting this and working through the set of requirements for pvalue construction/conversion, will get back to you soon.