All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.scalanative.nscplugin.NirGenExpr.scala Maven / Gradle / Ivy

There is a newer version: 0.5.6
Show newest version
package scala.scalanative.nscplugin

import scala.language.implicitConversions
import scala.annotation.tailrec

import dotty.tools.dotc.ast
import ast.tpd._
import ast.TreeInfo._
import dotty.tools.backend.ScalaPrimitivesOps._
import dotty.tools.dotc.core
import core.Contexts._
import core.Symbols._
import core.Names._
import core.Types._
import core.Constants._
import core.StdNames._
import core.Flags._
import core.Denotations._
import core.SymDenotations._
import core.TypeErasure.ErasedValueType
import core._
import dotty.tools.FatalError
import dotty.tools.dotc.report
import dotty.tools.dotc.transform
import transform.{ValueClasses, Erasure}
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
import scala.scalanative.nscplugin.CompilerCompat.SymUtilsCompat.*

import scala.collection.mutable
import scala.scalanative.nir
import nir._
import scala.scalanative.util.ScopedVar.scoped
import scala.scalanative.util.unsupported
import scala.scalanative.util.StringUtils
import dotty.tools.dotc.ast.desugar

trait NirGenExpr(using Context) {
  self: NirCodeGen =>
  import positionsConversions.fromSpan

  sealed case class ValTree(value: nir.Val) extends Tree
  sealed case class ContTree(f: () => nir.Val) extends Tree

  class ExprBuffer(using fresh: Fresh) extends FixupBuffer {
    buf =>
    def genExpr(tree: Tree): Val = {
      tree match {
        case EmptyTree      => Val.Unit
        case ValTree(value) => value
        case ContTree(f)    => f()
        case tree: Apply =>
          val updatedTree = lazyValsAdapter.transformApply(tree)
          genApply(updatedTree)
        case tree: Assign         => genAssign(tree)
        case tree: Block          => genBlock(tree)
        case tree: Closure        => genClosure(tree)
        case tree: Labeled        => genLabelDef(tree)
        case tree: Ident          => genIdent(tree)
        case tree: If             => genIf(tree)
        case tree: JavaSeqLiteral => genJavaSeqLiteral(tree)
        case tree: Literal        => genLiteral(tree)
        case tree: Match          => genMatch(tree)
        case tree: Return         => genReturn(tree)
        case tree: Select         => genSelect(tree)
        case tree: This           => genThis(tree)
        case tree: Try            => genTry(tree)
        case tree: Typed          => genTyped(tree)
        case tree: TypeApply      => genTypeApply(tree)
        case tree: ValDef         => genValDef(tree)
        case tree: WhileDo        => genWhileDo(tree)
        case _ =>
          throw FatalError(
            s"""Unexpected tree in genExpr: `${tree}`
             raw=$tree
             pos=${tree.span}
             """
          )
      }
    }

    def genApply(app: Apply): Val = {
      given nir.Position = app.span
      val Apply(fun, args) = app

      val sym = fun.symbol
      def isStatic = sym.owner.isStaticOwner
      def qualifier = qualifierOf(fun)

      fun match {
        case _: TypeApply => genApplyTypeApply(app)
        case Select(Super(_, _), _) =>
          genApplyMethod(
            sym,
            statically = true,
            curMethodThis.get.get,
            args
          )
        case Select(New(_), nme.CONSTRUCTOR) =>
          genApplyNew(app)
        case _ if sym == defn.newArrayMethod =>
          val Seq(
            Literal(componentType: Constant),
            arrayType,
            SeqLiteral(dimensions, _)
          ) = args: @unchecked
          if (dimensions.size == 1)
            val length = genExpr(dimensions.head)
            buf.arrayalloc(genType(componentType.typeValue), length, unwind)
          else genApplyMethod(sym, statically = isStatic, qualifier, args)
        case _ =>
          if (nirPrimitives.isPrimitive(fun)) genApplyPrimitive(app)
          else if (Erasure.Boxing.isBox(sym))
            val arg = args.head
            genApplyBox(arg.tpe, arg)
          else if (Erasure.Boxing.isUnbox(sym))
            genApplyUnbox(app.tpe, args.head)
          else genApplyMethod(sym, statically = isStatic, qualifier, args)
      }
    }

    def genAssign(tree: Assign): Val = {
      val Assign(lhsp, rhsp) = tree
      given nir.Position = tree.span

      desugarTree(lhsp) match {
        case sel @ Select(qualp, _) =>
          def rhs = genExpr(rhsp)
          val sym = sel.symbol
          val name = genFieldName(sym)
          if (sym.isExtern) {
            // Ignore intrinsic call to extern in class constructor
            // It would always present and would lead to undefined behaviour otherwise
            val shouldIgnoreAssign =
              curMethodSym.get.isClassConstructor &&
                rhsp.symbol == defnNir.UnsafePackage_extern
            if (shouldIgnoreAssign) Val.Unit
            else {
              val externTy = genExternType(sel.tpe)
              genStoreExtern(externTy, sym, rhs)
            }
          } else {
            val qual =
              if (sym.isStaticMember) genModule(qualp.symbol)
              else genExpr(qualp)
            val ty = genType(sel.tpe)
            buf.fieldstore(ty, qual, name, rhs, unwind)
          }

        case id: Ident =>
          val rhs = genExpr(rhsp)
          val slot = curMethodEnv.resolve(id.symbol)
          buf.varstore(slot, rhs, unwind)
      }
    }

    def genBlock(block: Block): Val = {
      val Block(stats, last) = block

      def isCaseLabelDef(tree: Tree) =
        tree.isInstanceOf[Labeled] && tree.symbol.isAllOf(SyntheticCase)

      def translateMatch(last: Labeled) = {
        val (prologue, cases) = stats.span(!isCaseLabelDef(_))
        val labels = cases.asInstanceOf[List[Labeled]]
        genMatch(prologue, labels :+ last)
      }

      last match {
        case label: Labeled if isCaseLabelDef(label) =>
          translateMatch(label)

        case Apply(
              TypeApply(Select(label: Labeled, nme.asInstanceOf_), _),
              _
            ) if isCaseLabelDef(label) =>
          translateMatch(label)

        case _ =>
          stats.foreach(genExpr)
          genExpr(last)
      }
    }

    // Scala Native does not have any special treatment for closures.
    // We reimplement the anonymous class generation which was originally used on
    // Scala 2.11 and earlier.
    //
    // Each anonymous function gets a generated class for it,
    // similarly to closure encoding on Scala 2.11 and earlier:
    //
    //   class AnonFunX extends java.lang.Object with FunctionalInterface {
    //     var capture1: T1
    //     ...
    //     var captureN: TN
    //     def (this, v1: T1, ..., vN: TN): Unit = {
    //       java.lang.Object.(this)
    //       this.capture1 = v1
    //       ...
    //       this.captureN = vN
    //     }
    //     def samMethod(param1: Tp1, ..., paramK: TpK): Tret = {
    //       EnclsoingClass.this.staticMethod(param1, ..., paramK, this.capture1, ..., this.captureN)
    //     }
    //   }
    //
    // Bridges might require multiple samMethod variants to be created.
    def genClosure(tree: Closure): Val = {
      given nir.Position = tree.span
      val Closure(env, fun, functionalInterface) = tree
      val treeTpe = tree.tpe.typeSymbol
      val funSym = fun.symbol
      val funInterfaceSym = functionalInterface.tpe.typeSymbol
      val isFunction =
        !funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym)

      val anonClassName = {
        val Global.Top(className) = genTypeName(curClassSym)
        val suffix = "$$Lambda$" + curClassFresh.get.apply().id
        nir.Global.Top(className + suffix)
      }

      val isStaticCall = funSym.isStaticInNIR
      val allCaptureValues =
        if (isStaticCall) env
        else qualifierOf(fun) :: env
      val captureSyms = allCaptureValues.map(_.symbol)
      val captureTypesAndNames = {
        for
          (tree, idx) <- allCaptureValues.zipWithIndex
          tpe = tree match {
            case This(iden) => genType(fun.symbol.owner)
            case _          => genType(tree.tpe)
          }
          name = anonClassName.member(nir.Sig.Field(s"capture$idx"))
        yield (tpe, name)
      }
      val (captureTypes, captureNames) = captureTypesAndNames.unzip

      def genAnonymousClass: nir.Defn = {
        val traits =
          if (functionalInterface.isEmpty) genTypeName(treeTpe) :: Nil
          else {
            val parents = funInterfaceSym.info.parents
            genTypeName(funInterfaceSym) +: parents.collect {
              case tpe if tpe.typeSymbol.isTraitOrInterface =>
                genTypeName(tpe.typeSymbol)
            }
          }

        nir.Defn.Class(
          attrs = Attrs.None,
          name = anonClassName,
          parent = Some(nir.Rt.Object.name),
          traits = traits
        )
      }

      def genCaptureFields: List[nir.Defn] = {
        for (tpe, name) <- captureTypesAndNames
        yield nir.Defn.Var(
          attrs = Attrs.None,
          name = name,
          ty = tpe,
          rhs = Val.Zero(tpe)
        )
      }

      val ctorName = anonClassName.member(Sig.Ctor(captureTypes))
      val ctorTy = nir.Type.Function(
        Type.Ref(anonClassName) +: captureTypes,
        Type.Unit
      )

      def genAnonymousClassCtor: nir.Defn = {
        val body = {
          val fresh = Fresh()
          val buf = new nir.Buffer()(fresh)

          val superTy = nir.Type.Function(Seq(Rt.Object), Type.Unit)
          val superName = Rt.Object.name.member(Sig.Ctor(Seq.empty))
          val superCtor = Val.Global(superName, Type.Ptr)

          val self = Val.Local(fresh(), Type.Ref(anonClassName))
          val captureFormals = captureTypes.map(Val.Local(fresh(), _))
          buf.label(fresh(), self +: captureFormals)
          buf.call(superTy, superCtor, Seq(self), Next.None)
          captureNames.zip(captureFormals).foreach { (name, capture) =>
            buf.fieldstore(capture.ty, self, name, capture, Next.None)
          }
          buf.ret(Val.Unit)

          buf.toSeq
        }

        nir.Defn.Define(Attrs.None, ctorName, ctorTy, body)
      }

      def resolveAnonClassMethods: List[Symbol] = {
        val samMethods =
          if (funInterfaceSym.exists) funInterfaceSym.info.possibleSamMethods
          else treeTpe.info.possibleSamMethods

        samMethods
          .map(_.symbol)
          .toList
      }

      def genAnonClassMethod(sym: Symbol): nir.Defn = {
        val Global.Member(_, funSig) = genName(sym): @unchecked
        val Sig.Method(_, sigTypes :+ retType, _) = funSig.unmangled: @unchecked

        val selfType = Type.Ref(anonClassName)
        val methodName = anonClassName.member(funSig)
        val paramTypes = selfType +: sigTypes
        val paramSyms = funSym.paramSymss.flatten

        def genBody = {
          given fresh: Fresh = Fresh()
          given buf: ExprBuffer = new ExprBuffer()
          scoped(
            curFresh := fresh,
            curExprBuffer := buf,
            curMethodEnv := MethodEnv(fresh),
            curMethodLabels := MethodLabelsEnv(fresh),
            curMethodInfo := CollectMethodInfo(),
            curUnwindHandler := None
          ) {
            val self = Val.Local(fresh(), selfType)
            val params = sigTypes.map(Val.Local(fresh(), _))

            buf.label(fresh(), self +: params)
            // At this point, the type parameter symbols are all Objects.
            // We need to transform them, so that their type conforms to
            // what the apply method expects:
            // - values that can be unboxed, are unboxed
            // - otherwise, the value is cast to the appropriate type
            val paramVals =
              for (param, sym) <- params.zip(paramSyms)
              yield ensureUnboxed(param, sym.info.finalResultType)

            val captureVals =
              for (sym, (tpe, name)) <- captureSyms.zip(captureTypesAndNames)
              yield buf.fieldload(tpe, self, name, unwind)

            val allVals = (captureVals ++ paramVals).toList.map(ValTree(_))
            val res = if (isStaticCall) {
              scoped(curMethodThis := None) {
                buf.genApplyStaticMethod(
                  funSym,
                  qualifierOf(fun).symbol,
                  allVals
                )
              }
            } else {
              val thisVal :: argVals = allVals: @unchecked
              scoped(curMethodThis := Some(thisVal.value)) {
                buf.genApplyMethod(
                  funSym,
                  statically = false,
                  thisVal,
                  argVals
                )
              }
            }

            val retValue =
              if (retType == res.ty) res
              else
                // Get the result type of the lambda after erasure, when entering posterasure.
                // This allows to recover the correct type in case value classes are involved.
                // In that case, the type will be an ErasedValueType.
                buf.ensureBoxed(res, funSym.info.finalResultType)
            buf.ret(retValue)
            buf.toSeq
          }
        }

        nir.Defn.Define(
          Attrs.None,
          methodName,
          Type.Function(paramTypes, retType),
          genBody
        )
      }

      def genAnonymousClassMethods: List[nir.Defn] =
        resolveAnonClassMethods
          .map(genAnonClassMethod)

      generatedDefns ++= {
        genAnonymousClass ::
          genAnonymousClassCtor ::
          genCaptureFields :::
          genAnonymousClassMethods
      }

      def allocateClosure() = {
        val alloc = buf.classalloc(anonClassName, unwind)
        val captures = allCaptureValues.map(genExpr)
        buf.call(
          ctorTy,
          Val.Global(ctorName, Type.Ptr),
          alloc +: captures,
          unwind
        )
        alloc
      }
      allocateClosure()
    }

    def genIdent(tree: Ident): Val =
      desugarIdent(tree) match {
        case Ident(_) =>
          val sym = tree.symbol
          given nir.Position = tree.span
          if (curMethodInfo.mutableVars.contains(sym))
            buf.varload(curMethodEnv.resolve(sym), unwind)
          else if (sym.is(Module))
            genModule(sym)
          else curMethodEnv.resolve(sym)
        case desuaged: Select =>
          genSelect(desuaged)
        case tree =>
          throw FatalError(s"Unsupported desugared ident tree: $tree")
      }

    def genIf(tree: If): Val = {
      given nir.Position = tree.span
      val If(cond, thenp, elsep) = tree
      def isUnitType(tpe: Types.Type) =
        tpe =:= defn.UnitType || defn.isBoxedUnitClass(tpe.sym)
      val retty =
        if (isUnitType(thenp.tpe) || isUnitType(elsep.tpe)) nir.Type.Unit
        else genType(tree.tpe)
      genIf(retty, cond, thenp, elsep)
    }

    def genIf(
        retty: nir.Type,
        condp: Tree,
        thenp: Tree,
        elsep: Tree,
        ensureLinktime: Boolean = false
    )(using
        nir.Position
    ): Val = {
      val thenn, elsen, mergen = fresh()
      val mergev = Val.Local(fresh(), retty)

      locally {
        given nir.Position = condp.span
        getLinktimeCondition(condp) match {
          case Some(cond) =>
            curMethodUsesLinktimeResolvedValues = true
            buf.branchLinktime(cond, Next(thenn), Next(elsen))
          case None =>
            if ensureLinktime then
              report.error(
                "Cannot resolve given condition in linktime, it might be depending on runtime value",
                condp.srcPos
              )
            val cond = genExpr(condp)
            buf.branch(cond, Next(thenn), Next(elsen))(using condp.span)
        }
      }

      locally {
        given nir.Position = thenp.span
        buf.label(thenn)
        val thenv = genExpr(thenp)
        buf.jumpExcludeUnitValue(retty)(mergen, thenv)
      }
      locally {
        given nir.Position = elsep.span
        buf.label(elsen)
        val elsev = genExpr(elsep)
        buf.jumpExcludeUnitValue(retty)(mergen, elsev)
      }
      buf.labelExcludeUnitValue(mergen, mergev)
    }

    def genJavaSeqLiteral(tree: JavaSeqLiteral): Val = {
      val JavaArrayType(elemTpe) = tree.tpe: @unchecked
      val arrayLength = Val.Int(tree.elems.length)

      val elems = tree.elems
      val elemty = genType(elemTpe)
      val values = genSimpleArgs(elems)
      given nir.Position = tree.span

      if (values.forall(_.isCanonical) && values.exists(v => !v.isZero))
        buf.arrayalloc(elemty, Val.ArrayValue(elemty, values), unwind)
      else
        val alloc = buf.arrayalloc(elemty, Val.Int(values.length), unwind)
        for (v, i) <- values.zipWithIndex if !v.isZero do
          given nir.Position = elems(i).span
          buf.arraystore(elemty, alloc, Val.Int(i), v, unwind)
        alloc
    }

    def genLabelDef(label: Labeled): Val = {
      given nir.Position = label.span
      val Labeled(bind, body) = label
      assert(bind.body == EmptyTree, "non-empty Labeled bind body")

      val (labelEntry, labelExit) = curMethodLabels.enterLabel(label)
      val labelExitParam = Val.Local(fresh(), genType(bind.tpe))
      curMethodLabels.enterExitType(labelExit, labelExitParam.ty)

      buf.jump(Next(labelEntry))

      buf.label(labelEntry, Nil)
      buf.jumpExcludeUnitValue(labelExitParam.ty)(
        labelExit,
        genExpr(label.expr)
      )

      buf.labelExcludeUnitValue(labelExit, labelExitParam)
    }

    def genLiteral(lit: Literal): Val = {
      given nir.Position = lit.span
      val value = lit.const

      value.tag match {
        case ClazzTag => genTypeValue(value.typeValue)
        case _        => genLiteralValue(lit)
      }
    }

    private def genLiteralValue(lit: Literal): Val = {
      val value = lit.const
      value.tag match {
        case UnitTag    => Val.Unit
        case NullTag    => Val.Null
        case BooleanTag => if (value.booleanValue) Val.True else Val.False
        case ByteTag    => Val.Byte(value.intValue.toByte)
        case ShortTag   => Val.Short(value.intValue.toShort)
        case CharTag    => Val.Char(value.intValue.toChar)
        case IntTag     => Val.Int(value.intValue)
        case LongTag    => Val.Long(value.longValue)
        case FloatTag   => Val.Float(value.floatValue)
        case DoubleTag  => Val.Double(value.doubleValue)
        case StringTag  => Val.String(value.stringValue)
      }
    }

    def genMatch(m: Match): Val = {
      given nir.Position = m.span
      val Match(scrutp, allcaseps) = m
      case class Case(
          name: Local,
          value: Val,
          tree: Tree,
          position: nir.Position
      )

      // Extract switch cases and assign unique names to them.
      val caseps: Seq[Case] = allcaseps.flatMap {
        case CaseDef(Ident(nme.WILDCARD), _, _) =>
          Seq.empty
        case cd @ CaseDef(pat, guard, body) =>
          assert(guard.isEmpty, "CaseDef guard was not empty")
          val vals: Seq[Val] = pat match {
            case lit: Literal =>
              List(genLiteralValue(lit))
            case Alternative(alts) =>
              alts.map {
                case lit: Literal => genLiteralValue(lit)
              }
            case _ =>
              Nil
          }
          vals.map(Case(fresh(), _, body, cd.span: nir.Position))
      }

      // Extract default case.
      val defaultp: Tree = allcaseps.collectFirst {
        case c @ CaseDef(Ident(nme.WILDCARD), _, body) => body
      }.get

      val retty = genType(m.tpe)
      val scrut = genExpr(scrutp)

      // Generate code for the switch and its cases.
      def genSwitch(): Val = {
        // Generate some more fresh names and types.
        val casenexts = caseps.map { case Case(n, v, _, _) => Next.Case(v, n) }
        val defaultnext = Next(fresh())
        val merge = fresh()
        val mergev = Val.Local(fresh(), retty)

        val defaultCasePos: nir.Position = defaultp.span

        // Generate code for the switch and its cases.
        val scrut = genExpr(scrutp)
        buf.switch(scrut, defaultnext, casenexts)
        buf.label(defaultnext.name)(using defaultCasePos)
        buf.jumpExcludeUnitValue(retty)(merge, genExpr(defaultp))(using
          defaultCasePos
        )
        caseps.foreach {
          case Case(n, _, expr, pos) =>
            given nir.Position = pos
            buf.label(n)
            val caseres = genExpr(expr)
            buf.jumpExcludeUnitValue(retty)(merge, caseres)
        }
        buf.labelExcludeUnitValue(merge, mergev)
      }

      def genIfsChain(): Val = {
        /* Default label needs to be generated before any others and then added to
         * current MethodEnv. It's label might be referenced in any of them in
         * case of match with guards, eg.:
         *
         * "Hello, World!" match {
         *  case "Hello" if cond1 => "foo"
         *  case "World" if cond2 => "bar"
         *  case _ if cond3 => "bar-baz"
         *  case _ => "baz-bar"
         * }
         *
         * might be translated to something like:
         *
         * val x1 = "Hello, World!"
         * if(x1 == "Hello"){ if(cond1) "foo" else default4() }
         * else if (x1 == "World"){ if(cond2) "bar" else default4() }
         * else default4()
         *
         * def default4() = if(cond3) "bar-baz" else "baz-bar
         *
         * We need to make sure to only generate LabelDef at this stage.
         * Generating other ASTs and mutating state might lead to unexpected
         * runtime errors.
         */
        val optDefaultLabel = defaultp match {
          case label: Labeled => Some(genLabelDef(label))
          case _              => None
        }

        def loop(cases: List[Case]): Val = {
          cases match {
            case Case(_, caze, body, p) :: elsep =>
              given nir.Position = p

              val cond =
                buf.genClassEquality(
                  leftp = ValTree(scrut),
                  rightp = ValTree(caze),
                  ref = false,
                  negated = false
                )
              buf.genIf(
                retty = retty,
                condp = ValTree(cond),
                thenp = ContTree(() => genExpr(body)),
                elsep = ContTree(() => loop(elsep))
              )

            case Nil => optDefaultLabel.getOrElse(genExpr(defaultp))
          }
        }
        loop(caseps.toList)
      }

      /* Since 2.13 we need to enforce that only Int switch cases reach backend
       * For all other cases we're generating If-else chain */
      val isIntMatch = scrut.ty == Type.Int &&
        caseps.forall(_._2.ty == Type.Int)

      if (isIntMatch) genSwitch()
      else genIfsChain()
    }

    private def genMatch(prologue: List[Tree], lds: List[Labeled]): Val = {
      // Generate prologue expressions.
      prologue.foreach(genExpr(_))

      // Enter symbols for all labels and jump to the first one.
      lds.foreach(curMethodLabels.enterLabel)
      val firstLd = lds.head
      given nir.Position = firstLd.span
      buf.jump(Next(curMethodLabels.resolveEntry(firstLd)))

      // Generate code for all labels and return value of the last one.
      lds.map(genLabelDef(_)).last
    }

    def genModule(sym: Symbol)(using nir.Position): Val = {
      val moduleSym = if (sym.isTerm) sym.moduleClass else sym
      val name = genModuleName(moduleSym)
      buf.module(name, unwind)
    }

    def genReturn(tree: Return): Val = {
      val Return(exprp, from) = tree
      given nir.Position = tree.span
      val rhs = genExpr(exprp)
      val fromSym = from.symbol
      val label = Option.when(fromSym.is(Label)) {
        curMethodLabels.resolveExit(fromSym)
      }
      genReturn(rhs, label)
    }

    def genReturn(value: Val, from: Option[Local] = None)(using
        pos: nir.Position
    ): Val = {
      val retv =
        if (curMethodIsExtern.get)
          val Type.Function(_, retty) = genExternMethodSig(curMethodSym)
          toExtern(retty, value)
        else value

      from match {
        case Some(label) =>
          val retty = curMethodLabels.resolveExitType(label)
          buf.jumpExcludeUnitValue(retty)(label, retv)
        case _ if retv.ty == Type.Unit => buf.ret(Val.Unit)
        case _                         => buf.ret(retv)
      }
      Val.Unit
    }

    def genSelect(tree: Select): Val = {
      given nir.Position = tree.span
      val Select(qualp, selp) = tree

      val sym = tree.symbol
      val owner = sym.owner

      if (sym.is(Module)) genModule(sym)
      else if (sym.isStaticInNIR && !sym.isExtern)
        genStaticMember(sym, qualp.symbol)
      else if (sym.is(Method))
        genApplyMethod(sym, statically = false, qualp, Seq.empty)
      else if (owner.isStruct) {
        val index = owner.info.decls.filter(_.isField).toList.indexOf(sym)
        val qual = genExpr(qualp)
        buf.extract(qual, Seq(index), unwind)
      } else {
        val ty = genType(tree.tpe)
        val qual =
          if (sym.isStaticMember) genModule(owner)
          else genExpr(qualp)
        val name = genFieldName(tree.symbol)
        if (sym.isExtern) {
          val externTy = genExternType(tree.tpe)
          genLoadExtern(ty, externTy, tree.symbol)
        } else {
          buf.fieldload(ty, qual, name, unwind)
        }
      }
    }

    def genThis(tree: This): Val = {
      given nir.Position = tree.span
      val sym = tree.symbol
      def currentThis = curMethodThis.get
      def currentClass = curClassSym.get
      def currentMethod = curMethodSym.get

      def canUseCurrentThis = currentThis.nonEmpty &&
        (sym == currentClass || currentMethod.owner == currentClass)
      val canLoadAsModule =
        sym != currentClass &&
          sym.is(ModuleClass, butNot = Package) ||
          sym.isPackageObject

      if (canLoadAsModule) genModule(sym)
      else if (canUseCurrentThis) currentThis.get
      else
        report.error(
          s"Cannot resolve `this` instance for ${tree}",
          tree.sourcePos
        )
        Val.Zero(genType(currentClass))
    }

    def genTry(tree: Try): Val = tree match {
      case Try(expr, catches, finalizer)
          if catches.isEmpty && finalizer.isEmpty =>
        genExpr(expr)
      case Try(expr, catches, finalizer) =>
        val retty = genType(tree.tpe)
        genTry(retty, expr, catches, finalizer)
    }

    private def genTry(
        retty: nir.Type,
        expr: Tree,
        catches: List[Tree],
        finallyp: Tree
    ): Val = {
      given nir.Position = expr.span
      val handler, excn, normaln, mergen = fresh()
      val excv = Val.Local(fresh(), Rt.Object)
      val mergev = Val.Local(fresh(), retty)

      // Nested code gen to separate out try/catch-related instructions.
      val nested = ExprBuffer()
      scoped(curUnwindHandler := Some(handler)) {
        nested.label(normaln)
        val res = nested.genExpr(expr)
        nested.jumpExcludeUnitValue(retty)(mergen, res)
      }
      locally {
        nested.label(handler, Seq(excv))
        val res = nested.genTryCatch(retty, excv, mergen, catches)
        nested.jumpExcludeUnitValue(retty)(mergen, res)
      }

      // Append finally to the try/catch instructions and merge them back.
      val insts =
        if (finallyp.isEmpty) nested.toSeq
        else genTryFinally(finallyp, nested.toSeq)

      // Append try/catch instructions to the outher instruction buffer.
      buf.jump(Next(normaln))
      buf ++= insts
      buf.labelExcludeUnitValue(mergen, mergev)
    }

    private def genTryCatch(
        retty: nir.Type,
        exc: Val,
        mergen: Local,
        catches: List[Tree]
    )(using exprPos: nir.Position): Val = {
      val cases = catches.map {
        case cd @ CaseDef(pat, _, body) =>
          val (excty, symopt) = pat match {
            case Typed(Ident(nme.WILDCARD), tpt) =>
              genType(tpt.tpe) -> None
            case Ident(nme.WILDCARD) =>
              genType(defn.ThrowableClass.info.resultType) -> None
            case Bind(_, _) =>
              genType(pat.tpe) -> Some(pat.symbol)
          }
          val f = { () =>
            symopt.foreach { sym =>
              val cast = buf.as(excty, exc, unwind)(cd.span)
              curMethodEnv.enter(sym, cast)
            }
            val res = genExpr(body)
            buf.jumpExcludeUnitValue(retty)(mergen, res)
            Val.Unit
          }
          (excty, f, exprPos)
      }

      def wrap(cases: Seq[(nir.Type, () => Val, nir.Position)]): Val =
        cases match {
          case Seq() =>
            buf.raise(exc, unwind)
            Val.Unit
          case (excty, f, pos) +: rest =>
            val cond = buf.is(excty, exc, unwind)(pos)
            genIf(
              retty,
              ValTree(cond),
              ContTree(f),
              ContTree(() => wrap(rest))
            )(using pos)
        }

      wrap(cases)
    }

    private def genTryFinally(
        finallyp: Tree,
        insts: Seq[nir.Inst]
    ): Seq[Inst] = {
      val labels =
        insts.collect {
          case Inst.Label(n, _) => n
        }.toSet
      def internal(cf: Inst.Cf) = cf match {
        case inst @ Inst.Jump(n) =>
          labels.contains(n.name)
        case inst @ Inst.If(_, n1, n2) =>
          labels.contains(n1.name) && labels.contains(n2.name)
        case inst @ Inst.LinktimeIf(_, n, n2) =>
          labels.contains(n.name) && labels.contains(n2.name)
        case inst @ Inst.Switch(_, n, ns) =>
          labels.contains(n.name) && ns.forall(n => labels.contains(n.name))
        case inst @ Inst.Throw(_, n) =>
          (n ne Next.None) && labels.contains(n.name)
        case _ =>
          false
      }

      val finalies = new ExprBuffer
      val transformed = insts.map {
        case cf: Inst.Cf if internal(cf) =>
          // We don't touch control-flow within try/catch block.
          cf
        case cf: Inst.Cf =>
          // All control-flow edges that jump outside the try/catch block
          // must first go through finally block if it's present. We generate
          // a new copy of the finally handler for every edge.
          val finallyn = fresh()
          finalies.label(finallyn)(cf.pos)
          val res = finalies.genExpr(finallyp)
          finalies += cf
          // The original jump outside goes through finally block first.
          Inst.Jump(Next(finallyn))(cf.pos)
        case inst =>
          inst
      }
      transformed ++ finalies.toSeq
    }

    def genTyped(tree: Typed): Val = tree match {
      case Typed(Super(_, _), _) => curMethodThis.get.get
      case Typed(expr, _)        => genExpr(expr)
    }

    def genTypeApply(tree: TypeApply): Val = {
      given nir.Position = tree.span
      val TypeApply(fun @ Select(receiverp, _), targs) = tree: @unchecked

      val funSym = fun.symbol
      val fromty = genType(receiverp.tpe)
      val toty = genType(targs.head.tpe)
      def boxty = genBoxType(targs.head.tpe)
      val value = genExpr(receiverp)
      def boxed = boxValue(receiverp.tpe, value)(using receiverp.span)

      if (funSym == defn.Any_isInstanceOf) buf.is(boxty, boxed, unwind)
      else if (funSym == defn.Any_asInstanceOf)
        (fromty, toty) match {
          case _ if boxed.ty == boxty =>
            boxed
          case (_: Type.PrimitiveKind, _: Type.PrimitiveKind) =>
            genCoercion(value, fromty, toty)
          case (_, Type.Nothing) =>
            val isNullL, notNullL = fresh()
            val isNull = buf.comp(Comp.Ieq, boxed.ty, boxed, Val.Null, unwind)
            buf.branch(isNull, Next(isNullL), Next(notNullL))
            buf.label(isNullL)
            buf.raise(Val.Null, unwind)
            buf.label(notNullL)
            buf.as(Rt.RuntimeNothing, boxed, unwind)
            buf.unreachable(unwind)
            buf.label(fresh())
            Val.Zero(Type.Nothing)
          case _ =>
            val cast = buf.as(boxty, boxed, unwind)
            unboxValue(tree.tpe, partial = true, cast)
        }
      else {
        report.error("Unkown case genTypeApply: " + funSym, tree.sourcePos)
        Val.Null
      }
    }

    def genValDef(vd: ValDef): Val = {
      given nir.Position = vd.span
      val rhs = genExpr(vd.rhs)
      val isMutable = curMethodInfo.mutableVars.contains(vd.symbol)
      if (vd.symbol.isExtern)
        checkExplicitReturnTypeAnnotation(vd, "extern field")
      if (isMutable)
        val slot = curMethodEnv.resolve(vd.symbol)
        buf.varstore(slot, rhs, unwind)
      else
        curMethodEnv.enter(vd.symbol, rhs)
        Val.Unit
    }

    def genWhileDo(wd: WhileDo): Val = {
      val WhileDo(cond, body) = wd
      val condLabel, bodyLabel, exitLabel = fresh()

      locally {
        given nir.Position = wd.span
        buf.jump(Next(condLabel))
      }

      locally {
        given nir.Position = cond.span
        buf.label(condLabel)
        val genCond =
          if (cond == EmptyTree) nir.Val.Bool(true)
          else genExpr(cond)
        buf.branch(genCond, Next(bodyLabel), Next(exitLabel))
      }

      locally {
        given nir.Position = body.span
        buf.label(bodyLabel)
        val _ = genExpr(body)
        buf.jump(condLabel, Nil)
      }

      locally {
        given nir.Position = wd.span.endPos
        buf.label(exitLabel, Seq.empty)
        if (cond == EmptyTree) Val.Zero(genType(defn.NothingClass))
        else Val.Unit
      }
    }

    private def genApplyBox(st: SimpleType, argp: Tree): Val = {
      given nir.Position = argp.span
      val value = genExpr(argp)
      buf.box(genBoxType(st), value, unwind)
    }

    private def genApplyUnbox(st: SimpleType, argp: Tree)(using
        nir.Position
    ): Val = {
      val value = genExpr(argp)
      value.ty match {
        case _: scalanative.nir.Type.I | _: scalanative.nir.Type.F =>
          // No need for unboxing, fixing some slack generated by the general
          // purpose Scala compiler.
          value
        case _ =>
          buf.unbox(genBoxType(st), value, unwind)
      }
    }

    private def genApplyPrimitive(app: Apply): Val = {
      import NirPrimitives._
      import dotty.tools.backend.ScalaPrimitivesOps._
      given nir.Position = app.span
      val Apply(fun, args) = app
      val Select(receiver, _) = desugarTree(fun): @unchecked

      val sym = app.symbol
      val code = nirPrimitives.getPrimitive(app, receiver.tpe)
      if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
        genSimpleOp(app, receiver :: args, code)
      else if (code == THROW) genThrow(app, args)
      else if (code == CONCAT) genStringConcat(receiver, args.head)
      else if (code == HASH) genHashCode(args.head)
      else if (code == BOXED_UNIT) Val.Unit
      else if (code == SYNCHRONIZED) genSynchronized(receiver, args.head)
      else if (isArrayOp(code) || code == ARRAY_CLONE) genArrayOp(app, code)
      else if (isCoercion(code)) genCoercion(app, receiver, code)
      else if (NirPrimitives.isRawPtrOp(code)) genRawPtrOp(app, code)
      else if (NirPrimitives.isRawCastOp(code)) genRawCastOp(app, code)
      else if (NirPrimitives.isUnsignedOp(code)) genUnsignedOp(app, code)
      else if (code == CFUNCPTR_APPLY) genCFuncPtrApply(app)
      else if (code == CFUNCPTR_FROM_FUNCTION) genCFuncFromScalaFunction(app)
      else if (code == STACKALLOC) genStackalloc(app)
      else if (code == CQUOTE) genCQuoteOp(app)
      else if (code == CLASS_FIELD_RAWPTR) genClassFieldRawPtr(app)
      else if (code == REFLECT_SELECTABLE_SELECTDYN)
        // scala.reflect.Selectable.selectDynamic
        genReflectiveCall(app, isSelectDynamic = true)
      else if (code == REFLECT_SELECTABLE_APPLYDYN)
        // scala.reflect.Selectable.applyDynamic
        genReflectiveCall(app, isSelectDynamic = false)
      else {
        report.error(
          s"Unknown primitive operation: ${sym.fullName}(${fun.symbol.showName})",
          app.sourcePos
        )
        Val.Null
      }
    }

    private def genApplyTypeApply(app: Apply): Val = {
      val Apply(tApply @ TypeApply(fun, targs), argsp) = app: @unchecked
      val Select(receiverp, _) = desugarTree(fun): @unchecked
      given nir.Position = fun.span

      val funSym = fun.symbol
      val value = genExpr(receiverp)
      def boxed = boxValue(receiverp.tpe, value)(using receiverp.span)

      if (funSym == defn.Object_synchronized)
        assert(argsp.size == 1, "synchronized with wrong number of args")
        genSynchronized(ValTree(boxed), argsp.head)
      else genTypeApply(tApply)
    }

    private def genApplyNew(app: Apply): Val = {
      val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = app: @unchecked
      given nir.Position = app.span

      fromType(tpt.tpe) match {
        case st if st.sym.isStruct =>
          genApplyNewStruct(st, args)

        case SimpleType(cls, Seq()) =>
          val ctor = fun.symbol
          assert(
            ctor.isClassConstructor,
            "'new' call to non-constructor: " + ctor.name
          )

          genApplyNew(cls, ctor, args)

        case SimpleType(sym, targs) =>
          unsupported(s"unexpected new: $sym with targs $targs")
      }
    }

    private def genApplyNewStruct(st: SimpleType, argsp: Seq[Tree]): Val = {
      val ty = genType(st)
      val args = genSimpleArgs(argsp)
      var res: Val = Val.Zero(ty)

      for ((arg, argp), idx) <- args.zip(argsp).zipWithIndex
      do
        given nir.Position = argp.span
        res = buf.insert(res, arg, Seq(idx), unwind)
      res
    }

    private def genApplyNew(clssym: Symbol, ctorsym: Symbol, args: List[Tree])(
        using nir.Position
    ): Val = {
      val alloc = buf.classalloc(genTypeName(clssym), unwind)
      val call = genApplyMethod(ctorsym, statically = true, alloc, args)
      alloc
    }

    def genApplyModuleMethod(
        module: Symbol,
        method: Symbol,
        args: Seq[Tree]
    )(using
        nir.Position
    ): Val = {
      val self = genModule(module)
      genApplyMethod(method, statically = true, self, args)
    }

    def genApplyMethod(
        sym: Symbol,
        statically: Boolean,
        selfp: Tree,
        argsp: Seq[Tree]
    )(using nir.Position): Val = {
      if (sym.isExtern && sym.is(Accessor)) genApplyExternAccessor(sym, argsp)
      else if (sym.isStaticInNIR && !sym.isExtern)
        genApplyStaticMethod(sym, selfp.symbol, argsp)
      else
        val self = genExpr(selfp)
        genApplyMethod(sym, statically, self, argsp)
    }

    private def genApplyMethod(
        sym: Symbol,
        statically: Boolean,
        self: Val,
        argsp: Seq[Tree]
    )(using nir.Position): Val = {
      assert(!sym.isStaticMethod, sym)
      val owner = sym.owner.asClass
      val name = genMethodName(sym)
      val isExtern = sym.isExtern

      val origSig = genMethodSig(sym)
      val sig =
        if isExtern then genExternMethodSig(sym)
        else origSig
      val args = genMethodArgs(sym, argsp)

      val isStaticCall = statically || owner.isStruct || isExtern
      val method =
        if (isStaticCall) Val.Global(name, nir.Type.Ptr)
        else
          val Global.Member(_, sig) = name: @unchecked
          buf.method(self, sig, unwind)
      val values =
        if isExtern then args
        else self +: args

      val res = buf.call(sig, method, values, unwind)

      if !isExtern then res
      else {
        val Type.Function(_, retty) = origSig
        fromExtern(retty, res)
      }
    }

    def genApplyStaticMethod(
        sym: Symbol,
        receiver: Symbol,
        argsp: Seq[Tree]
    )(using nir.Position): Val = {
      require(!sym.isExtern, sym)

      val sig = genMethodSig(sym)
      val args = genMethodArgs(sym, argsp)
      val methodName = genStaticMemberName(sym, receiver)
      val method = Val.Global(methodName, nir.Type.Ptr)
      buf.call(sig, method, args, unwind)
    }

    private def genApplyExternAccessor(sym: Symbol, argsp: Seq[Tree])(using
        nir.Position
    ): Val = {
      argsp match {
        case Seq() =>
          val ty = genMethodSig(sym).ret
          val externTy = genExternMethodSig(sym).ret
          genLoadExtern(ty, externTy, sym)
        case Seq(valuep) =>
          val externTy = genExternType(valuep.tpe)
          genStoreExtern(externTy, sym, genExpr(valuep))
      }
    }

    // Utils
    private def boxValue(st: SimpleType, value: Val)(using
        nir.Position
    ): Val = {
      if (st.sym.isUnsignedType)
        genApplyModuleMethod(
          defnNir.RuntimeBoxesModule,
          defnNir.BoxUnsignedMethod(st.sym),
          Seq(ValTree(value))
        )
      else if (genPrimCode(st) == 'O') value
      else genApplyBox(st, ValTree(value))
    }

    private def unboxValue(st: SimpleType, partial: Boolean, value: Val)(using
        nir.Position
    ): Val = {
      if (st.sym.isUnsignedType) {
        // Results of asInstanceOfs are partially unboxed, meaning
        // that non-standard value types remain to be boxed.
        if (partial) value
        else
          genApplyModuleMethod(
            defnNir.RuntimeBoxesModule,
            defnNir.UnboxUnsignedMethod(st.sym),
            Seq(ValTree(value))
          )
      } else if (genPrimCode(st) == 'O') value
      else genApplyUnbox(st, ValTree(value))
    }

    private def genSimpleOp(app: Apply, args: List[Tree], code: Int): Val = {
      given nir.Position = app.span
      val retty = genType(app.tpe)

      args match {
        case List(right)       => genUnaryOp(code, right, retty)
        case List(left, right) => genBinaryOp(code, left, right, retty)
        case _ =>
          report.error(
            s"Too many arguments for primitive function: $app",
            app.sourcePos
          )
          Val.Null
      }
    }

    private def negateBool(value: nir.Val)(using nir.Position): Val =
      buf.bin(Bin.Xor, Type.Bool, Val.True, value, unwind)

    private def genUnaryOp(code: Int, rightp: Tree, opty: nir.Type): Val = {
      given nir.Position = rightp.span
      val right = genExpr(rightp)
      val coerced = genCoercion(right, right.ty, opty)
      val tpe = coerced.ty

      def numOfType(num: Int, ty: nir.Type): Val = ty match {
        case Type.Byte              => Val.Byte(num.toByte)
        case Type.Short | Type.Char => Val.Short(num.toShort)
        case Type.Int               => Val.Int(num)
        case Type.Long              => Val.Long(num.toLong)
        case Type.Float             => Val.Float(num.toFloat)
        case Type.Double            => Val.Double(num.toDouble)
        case _ => unsupported(s"num = $num, ty = ${ty.show}")
      }

      (opty, code) match {
        case (_: Type.I | _: Type.F, POS) => coerced
        case (_: Type.I, NOT) =>
          buf.bin(Bin.Xor, tpe, numOfType(-1, tpe), coerced, unwind)
        case (_: Type.F, NEG) =>
          buf.bin(Bin.Fmul, tpe, numOfType(-1, tpe), coerced, unwind)
        case (_: Type.I, NEG) =>
          buf.bin(Bin.Isub, tpe, numOfType(0, tpe), coerced, unwind)
        case (Type.Bool, ZNOT) => negateBool(coerced)
        case _ =>
          report.error(s"Unknown unary operation code: $code", rightp.sourcePos)
          Val.Null
      }
    }

    private def genBinaryOp(
        code: Int,
        left: Tree,
        right: Tree,
        retty: nir.Type
    )(using nir.Position): Val = {
      val lty = genType(left.tpe)
      val rty = genType(right.tpe)
      val opty = {
        if (isShiftOp(code))
          if (lty == nir.Type.Long) nir.Type.Long
          else nir.Type.Int
        else
          binaryOperationType(lty, rty)
      }
      def genOp(op: (nir.Type, Val, Val) => Op): Val = {
        val leftcoerced = genCoercion(genExpr(left), lty, opty)(using left.span)
        val rightcoerced =
          genCoercion(genExpr(right), rty, opty)(using right.span)
        buf.let(op(opty, leftcoerced, rightcoerced), unwind)(using left.span)
      }

      val binres = opty match {
        case _: Type.F =>
          code match {
            case ADD => genOp(Op.Bin(Bin.Fadd, _, _, _))
            case SUB => genOp(Op.Bin(Bin.Fsub, _, _, _))
            case MUL => genOp(Op.Bin(Bin.Fmul, _, _, _))
            case DIV => genOp(Op.Bin(Bin.Fdiv, _, _, _))
            case MOD => genOp(Op.Bin(Bin.Frem, _, _, _))

            case EQ => genOp(Op.Comp(Comp.Feq, _, _, _))
            case NE => genOp(Op.Comp(Comp.Fne, _, _, _))
            case LT => genOp(Op.Comp(Comp.Flt, _, _, _))
            case LE => genOp(Op.Comp(Comp.Fle, _, _, _))
            case GT => genOp(Op.Comp(Comp.Fgt, _, _, _))
            case GE => genOp(Op.Comp(Comp.Fge, _, _, _))

            case _ =>
              report.error(
                s"Unknown floating point type binary operation code: $code",
                right.sourcePos
              )
              Val.Null
          }
        case Type.Bool | _: Type.I =>
          code match {
            case ADD => genOp(Op.Bin(Bin.Iadd, _, _, _))
            case SUB => genOp(Op.Bin(Bin.Isub, _, _, _))
            case MUL => genOp(Op.Bin(Bin.Imul, _, _, _))
            case DIV => genOp(Op.Bin(Bin.Sdiv, _, _, _))
            case MOD => genOp(Op.Bin(Bin.Srem, _, _, _))

            case OR  => genOp(Op.Bin(Bin.Or, _, _, _))
            case XOR => genOp(Op.Bin(Bin.Xor, _, _, _))
            case AND => genOp(Op.Bin(Bin.And, _, _, _))
            case LSL => genOp(Op.Bin(Bin.Shl, _, _, _))
            case LSR => genOp(Op.Bin(Bin.Lshr, _, _, _))
            case ASR => genOp(Op.Bin(Bin.Ashr, _, _, _))

            case EQ => genOp(Op.Comp(Comp.Ieq, _, _, _))
            case NE => genOp(Op.Comp(Comp.Ine, _, _, _))
            case LT => genOp(Op.Comp(Comp.Slt, _, _, _))
            case LE => genOp(Op.Comp(Comp.Sle, _, _, _))
            case GT => genOp(Op.Comp(Comp.Sgt, _, _, _))
            case GE => genOp(Op.Comp(Comp.Sge, _, _, _))

            case ZOR  => genIf(retty, left, Literal(Constant(true)), right)
            case ZAND => genIf(retty, left, right, Literal(Constant(false)))
            case _ =>
              report.error(
                s"Unknown integer type binary operation code: $code",
                right.sourcePos
              )
              Val.Null
          }
        case _: Type.RefKind =>
          def genEquals(ref: Boolean, negated: Boolean) = (left, right) match {
            // If null is present on either side, we must always
            // generate reference equality, regardless of where it
            // was called with == or eq. This shortcut is not optional.
            case (Literal(Constant(null)), _) | (_, Literal(Constant(null))) =>
              genClassEquality(left, right, ref = true, negated = negated)
            case _ =>
              genClassEquality(left, right, ref = ref, negated = negated)
          }

          code match {
            case EQ => genEquals(ref = false, negated = false)
            case NE => genEquals(ref = false, negated = true)
            case ID => genEquals(ref = true, negated = false)
            case NI => genEquals(ref = true, negated = true)
            case _ =>
              report.error(
                s"Unknown reference type binary operation code: $code",
                right.sourcePos
              )
              Val.Null
          }
        case Type.Ptr =>
          code match {
            case EQ | ID => genOp(Op.Comp(Comp.Ieq, _, _, _))
            case NE | NI => genOp(Op.Comp(Comp.Ine, _, _, _))
          }
        case ty =>
          report.error(
            s"Unknown binary operation type: $ty",
            right.sourcePos
          )
          Val.Null
      }

      genCoercion(binres, binres.ty, retty)(using right.span)
    }

    private def binaryOperationType(lty: nir.Type, rty: nir.Type) =
      (lty, rty) match {
        // Bug compatibility with scala/bug/issues/11253
        case (Type.Long, Type.Float)             => Type.Double
        case (nir.Type.Ptr, _: nir.Type.RefKind) => lty
        case (_: nir.Type.RefKind, nir.Type.Ptr) => rty
        case (nir.Type.Bool, nir.Type.Bool)      => nir.Type.Bool
        case (nir.Type.I(lwidth, _), nir.Type.I(rwidth, _))
            if lwidth < 32 && rwidth < 32 =>
          nir.Type.Int
        case (nir.Type.I(lwidth, _), nir.Type.I(rwidth, _)) =>
          if (lwidth >= rwidth) lty
          else rty
        case (nir.Type.I(_, _), nir.Type.F(_)) => rty
        case (nir.Type.F(_), nir.Type.I(_, _)) => lty
        case (nir.Type.F(lwidth), nir.Type.F(rwidth)) =>
          if (lwidth >= rwidth) lty
          else rty
        case (_: nir.Type.RefKind, _: nir.Type.RefKind) => Rt.Object
        case (ty1, ty2) if ty1 == ty2                   => ty1
        case (Type.Nothing, ty)                         => ty
        case (ty, Type.Nothing)                         => ty
        case _ =>
          report.error(s"can't perform binary operation between $lty and $rty")
          Type.Nothing
      }

    private def genClassEquality(
        leftp: Tree,
        rightp: Tree,
        ref: Boolean,
        negated: Boolean
    ): Val = {
      given nir.Position = leftp.span
      val left = genExpr(leftp)

      if (ref) {
        val right = genExpr(rightp)
        val comp = if (negated) Comp.Ine else Comp.Ieq
        buf.comp(comp, Rt.Object, left, right, unwind)
      } else {
        val thenn, elsen, mergen = fresh()
        val mergev = Val.Local(fresh(), nir.Type.Bool)

        val isnull = buf.comp(Comp.Ieq, Rt.Object, left, Val.Null, unwind)
        buf.branch(isnull, Next(thenn), Next(elsen))
        locally {
          buf.label(thenn)
          val right = genExpr(rightp)
          val thenv = buf.comp(Comp.Ieq, Rt.Object, right, Val.Null, unwind)
          buf.jump(mergen, Seq(thenv))
        }
        locally {
          buf.label(elsen)
          val elsev = genApplyMethod(
            defnNir.NObject_equals,
            statically = false,
            left,
            Seq(rightp)
          )
          buf.jump(mergen, Seq(elsev))
        }
        buf.label(mergen, Seq(mergev))
        if (negated) negateBool(mergev)
        else mergev
      }
    }

    def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] =
      if sym.isExtern
      then genExternMethodArgs(sym, argsp)
      else genSimpleArgs(argsp)

    private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = argsp.map(genExpr)

    private def genExternMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = {
      val res = Seq.newBuilder[Val]
      val Type.Function(argTypes, _) = genExternMethodSig(sym)
      val paramTypes = sym.paramInfo.paramInfoss.flatten
      assert(
        argTypes.size == argsp.size && argTypes.size == paramTypes.size,
        "Different number of arguments passed to method signature and apply method"
      )

      def genArg(
          argp: Tree,
          paramTpe: Types.Type,
          isVarArg: Boolean = false
      ): nir.Val = {
        given nir.Position = argp.span
        given ExprBuffer = buf
        val externType = genExternType(paramTpe.finalResultType)
        val rawValue = genExpr(argp)
        val maybeUnboxed =
          if (isVarArg) ensureUnboxed(rawValue, paramTpe.finalResultType)
          else rawValue
        val value = (maybeUnboxed, Type.box.get(externType)) match {
          case (value @ Val.Null, Some(unboxedType)) =>
            externType match {
              case Type.Ptr | _: Type.RefKind => value
              case _ =>
                report.warning(
                  s"Passing null as argument of type ${paramTpe.show} to the extern method is unsafe. " +
                    s"The argument would be unboxed to primitive value of type $externType.",
                  argp.srcPos
                )
                Val.Zero(unboxedType)
            }
          case (value, _) => value
        }
        toExtern(externType, value)
      }

      for ((argp, sigType), paramTpe) <- argsp zip argTypes zip paramTypes
      do
        sigType match {
          case nir.Type.Vararg =>
            argp match {
              case Apply(_, List(seqLiteral: JavaSeqLiteral)) =>
                for tree <- seqLiteral.elems
                do
                  given nir.Position = tree.span
                  val tpe = tree
                    .getAttachment(NirDefinitions.NonErasedType)
                    .getOrElse(tree.tpe)
                  val arg = genArg(tree, tpe, isVarArg = true)
                  def isUnsigned = Type.isUnsignedType(genType(tpe))
                  // Decimal varargs needs to be promoted to at least Int, and Float needs to be promoted to Double
                  val promotedArg = arg.ty match {
                    case Type.Float =>
                      this.genCastOp(Type.Float, Type.Double, arg)
                    case Type.I(width, _) if width < Type.Int.width =>
                      val conv =
                        if (isUnsigned) nir.Conv.Zext
                        else nir.Conv.Sext
                      buf.conv(conv, Type.Int, arg, unwind)
                    case _ => arg
                  }
                  res += promotedArg
              case _ =>
                report.error(
                  "Unable to extract vararg arguments, varargs to extern methods must be passed directly to the applied function",
                  argp.srcPos
                )
            }
          case _ => res += genArg(argp, paramTpe)
        }
      res.result()
    }

    private def genArrayOp(app: Apply, code: Int): Val = {
      import NirPrimitives._
      import dotty.tools.backend.ScalaPrimitivesOps._

      val Apply(Select(arrayp, _), argsp) = app: @unchecked
      val Type.Array(elemty, _) = genType(arrayp.tpe): @unchecked
      given nir.Position = app.span

      def elemcode = genArrayCode(arrayp.tpe)
      val array = genExpr(arrayp)

      if (code == ARRAY_CLONE)
        val method = defnNir.RuntimeArray_clone(elemcode)
        genApplyMethod(method, statically = true, array, argsp)
      else if (isArrayGet(code))
        val idx = genExpr(argsp(0))
        buf.arrayload(elemty, array, idx, unwind)
      else if (isArraySet(code))
        val idx = genExpr(argsp(0))
        val value = genExpr(argsp(1))
        buf.arraystore(elemty, array, idx, value, unwind)
      else buf.arraylength(array, unwind)
    }

    private def genHashCode(argp: Tree)(using nir.Position): Val = {
      val arg = boxValue(argp.tpe, genExpr(argp))
      val isnull =
        buf.comp(Comp.Ieq, Rt.Object, arg, Val.Null, unwind)(using
          argp.span: nir.Position
        )
      val cond = ValTree(isnull)
      val thenp = ValTree(Val.Int(0))
      val elsep = ContTree { () =>
        val meth = defnNir.NObject_hashCode
        genApplyMethod(meth, statically = false, arg, Seq.empty)
      }
      genIf(Type.Int, cond, thenp, elsep)
    }

    private def genStringConcat(leftp: Tree, rightp: Tree): Val = {
      def stringify(sym: Symbol, value: Val)(using nir.Position): Val = {
        val cond = ContTree { () =>
          buf.comp(Comp.Ieq, Rt.Object, value, Val.Null, unwind)
        }
        val thenp = ContTree { () => Val.String("null") }
        val elsep = ContTree { () =>
          if (sym == defn.StringClass) value
          else {
            val meth = defn.Any_toString
            genApplyMethod(meth, statically = false, value, Seq.empty)
          }
        }
        genIf(Rt.String, cond, thenp, elsep)
      }

      val left = {
        given nir.Position = leftp.span
        val typesym = leftp.tpe.typeSymbol
        val unboxed = genExpr(leftp)
        val boxed = boxValue(typesym, unboxed)
        stringify(typesym, boxed)
      }

      val right = {
        given nir.Position = rightp.span
        val typesym = rightp.tpe.typeSymbol
        val boxed = genExpr(rightp)
        stringify(typesym, boxed)
      }

      genApplyMethod(
        defn.String_+,
        statically = true,
        left,
        Seq(ValTree(right))
      )(using leftp.span: nir.Position)
    }

    private def genStaticMember(sym: Symbol, receiver: Symbol)(using
        nir.Position
    ): Val = {
      /* Actually, there is no static member in Scala Native. If we come here, that
       * is because we found the symbol in a Java-emitted .class in the
       * classpath. But the corresponding implementation in Scala Native will
       * actually be a val in the companion module.
       */

      if (sym == defn.BoxedUnit_UNIT) Val.Unit
      else if (sym == defn.BoxedUnit_TYPE) Val.Unit
      else genApplyStaticMethod(sym, receiver, Seq.empty)
    }

    private def genSynchronized(receiverp: Tree, bodyp: Tree)(using
        nir.Position
    ): Val = {
      genSynchronized(receiverp)(_.genExpr(bodyp))
    }

    def genSynchronized(
        receiverp: Tree
    )(bodyGen: ExprBuffer => Val)(using nir.Position): Val = {
      val monitor =
        genApplyModuleMethod(
          defnNir.RuntimePackageClass,
          defnNir.RuntimePackage_getMonitor,
          Seq(receiverp)
        )
      val enter = genApplyMethod(
        defnNir.RuntimeMonitor_enter,
        statically = true,
        monitor,
        Seq.empty
      )
      val ret = bodyGen(this)
      val exit = genApplyMethod(
        defnNir.RuntimeMonitor_exit,
        statically = true,
        monitor,
        Seq.empty
      )

      ret
    }

    private def genThrow(tree: Tree, args: List[Tree]): Val = {
      given nir.Position = tree.span
      val exception = args.head
      val res = genExpr(exception)
      buf.raise(res, unwind)
      Val.Unit
    }

    def genCastOp(from: nir.Type, to: nir.Type, value: Val)(using
        nir.Position
    ): Val =
      castConv(from, to).fold(value)(buf.conv(_, to, value, unwind))

    private def genCoercion(app: Apply, receiver: Tree, code: Int): Val = {
      given nir.Position = app.span
      val rec = genExpr(receiver)
      val (fromty, toty) = coercionTypes(code)

      genCoercion(rec, fromty, toty)
    }

    private def genCoercion(value: Val, fromty: nir.Type, toty: nir.Type)(using
        nir.Position
    ): Val = {
      if (fromty == toty) value
      else if (fromty == nir.Type.Nothing || toty == nir.Type.Nothing) value
      else {
        val conv = (fromty, toty) match {
          case (nir.Type.Ptr, _: nir.Type.RefKind) => Conv.Bitcast
          case (_: nir.Type.RefKind, nir.Type.Ptr) => Conv.Bitcast
          case (nir.Type.I(fromw, froms), nir.Type.I(tow, tos)) =>
            if (fromw < tow)
              if (froms) Conv.Sext
              else Conv.Zext
            else if (fromw > tow) Conv.Trunc
            else Conv.Bitcast
          case (nir.Type.I(_, true), _: nir.Type.F)  => Conv.Sitofp
          case (nir.Type.I(_, false), _: nir.Type.F) => Conv.Uitofp
          case (_: nir.Type.F, nir.Type.I(iwidth, true)) =>
            if (iwidth < 32) {
              val ivalue = genCoercion(value, fromty, Type.Int)
              return genCoercion(ivalue, Type.Int, toty)
            }
            Conv.Fptosi
          case (_: nir.Type.F, nir.Type.I(iwidth, false)) =>
            if (iwidth < 32) {
              val ivalue = genCoercion(value, fromty, Type.Int)
              return genCoercion(ivalue, Type.Int, toty)
            }
            Conv.Fptoui
          case (nir.Type.Double, nir.Type.Float) => Conv.Fptrunc
          case (nir.Type.Float, nir.Type.Double) => Conv.Fpext
          case _ =>
            report.error(
              s"Unsupported coercion types: from $fromty to $toty"
            )
            Conv.Bitcast
        }
        buf.conv(conv, toty, value, unwind)
      }
    }

    private def coercionTypes(code: Int): (nir.Type, nir.Type) = {
      code match {
        case B2B => (nir.Type.Byte, nir.Type.Byte)
        case B2S => (nir.Type.Byte, nir.Type.Short)
        case B2C => (nir.Type.Byte, nir.Type.Char)
        case B2I => (nir.Type.Byte, nir.Type.Int)
        case B2L => (nir.Type.Byte, nir.Type.Long)
        case B2F => (nir.Type.Byte, nir.Type.Float)
        case B2D => (nir.Type.Byte, nir.Type.Double)

        case S2B => (nir.Type.Short, nir.Type.Byte)
        case S2S => (nir.Type.Short, nir.Type.Short)
        case S2C => (nir.Type.Short, nir.Type.Char)
        case S2I => (nir.Type.Short, nir.Type.Int)
        case S2L => (nir.Type.Short, nir.Type.Long)
        case S2F => (nir.Type.Short, nir.Type.Float)
        case S2D => (nir.Type.Short, nir.Type.Double)

        case C2B => (nir.Type.Char, nir.Type.Byte)
        case C2S => (nir.Type.Char, nir.Type.Short)
        case C2C => (nir.Type.Char, nir.Type.Char)
        case C2I => (nir.Type.Char, nir.Type.Int)
        case C2L => (nir.Type.Char, nir.Type.Long)
        case C2F => (nir.Type.Char, nir.Type.Float)
        case C2D => (nir.Type.Char, nir.Type.Double)

        case I2B => (nir.Type.Int, nir.Type.Byte)
        case I2S => (nir.Type.Int, nir.Type.Short)
        case I2C => (nir.Type.Int, nir.Type.Char)
        case I2I => (nir.Type.Int, nir.Type.Int)
        case I2L => (nir.Type.Int, nir.Type.Long)
        case I2F => (nir.Type.Int, nir.Type.Float)
        case I2D => (nir.Type.Int, nir.Type.Double)

        case L2B => (nir.Type.Long, nir.Type.Byte)
        case L2S => (nir.Type.Long, nir.Type.Short)
        case L2C => (nir.Type.Long, nir.Type.Char)
        case L2I => (nir.Type.Long, nir.Type.Int)
        case L2L => (nir.Type.Long, nir.Type.Long)
        case L2F => (nir.Type.Long, nir.Type.Float)
        case L2D => (nir.Type.Long, nir.Type.Double)

        case F2B => (nir.Type.Float, nir.Type.Byte)
        case F2S => (nir.Type.Float, nir.Type.Short)
        case F2C => (nir.Type.Float, nir.Type.Char)
        case F2I => (nir.Type.Float, nir.Type.Int)
        case F2L => (nir.Type.Float, nir.Type.Long)
        case F2F => (nir.Type.Float, nir.Type.Float)
        case F2D => (nir.Type.Float, nir.Type.Double)

        case D2B => (nir.Type.Double, nir.Type.Byte)
        case D2S => (nir.Type.Double, nir.Type.Short)
        case D2C => (nir.Type.Double, nir.Type.Char)
        case D2I => (nir.Type.Double, nir.Type.Int)
        case D2L => (nir.Type.Double, nir.Type.Long)
        case D2F => (nir.Type.Double, nir.Type.Float)
        case D2D => (nir.Type.Double, nir.Type.Double)
      }
    }

    private def castConv(fromty: nir.Type, toty: nir.Type): Option[nir.Conv] =
      (fromty, toty) match {
        case (_: Type.I, Type.Ptr)                   => Some(nir.Conv.Inttoptr)
        case (Type.Ptr, _: Type.I)                   => Some(nir.Conv.Ptrtoint)
        case (_: Type.RefKind, Type.Ptr)             => Some(nir.Conv.Bitcast)
        case (Type.Ptr, _: Type.RefKind)             => Some(nir.Conv.Bitcast)
        case (_: Type.RefKind, _: Type.RefKind)      => Some(nir.Conv.Bitcast)
        case (_: Type.RefKind, _: Type.I)            => Some(nir.Conv.Ptrtoint)
        case (_: Type.I, _: Type.RefKind)            => Some(nir.Conv.Inttoptr)
        case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast)
        case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast)
        case _ if fromty == toty                     => None
        case (Type.Float, Type.Double)               => Some(nir.Conv.Fpext)
        case (Type.Double, Type.Float)               => Some(nir.Conv.Fptrunc)
        case _ => unsupported(s"cast from $fromty to $toty")
      }

    /** Boxes a value of the given type before `elimErasedValueType`.
     *
     *  This should be used when sending values to a LLVM context, which is
     *  erased/boxed at the NIR level, although it is not erased at the
     *  dotty/JVM level.
     *
     *  @param value
     *    Value to be boxed if needed.
     *  @param tpeEnteringElimErasedValueType
     *    The type of `value` as it was entering the `elimErasedValueType`
     *    phase.
     */
    private def ensureBoxed(
        value: Val,
        tpeEnteringPosterasure: core.Types.Type
    )(using buf: ExprBuffer, pos: nir.Position): Val = {
      tpeEnteringPosterasure match {
        case tpe if tpe.isPrimitiveValueType =>
          buf.boxValue(tpe, value)

        case ErasedValueType(valueClass, _) =>
          val boxedClass = valueClass.typeSymbol.asClass
          val ctorName = genMethodName(boxedClass.primaryConstructor)
          val ctorSig = genMethodSig(boxedClass.primaryConstructor)

          val alloc = buf.classalloc(genTypeName(boxedClass), unwind)
          val ctor = buf.method(
            alloc,
            ctorName.asInstanceOf[nir.Global.Member].sig,
            unwind
          )
          buf.call(ctorSig, ctor, Seq(alloc, value), unwind)

          alloc

        case _ =>
          value
      }
    }

    /** Unboxes a value typed as Any to the given type before
     *  `elimErasedValueType`.
     *
     *  This should be used when receiving values from a LLVM context, which is
     *  erased/boxed at the NIR level, although it is not erased at the
     *  dotty/JVM level.
     *
     *  @param value
     *    Tree to be extracted.
     *  @param tpeEnteringElimErasedValueType
     *    The type of `value` as it was entering the `elimErasedValueType`
     *    phase.
     */
    private def ensureUnboxed(
        value: Val,
        tpeEnteringPosterasure: core.Types.Type
    )(using
        buf: ExprBuffer,
        pos: nir.Position
    ): Val = {
      tpeEnteringPosterasure match {
        case tpe if tpe.isPrimitiveValueType =>
          val targetTpe = genType(tpeEnteringPosterasure)
          if (targetTpe == value.ty) value
          else buf.unbox(genBoxType(tpe), value, Next.None)

        case ErasedValueType(valueClass, _) =>
          val boxedClass = valueClass.typeSymbol.asClass
          val unboxMethod = ValueClasses.valueClassUnbox(boxedClass)
          val castedValue = buf.genCastOp(value.ty, genType(valueClass), value)
          buf.genApplyMethod(
            sym = unboxMethod,
            statically = false,
            self = castedValue,
            argsp = Nil
          )

        case tpe =>
          val unboxed = buf.unboxValue(tpe, partial = true, value)
          if (unboxed == value) // no need to or cannot unbox, we should cast
            buf.genCastOp(genType(tpeEnteringPosterasure), genType(tpe), value)
          else unboxed
      }
    }

    // Native specifc features
    private def genRawPtrOp(app: Apply, code: Int): Val = {
      if (NirPrimitives.isRawPtrLoadOp(code)) genRawPtrLoadOp(app, code)
      else if (NirPrimitives.isRawPtrStoreOp(code)) genRawPtrStoreOp(app, code)
      else if (code == NirPrimitives.ELEM_RAW_PTR) genRawPtrElemOp(app, code)
      else {
        report.error(s"Unknown pointer operation #$code : $app", app.sourcePos)
        Val.Null
      }
    }

    private def genRawPtrLoadOp(app: Apply, code: Int): Val = {
      import NirPrimitives._
      given nir.Position = app.span

      val Apply(_, Seq(ptrp)) = app
      val ptr = genExpr(ptrp)
      val ty = code match {
        case LOAD_BOOL    => nir.Type.Bool
        case LOAD_CHAR    => nir.Type.Char
        case LOAD_BYTE    => nir.Type.Byte
        case LOAD_SHORT   => nir.Type.Short
        case LOAD_INT     => nir.Type.Int
        case LOAD_LONG    => nir.Type.Long
        case LOAD_FLOAT   => nir.Type.Float
        case LOAD_DOUBLE  => nir.Type.Double
        case LOAD_RAW_PTR => nir.Type.Ptr
        case LOAD_OBJECT  => Rt.Object
      }
      buf.load(ty, ptr, unwind)
    }

    private def genRawPtrStoreOp(app: Apply, code: Int): Val = {
      import NirPrimitives._
      given nir.Position = app.span
      val Apply(_, Seq(ptrp, valuep)) = app

      val ptr = genExpr(ptrp)
      val value = genExpr(valuep)
      val ty = code match {
        case STORE_BOOL    => nir.Type.Bool
        case STORE_CHAR    => nir.Type.Char
        case STORE_BYTE    => nir.Type.Byte
        case STORE_SHORT   => nir.Type.Short
        case STORE_INT     => nir.Type.Int
        case STORE_LONG    => nir.Type.Long
        case STORE_FLOAT   => nir.Type.Float
        case STORE_DOUBLE  => nir.Type.Double
        case STORE_RAW_PTR => nir.Type.Ptr
        case STORE_OBJECT  => Rt.Object
      }
      buf.store(ty, ptr, value, unwind)
    }

    private def genRawPtrElemOp(app: Apply, code: Int): Val = {
      given nir.Position = app.span
      val Apply(_, Seq(ptrp, offsetp)) = app

      val ptr = genExpr(ptrp)
      val offset = genExpr(offsetp)
      buf.elem(Type.Byte, ptr, Seq(offset), unwind)
    }

    private def genRawCastOp(app: Apply, code: Int): Val = {
      given nir.Position = app.span
      val Apply(_, Seq(argp)) = app

      val fromty = genType(argp.tpe)
      val toty = genType(app.tpe)
      val value = genExpr(argp)

      genCastOp(fromty, toty, value)
    }

    private def genUnsignedOp(app: Tree, code: Int): Val = {
      given nir.Position = app.span
      import NirPrimitives._
      def castUnsignedInteger = code >= BYTE_TO_UINT && code <= INT_TO_ULONG
      def castUnsignedToFloat = code >= UINT_TO_FLOAT && code <= ULONG_TO_DOUBLE
      app match {
        case Apply(_, Seq(argp)) if castUnsignedInteger =>
          val ty = genType(app.tpe)
          val arg = genExpr(argp)

          buf.conv(Conv.Zext, ty, arg, unwind)

        case Apply(_, Seq(argp)) if castUnsignedToFloat =>
          val ty = genType(app.tpe)
          val arg = genExpr(argp)

          buf.conv(Conv.Uitofp, ty, arg, unwind)

        case Apply(_, Seq(leftp, rightp)) =>
          val bin = code match {
            case DIV_UINT | DIV_ULONG => nir.Bin.Udiv
            case REM_UINT | REM_ULONG => nir.Bin.Urem
          }
          val ty = genType(leftp.tpe)
          val left = genExpr(leftp)
          val right = genExpr(rightp)

          buf.bin(bin, ty, left, right, unwind)
      }
    }

    private def getLinktimeCondition(condp: Tree): Option[LinktimeCondition] = {
      import nir.LinktimeCondition._
      def genComparsion(name: Name, value: Val): Comp = {
        def intOrFloatComparison(onInt: Comp, onFloat: Comp) = value.ty match {
          case _: Type.F => onFloat
          case _         => onInt
        }

        import Comp._
        name match {
          case nme.EQ => intOrFloatComparison(Ieq, Feq)
          case nme.NE => intOrFloatComparison(Ine, Fne)
          case nme.GT => intOrFloatComparison(Sgt, Fgt)
          case nme.GE => intOrFloatComparison(Sge, Fge)
          case nme.LT => intOrFloatComparison(Slt, Flt)
          case nme.LE => intOrFloatComparison(Sle, Fle)
          case nme =>
            report.error(s"Unsupported condition '$nme'", condp.sourcePos)
            Comp.Ine
        }
      }

      condp match {
        // if(bool) (...)
        case Apply(LinktimeProperty(name, _, position), Nil) =>
          Some {
            SimpleCondition(
              propertyName = name,
              comparison = Comp.Ieq,
              value = Val.True
            )(using position)
          }

        // if(!bool) (...)
        case Apply(
              Select(
                Apply(LinktimeProperty(name, _, position), Nil),
                nme.UNARY_!
              ),
              Nil
            ) =>
          Some {
            SimpleCondition(
              propertyName = name,
              comparison = Comp.Ieq,
              value = Val.False
            )(using position)
          }

        // if(property  x) (...)
        case Apply(
              Select(LinktimeProperty(name, _, position), comp),
              List(arg @ Literal(Constant(_)))
            ) =>
          Some {
            val argValue = genLiteralValue(arg)
            SimpleCondition(
              propertyName = name,
              comparison = genComparsion(comp, argValue),
              value = argValue
            )(using position)
          }

        // special case for Scala 3 - it wraps != into !(_ == _)
        // if(property == x) (...)
        case Apply(
              Select(
                Apply(
                  Select(LinktimeProperty(name, _, position), nme.EQ),
                  List(arg @ Literal(Constant(_)))
                ),
                nme.UNARY_!
              ),
              Nil
            ) =>
          Some {
            val argValue = genLiteralValue(arg)
            SimpleCondition(
              propertyName = name,
              comparison = genComparsion(nme.NE, argValue),
              value = argValue
            )(using position)
          }

        // if(cond1 {&&,||} cond2) (...)
        case Apply(Select(cond1, op), List(cond2)) =>
          (getLinktimeCondition(cond1), getLinktimeCondition(cond2)) match {
            case (Some(c1), Some(c2)) =>
              val bin = op match {
                case nme.ZAND => Bin.And
                case nme.ZOR  => Bin.Or
              }
              given nir.Position = condp.span
              Some(ComplexCondition(bin, c1, c2))
            case (None, None) => None
            case _ =>
              report.error(
                "Mixing link-time and runtime conditions is not allowed",
                condp.sourcePos
              )
              None
          }

        case _ => None
      }
    }

    private def genStackalloc(app: Apply): Val = {
      val Apply(_, Seq(sizep)) = app

      val size = genExpr(sizep)
      val unboxed = buf.unbox(size.ty, size, unwind)(using sizep.span)

      buf.stackalloc(nir.Type.Byte, unboxed, unwind)(using app.span)
    }

    def genCQuoteOp(app: Apply): Val = {
      app match {
        // case q"""
        //   scala.scalanative.unsafe.`package`.CQuote(
        //     new StringContext(scala.this.Predef.wrapRefArray(
        //       Array[String]{${str: String}}.$asInstanceOf[Array[Object]]()
        //     ))
        //   ).c()
        case Apply(
              Select(
                Apply(
                  _, // Ident(CQuote),
                  List(
                    Apply(
                      _,
                      List(Apply(_, List(javaSeqLiteral: JavaSeqLiteral)))
                    )
                  )
                ),
                _
              ),
              _
            ) =>
          given nir.Position = app.span
          val List(Literal(Constant(str: String))) =
            javaSeqLiteral.elems: @unchecked
          val chars = Val.Chars(StringUtils.processEscapes(str).toIndexedSeq)
          val const = Val.Const(chars)
          buf.box(nir.Rt.BoxedPtr, const, unwind)

        case _ =>
          report.error("Failed to interpret CQuote", app.sourcePos)
          Val.Null
      }
    }

    def genClassFieldRawPtr(app: Apply): Val = {
      given nir.Position = app.span
      val Apply(_, List(target, fieldName: Literal)) = app: @unchecked
      val fieldNameId = fieldName.const.stringValue
      val classInfo = target.tpe.finalResultType
      val classInfoSym = classInfo.typeSymbol.asClass
      def matchesName(f: SingleDenotation) =
        f.name.mangled == termName(fieldNameId).mangled
      def isImmutableField(f: SymDenotation) = {
        // If `val` was defined in trait it would be internally mutable, but with stable accessors
        !f.is(Mutable) || classInfoSym.parentSyms.exists(s =>
          s.asClass.info.decls.exists { f =>
            matchesName(f) && f.asSymDenotation
              .isAllOf(Method | Accessor, butNot = Mutable)
          }
        )
      }

      val allFields =
        classInfoSym.info.fields ++ classInfoSym.info.parents.flatMap(_.fields)
      allFields
        .collectFirst {
          case f if matchesName(f) =>
            // Don't allow to get pointer to immutable field, as it might allow for mutation
            if (isImmutableField(f.asSymDenotation)) {
              val owner = f.asSymDenotation.owner
              report.error(
                s"Resolving pointer of immutable field ${fieldNameId} in ${owner.show} is not allowed"
              )
            }
            buf.field(genExpr(target), genFieldName(f.symbol), unwind)
        }
        .getOrElse {
          report.error(
            s"${classInfoSym.show} does not contain field ${fieldNameId}",
            app.sourcePos
          )
          Val.Int(-1)
        }
    }

    def genLoadExtern(ty: nir.Type, externTy: nir.Type, sym: Symbol)(using
        nir.Position
    ): Val = {
      assert(sym.isExtern, "loadExtern was not extern")

      val name = Val.Global(genName(sym), Type.Ptr)

      fromExtern(ty, buf.load(externTy, name, unwind))
    }

    def genStoreExtern(externTy: nir.Type, sym: Symbol, value: Val)(using
        nir.Position
    ): Val = {
      assert(sym.isExtern, "storeExtern was not extern")
      val name = Val.Global(genName(sym), Type.Ptr)
      val externValue = toExtern(externTy, value)

      buf.store(externTy, name, externValue, unwind)
    }

    def toExtern(expectedTy: nir.Type, value: Val)(using nir.Position): Val =
      (expectedTy, value.ty) match {
        case (Type.Unit, _) => Val.Unit
        case (_, refty: Type.Ref)
            if Type.boxClasses.contains(refty.name)
              && Type.unbox(Type.Ref(refty.name)) == expectedTy =>
          buf.unbox(Type.Ref(refty.name), value, unwind)
        case _ =>
          value
      }

    def fromExtern(expectedTy: nir.Type, value: Val)(using nir.Position): Val =
      (expectedTy, value.ty) match {
        case (refty: nir.Type.Ref, ty)
            if Type.boxClasses.contains(refty.name)
              && Type.unbox(Type.Ref(refty.name)) == ty =>
          buf.box(Type.Ref(refty.name), value, unwind)
        case _ =>
          value
      }

    /** Generates direct call to function ptr with optional unboxing arguments
     *  and boxing result Apply.args can contain different number of arguments
     *  depending on usage, however they are passed in constant order:
     *    - 0..N args
     *    - 0..N+1 type evidences of args (scalanative.Tag)
     *    - return type evidence
     */
    private def genCFuncPtrApply(app: Apply): Val = {
      given nir.Position = app.span
      val Apply(appRec @ Select(receiverp, _), aargs) = app: @unchecked

      val argsp = if (aargs.size > 2) aargs.take(aargs.length / 2) else Nil
      val evidences = aargs.drop(aargs.length / 2)

      val self = genExpr(receiverp)

      val retTypeEv = evidences.last
      val unwrappedRetType = unwrapTag(retTypeEv)
      val retType = genType(unwrappedRetType)
      val unboxedRetType = Type.unbox.getOrElse(retType, retType)

      val args = argsp
        .zip(evidences)
        .map {
          case (Apply(Select(_, nme.box), List(value)), _) =>
            genExpr(value)
          case (arg, evidence) =>
            given nir.Position = arg.span
            val tag = unwrapTag(evidence)
            val tpe = genType(tag)
            val obj = genExpr(arg)

            /* buf.unboxValue does not handle Ref( Ptr | CArray | ... ) unboxing
             * That's why we're doing it directly */
            if (Type.unbox.isDefinedAt(tpe)) buf.unbox(tpe, obj, unwind)
            else buf.unboxValue(tag, partial = false, obj)
        }
      val argTypes = args.map(_.ty)
      val funcSig = Type.Function(argTypes, unboxedRetType)

      val selfName = genTypeName(defnNir.CFuncPtrClass)
      val getRawPtrName = selfName
        .member(Sig.Field("rawptr", Sig.Scope.Private(selfName)))

      val target = buf.fieldload(Type.Ptr, self, getRawPtrName, unwind)
      val result = buf.call(funcSig, target, args, unwind)
      if (retType != unboxedRetType) buf.box(retType, result, unwind)
      else boxValue(unwrappedRetType, result)
    }

    private final val ExternForwarderSig = Sig.Generated("$extern$forwarder")

    private def genCFuncFromScalaFunction(app: Apply): Val = {
      given pos: nir.Position = app.span
      val fn :: evidences = app.args: @unchecked
      val paramTypes = evidences.map(unwrapTag)

      @tailrec
      def resolveFunction(tree: Tree): Val = tree match {
        case Typed(expr, _) => resolveFunction(expr)
        case Block(_, expr) => resolveFunction(expr)
        case fn @ Closure(env, target, _) =>
          if env.nonEmpty then
            report.error(
              s"Closing over local state of ${env.map(_.symbol.show).mkString(", ")} in function transformed to CFuncPtr results in undefined behaviour.",
              fn.srcPos
            )

          val fnRef = genClosure(fn)
          val Type.Ref(className, _, _) = fnRef.ty: @unchecked

          generatedDefns += genFuncExternForwarder(
            className,
            target.symbol,
            fn,
            paramTypes
          )
          fnRef

        case ref: RefTree =>
          report.error(
            s"Function passed to ${app.symbol.show} needs to be inlined",
            tree.sourcePos
          )
          Val.Null

        case _ =>
          report.error(
            "Failed to resolve function ref for extern forwarder",
            tree.sourcePos
          )
          Val.Null
      }

      val fnRef = resolveFunction(fn)
      val className = genTypeName(app.tpe.sym)

      val ctorTy = nir.Type.Function(
        Seq(Type.Ref(className), Type.Ptr),
        Type.Unit
      )
      val ctorName = className.member(Sig.Ctor(Seq(Type.Ptr)))
      val rawptr = buf.method(fnRef, ExternForwarderSig, unwind)

      val alloc = buf.classalloc(className, unwind)
      buf.call(
        ctorTy,
        Val.Global(ctorName, Type.Ptr),
        Seq(alloc, rawptr),
        unwind
      )
      alloc
    }

    private def genFuncExternForwarder(
        funcName: Global,
        funSym: Symbol,
        funTree: Closure,
        evidences: List[SimpleType]
    )(using nir.Position): Defn = {
      val attrs = Attrs(isExtern = true)

      // In case if passed function is adapted closure it's param types
      // would be erased, in such case we would recover original types
      // using evidence types (materialized unsafe.Tags)
      val isAdapted = funSym.name.mangledString.contains("$adapted$")
      val sig = genMethodSig(funSym)
      val externSig = genExternMethodSig(funSym)

      val Type.Function(origtys, _) =
        if (!isAdapted) sig
        else {
          val params :+ retty = evidences
            .map(genType)
            .map(t => nir.Type.box.getOrElse(t, t)): @unchecked
          Type.Function(params, retty)
        }

      val forwarderSig @ Type.Function(paramtys, retty) =
        if (!isAdapted) externSig
        else {
          val params :+ retty = evidences
            .map(genExternType)
            .map(t => nir.Type.unbox.getOrElse(t, t)): @unchecked
          Type.Function(params, retty)
        }

      val methodName = genMethodName(funSym)
      val method = Val.Global(methodName, Type.Ptr)

      val forwarderName = funcName.member(ExternForwarderSig)
      val forwarderBody = scoped(
        curUnwindHandler := None
      ) {
        val fresh = Fresh()
        val buf = ExprBuffer(using fresh)

        val params = paramtys.map(ty => Val.Local(fresh(), ty))
        buf.label(fresh(), params)
        val origTypes =
          if (funSym.isStaticInNIR || isAdapted) origtys else origtys.tail
        val boxedParams = origTypes.zip(params).map(buf.fromExtern(_, _))
        val argsp = boxedParams.map(ValTree(_))

        // Check number of arguments that would be be used in a call to the function,
        // it should be equal to the quantity of implicit evidences (without return type evidence)
        // and arguments passed via closure env.
        if (argsp.size != evidences.length - 1 + funTree.env.size) {
          report.error(
            "Failed to create scalanative.unsafe.CFuncPtr from scala.Function, report this issue to Scala Native team.",
            funTree.srcPos
          )
        }

        val res =
          if (funSym.isStaticInNIR)
            buf.genApplyStaticMethod(funSym, NoSymbol, argsp)
          else
            val owner = buf.genModule(funSym.owner)
            val selfp = ValTree(owner)
            buf.genApplyMethod(funSym, statically = true, selfp, argsp)

        val unboxedRes = buf.toExtern(retty, res)
        buf.ret(unboxedRes)

        buf.toSeq
      }
      Defn.Define(attrs, forwarderName, forwarderSig, forwarderBody)
    }

    private object WrapArray {
      lazy val isWrapArray: Set[Symbol] = {
        val names = defn
          .ScalaValueClasses()
          .map(sym => nme.wrapXArray(sym.name))
          .concat(Set(nme.wrapRefArray, nme.genericWrapArray))
        val symsInPredef = names.map(defn.ScalaPredefModule.requiredMethod(_))
        val symsInScalaRunTime =
          names.map(defn.ScalaRuntimeModule.requiredMethod(_))
        (symsInPredef ++ symsInScalaRunTime).toSet
      }

      def unapply(tree: Apply): Option[Tree] = tree match {
        case Apply(wrapArray_?, List(wrapped))
            if isWrapArray(wrapArray_?.symbol) =>
          Some(wrapped)
        case _ =>
          None
      }
    }

    private def genReflectiveCall(
        tree: Apply,
        isSelectDynamic: Boolean
    ): Val = {
      given nir.Position = tree.span
      val Apply(fun @ Select(receiver, _), args) = tree: @unchecked

      val selectedValue = genApplyMethod(
        defnNir.ReflectSelectable_selectedValue,
        statically = false,
        genExpr(receiver),
        Seq.empty
      )

      // Extract the method name as a String
      val methodNameStr = args.head match {
        case Literal(Constants.Constant(name: String)) => name
        case _ =>
          report.error(
            "The method name given to Selectable.selectDynamic or Selectable.applyDynamic " +
              "must be a literal string. " +
              "Other uses are not supported in Scala Native.",
            args.head.sourcePos
          )
          "erroneous"
      }

      val (formalParamTypeRefs, actualArgs) =
        if (isSelectDynamic) (Nil, Nil)
        else
          args.tail match {
            // Extract the param type refs and actual args from the 2nd and 3rd argument to applyDynamic
            case WrapArray(classOfsArray: JavaSeqLiteral) ::
                WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
              // Extract nir.Type's from the classOf[_] trees
              val formalParamTypes = classOfsArray.elems.map {
                // classOf[tp] -> tp
                case Literal(const) if const.tag == Constants.ClazzTag =>
                  genType(const.typeValue)

                // Anything else is invalid
                case otherTree =>
                  report.error(
                    "The java.lang.Class[_] arguments passed to Selectable.applyDynamic must be " +
                      "literal classOf[T] expressions (typically compiler-generated). " +
                      "Other uses are not supported in Scala Native.",
                    otherTree.sourcePos
                  )
                  Rt.Object
              }

              // Gen the actual args, downcasting them to the formal param types
              val actualArgs =
                actualArgsAnyArray.elems
                  .zip(formalParamTypes)
                  .map { (actualArgAny, formalParamType) =>
                    val genActualArgAny = genExpr(actualArgAny)
                    (genActualArgAny.ty, formalParamType) match {
                      case (ty: Type.Ref, formal: Type.Ref) =>
                        if ty.name == formal.name then genActualArgAny
                        else
                          buf.as(
                            formalParamType,
                            genActualArgAny,
                            unwind
                          )

                      case (ty: Type.Ref, formal: Type.PrimitiveKind) =>
                        assert(Type.Ref(ty.name) == Type.box(formal))
                        genActualArgAny

                      case _ => scalanative.util.unreachable
                    }
                  }

              (formalParamTypes, actualArgs)

            case _ =>
              report.error(
                "Passing the varargs of Selectable.applyDynamic with `: _*` " +
                  "is not supported in Scala Native.",
                tree.sourcePos
              )
              (Nil, Nil)
          }

      val dynMethod = buf.dynmethod(
        selectedValue,
        Sig.Proxy(methodNameStr, formalParamTypeRefs),
        unwind
      )
      // Proxies operate only on boxed types, however formal param types and name of the method
      // might contain primitive types. With current imlementation of proxies we workaround it
      // by always using boxed types in function calls
      val boxedFormalParamTypeRefs = formalParamTypeRefs.map {
        case ty: Type.PrimitiveKind => Type.box(ty)
        case ty                     => ty
      }
      buf.call(
        Type.Function(selectedValue.ty :: boxedFormalParamTypeRefs, Rt.Object),
        dynMethod,
        selectedValue :: actualArgs,
        unwind
      )
    }

    private def labelExcludeUnitValue(label: Local, value: nir.Val.Local)(using
        nir.Position
    ): nir.Val =
      value.ty match
        case Type.Unit => buf.label(label); Val.Unit
        case _         => buf.label(label, Seq(value)); value

    private def jumpExcludeUnitValue(
        mergeType: nir.Type
    )(label: Local, value: nir.Val)(using
        nir.Position
    ): Unit =
      mergeType match
        case Type.Unit => buf.jump(label, Nil)
        case _         => buf.jump(label, Seq(value))

  }

  sealed class FixupBuffer(using fresh: Fresh) extends nir.Buffer {
    private var labeled = false

    override def +=(inst: Inst): Unit = {
      given nir.Position = inst.pos
      inst match {
        case inst: nir.Inst.Label =>
          if (labeled) {
            unreachable(unwind)
          }
          labeled = true
        case _ =>
          if (!labeled) {
            label(fresh())
          }
          labeled = !inst.isInstanceOf[nir.Inst.Cf]
      }
      super.+=(inst)
      inst match {
        case Inst.Let(_, op, _) if op.resty == Type.Nothing =>
          unreachable(unwind)
          label(fresh())
        case _ =>
          ()
      }
    }

    override def ++=(insts: Seq[Inst]): Unit =
      insts.foreach { inst => this += inst }

    override def ++=(other: nir.Buffer): Unit =
      this ++= other.toSeq
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy