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

scala.scalanative.codegen.Lower.scala Maven / Gradle / Ivy

There is a newer version: 0.5.5
Show newest version
package scala.scalanative
package codegen

import scala.collection.mutable
import scalanative.util.{ScopedVar, unsupported}
import scalanative.linker._
import scalanative.interflow.UseDef.eliminateDeadCode

private[scalanative] object Lower {

  def apply(
      defns: Seq[nir.Defn]
  )(implicit meta: Metadata, logger: build.Logger): Seq[nir.Defn] =
    (new Impl).onDefns(defns)

  private final class Impl(implicit meta: Metadata, logger: build.Logger)
      extends nir.Transform {
    import meta._
    import meta.config
    import meta.layouts.{Rtti, ClassRtti, ArrayHeader}

    implicit val analysis: ReachabilityAnalysis.Result = meta.analysis

    val Object = analysis.infos(nir.Rt.Object.name).asInstanceOf[Class]

    private val zero = nir.Val.Int(0)
    private val one = nir.Val.Int(1)
    val RttiClassIdPath = Seq(zero, nir.Val.Int(Rtti.ClassIdIdx))
    val RttiTraitIdPath = Seq(zero, nir.Val.Int(Rtti.TraitIdIdx))
    val ClassRttiDynmapPath = Seq(zero, nir.Val.Int(ClassRtti.DynmapIdx))
    val ClassRttiVtablePath = Seq(zero, nir.Val.Int(ClassRtti.VtableIdx))
    val ArrayHeaderLengthPath = Seq(zero, nir.Val.Int(ArrayHeader.LengthIdx))

    // Type of the bare runtime type information struct.
    private val classRttiType =
      rtti(analysis.infos(nir.Global.Top("java.lang.Object"))).struct

    // Names of the fields of the java.lang.String in the memory layout order.
    private val stringFieldNames = {
      val node = ClassRef.unapply(nir.Rt.StringName).get
      val names = layout(node).entries.map(_.name)
      assert(names.length == 4, "java.lang.String is expected to have 4 fields")
      names
    }

    private val fresh = new util.ScopedVar[nir.Fresh]
    private val unwindHandler = new util.ScopedVar[Option[nir.Local]]
    private val currentDefn = new util.ScopedVar[nir.Defn.Define]
    private val nullGuardedVals = mutable.Set.empty[nir.Val]
    private def currentDefnRetType = {
      val nir.Type.Function(_, ret) = currentDefn.get.ty
      ret
    }

    private val unreachableSlowPath =
      mutable.Map.empty[Option[nir.Local], nir.Local]
    private val nullPointerSlowPath =
      mutable.Map.empty[Option[nir.Local], nir.Local]
    private val divisionByZeroSlowPath =
      mutable.Map.empty[Option[nir.Local], nir.Local]
    private val classCastSlowPath =
      mutable.Map.empty[Option[nir.Local], nir.Local]
    private val outOfBoundsSlowPath =
      mutable.Map.empty[Option[nir.Local], nir.Local]
    private val noSuchMethodSlowPath =
      mutable.Map.empty[Option[nir.Local], nir.Local]

    private def unwind: nir.Next =
      unwindHandler.get.fold[nir.Next](nir.Next.None) { handler =>
        val exc = nir.Val.Local(fresh(), nir.Rt.Object)
        nir.Next.Unwind(exc, nir.Next.Label(handler, Seq(exc)))
      }

    override def onDefns(defns: Seq[nir.Defn]): Seq[nir.Defn] = {
      val buf = mutable.UnrolledBuffer.empty[nir.Defn]

      defns.foreach {
        case _: nir.Defn.Class | _: nir.Defn.Module | _: nir.Defn.Trait =>
          ()
        case nir.Defn.Declare(attrs, MethodRef(_: Class | _: Trait, _), _)
            if !attrs.isExtern =>
          ()
        case nir.Defn.Var(attrs, FieldRef(_: Class, _), _, _)
            if !attrs.isExtern =>
          ()
        case defn =>
          buf += onDefn(defn)
      }

      buf.toSeq
    }

    override def onDefn(defn: nir.Defn): nir.Defn = defn match {
      case defn: nir.Defn.Define =>
        val nir.Type.Function(_, ty) = defn.ty
        ScopedVar.scoped(
          fresh := nir.Fresh(defn.insts),
          currentDefn := defn
        ) {
          try super.onDefn(defn)
          finally nullGuardedVals.clear()
        }
      case _ =>
        super.onDefn(defn)
    }

    override def onType(ty: nir.Type): nir.Type = ty

    def genNext(
        buf: nir.InstructionBuilder,
        next: nir.Next
    )(implicit pos: nir.SourcePosition): nir.Next = {
      next match {
        case nir.Next.Unwind(exc, next) =>
          nir.Next.Unwind(exc, genNext(buf, next))
        case nir.Next.Case(value, next) =>
          nir.Next.Case(genVal(buf, value), genNext(buf, next))
        case nir.Next.Label(name, args) =>
          nir.Next.Label(name, args.map(genVal(buf, _)))
        case n => n
      }
    }

    private def optionallyBoxedUnit(
        v: nir.Val
    )(implicit pos: nir.SourcePosition): nir.Val = {
      require(
        v.ty == nir.Type.Unit,
        s"Definition is expected to return Unit type, found ${v.ty}"
      )
      if (currentDefnRetType == nir.Type.Unit) nir.Val.Unit
      else unit
    }

    override def onInsts(insts: Seq[nir.Inst]): Seq[nir.Inst] = {
      val buf = new nir.InstructionBuilder()(fresh)
      val handlers = new nir.InstructionBuilder()(fresh)

      buf += insts.head

      def newUnwindHandler(
          next: nir.Next
      )(implicit pos: nir.SourcePosition): Option[nir.Local] =
        next match {
          case nir.Next.None =>
            None
          case nir.Next.Unwind(exc, next) =>
            val handler = fresh()
            handlers.label(handler, Seq(exc))
            handlers.jump(next)
            Some(handler)
          case _ =>
            util.unreachable
        }

      insts.foreach {
        case inst @ nir.Inst.Let(n, nir.Op.Var(ty), unwind) =>
          buf.let(n, nir.Op.Stackalloc(ty, one), unwind)(inst.pos, inst.scopeId)
        case _ => ()
      }

      val nir.Inst.Label(firstLabel, _) = insts.head: @unchecked
      val labelPositions = insts
        .collect { case nir.Inst.Label(id, _) => id }
        .zipWithIndex
        .toMap
      var currentBlockPosition = labelPositions(firstLabel)

      genThisValueNullGuardIfUsed(
        currentDefn.get,
        buf,
        () => newUnwindHandler(nir.Next.None)(insts.head.pos)
      )

      implicit var lastScopeId: nir.ScopeId = nir.ScopeId.TopLevel
      insts.tail.foreach {
        case inst @ nir.Inst.Let(n, op, unwind) =>
          ScopedVar.scoped(
            unwindHandler := newUnwindHandler(unwind)(inst.pos)
          ) {
            lastScopeId = inst.scopeId
            genLet(buf, n, op)(inst.pos, lastScopeId)
          }

        case inst @ nir.Inst.Throw(v, unwind) =>
          ScopedVar.scoped(
            unwindHandler := newUnwindHandler(unwind)(inst.pos)
          ) {
            genThrow(buf, v)(inst.pos, lastScopeId)
          }

        case inst @ nir.Inst.Unreachable(unwind) =>
          ScopedVar.scoped(
            unwindHandler := newUnwindHandler(unwind)(inst.pos)
          ) {
            genUnreachable(buf)(inst.pos)
          }

        case inst @ nir.Inst.Ret(v) =>
          implicit val pos: nir.SourcePosition = inst.pos
          if (config.semanticsConfig.finalFields.isNone) () // no-op
          else
            currentDefn.get.name match {
              case nir.Global.Member(ClassRef(cls), sig) if sig.isCtor && {
                    (config.semanticsConfig.finalFields.isStrict && cls.hasFinalFields) ||
                    (config.semanticsConfig.finalFields.isRelaxed && cls.hasFinalSafePublishFields)
                  } =>
                // Release memory fence after initialization of constructor with final fields
                buf.fence(nir.MemoryOrder.Release)
              case _ => () // no-op
            }
          genGCYieldpoint(buf)
          val retVal =
            if (v.ty == nir.Type.Unit) optionallyBoxedUnit(v)
            else genVal(buf, v)
          buf += nir.Inst.Ret(retVal)

        case inst @ nir.Inst.Jump(next) =>
          implicit val pos: nir.SourcePosition = inst.pos
          // Generate GC yield points before backward jumps, eg. in loops
          next match {
            case nir.Next.Label(target, _)
                if labelPositions(target) < currentBlockPosition =>
              genGCYieldpoint(buf)
            case _ => ()
          }
          buf += nir.Inst.Jump(genNext(buf, next))

        case inst @ nir.Inst.Label(name, _) =>
          currentBlockPosition = labelPositions(name)
          buf += inst

        case inst =>
          buf += inst
      }

      locally {
        implicit val pos: nir.SourcePosition = nir.SourcePosition.NoPosition
        genNullPointerSlowPath(buf)
        genDivisionByZeroSlowPath(buf)
        genClassCastSlowPath(buf)
        genUnreachableSlowPath(buf)
        genOutOfBoundsSlowPath(buf)
        genNoSuchMethodSlowPath(buf)
      }

      nullPointerSlowPath.clear()
      divisionByZeroSlowPath.clear()
      classCastSlowPath.clear()
      unreachableSlowPath.clear()
      outOfBoundsSlowPath.clear()
      noSuchMethodSlowPath.clear()

      buf ++= handlers

      eliminateDeadCode(buf.toSeq.map(onInst))
    }

    override def onInst(inst: nir.Inst): nir.Inst = {
      implicit def pos: nir.SourcePosition = inst.pos
      inst match {
        case nir.Inst.Ret(v) if v.ty == nir.Type.Unit =>
          nir.Inst.Ret(optionallyBoxedUnit(v))
        case _ =>
          super.onInst(inst)
      }
    }

    override def onVal(value: nir.Val): nir.Val = value match {
      case nir.Val.ClassOf(_) =>
        util.unsupported("Lowering ClassOf needs nir.InstructionBuilder")
      case nir.Val.Global(ScopeRef(node), _) =>
        rtti(node).const
      case nir.Val.String(v) =>
        genStringVal(v)
      case nir.Val.Unit =>
        unit
      case _ =>
        super.onVal(value)
    }

    def genVal(buf: nir.InstructionBuilder, value: nir.Val)(implicit
        pos: nir.SourcePosition
    ): nir.Val =
      value match {
        case nir.Val.ClassOf(ScopeRef(node)) =>
          rtti(node).const
        case nir.Val.Const(v) =>
          nir.Val.Const(genVal(buf, v))
        case nir.Val.StructValue(values) =>
          nir.Val.StructValue(values.map(genVal(buf, _)))
        case nir.Val.ArrayValue(ty, values) =>
          nir.Val.ArrayValue(onType(ty), values.map(genVal(buf, _)))
        case _ => onVal(value)
      }

    def genNullPointerSlowPath(
        buf: nir.InstructionBuilder
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      nullPointerSlowPath.toSeq.sortBy(_._2.id).foreach {
        case (slowPathUnwindHandler, slowPath) =>
          ScopedVar.scoped(
            unwindHandler := slowPathUnwindHandler
          ) {
            buf.label(slowPath)
            buf.call(
              throwNullPointerTy,
              throwNullPointerVal,
              Seq(nir.Val.Null),
              unwind
            )
            buf.unreachable(nir.Next.None)
          }
      }
    }

    def genDivisionByZeroSlowPath(
        buf: nir.InstructionBuilder
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      divisionByZeroSlowPath.toSeq.sortBy(_._2.id).foreach {
        case (slowPathUnwindHandler, slowPath) =>
          ScopedVar.scoped(
            unwindHandler := slowPathUnwindHandler
          ) {
            buf.label(slowPath)
            buf.call(
              throwDivisionByZeroTy,
              throwDivisionByZeroVal,
              Seq(nir.Val.Null),
              unwind
            )
            buf.unreachable(nir.Next.None)
          }
      }
    }

    def genClassCastSlowPath(
        buf: nir.InstructionBuilder
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      classCastSlowPath.toSeq.sortBy(_._2.id).foreach {
        case (slowPathUnwindHandler, slowPath) =>
          ScopedVar.scoped(
            unwindHandler := slowPathUnwindHandler
          ) {
            val obj = nir.Val.Local(fresh(), nir.Type.Ptr)
            val toty = nir.Val.Local(fresh(), nir.Type.Ptr)

            buf.label(slowPath, Seq(obj, toty))
            val fromty = buf.let(nir.Op.Load(nir.Type.Ptr, obj), unwind)
            buf.call(
              throwClassCastTy,
              throwClassCastVal,
              Seq(nir.Val.Null, fromty, toty),
              unwind
            )
            buf.unreachable(nir.Next.None)
          }
      }
    }

    def genUnreachableSlowPath(
        buf: nir.InstructionBuilder
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      unreachableSlowPath.toSeq.sortBy(_._2.id).foreach {
        case (slowPathUnwindHandler, slowPath) =>
          ScopedVar.scoped(
            unwindHandler := slowPathUnwindHandler
          ) {
            buf.label(slowPath)
            buf.call(
              throwUndefinedTy,
              throwUndefinedVal,
              Seq(nir.Val.Null),
              unwind
            )
            buf.unreachable(nir.Next.None)
          }
      }
    }

    def genOutOfBoundsSlowPath(
        buf: nir.InstructionBuilder
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      outOfBoundsSlowPath.toSeq.sortBy(_._2.id).foreach {
        case (slowPathUnwindHandler, slowPath) =>
          ScopedVar.scoped(
            unwindHandler := slowPathUnwindHandler
          ) {
            val idx = nir.Val.Local(fresh(), nir.Type.Int)
            val len = nir.Val.Local(fresh(), nir.Type.Int)

            buf.label(slowPath, Seq(idx, len))
            buf.call(
              throwOutOfBoundsTy,
              throwOutOfBoundsVal,
              Seq(nir.Val.Null, idx, len),
              unwind
            )
            buf.unreachable(nir.Next.None)
          }
      }
    }

    def genNoSuchMethodSlowPath(
        buf: nir.InstructionBuilder
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      noSuchMethodSlowPath.toSeq.sortBy(_._2.id).foreach {
        case (slowPathUnwindHandler, slowPath) =>
          ScopedVar.scoped(
            unwindHandler := slowPathUnwindHandler
          ) {
            val sig = nir.Val.Local(fresh(), nir.Type.Ptr)

            buf.label(slowPath, Seq(sig))
            buf.call(
              throwNoSuchMethodTy,
              throwNoSuchMethodVal,
              Seq(nir.Val.Null, sig),
              unwind
            )
            buf.unreachable(nir.Next.None)
          }
      }
    }

    def genLet(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit =
      op.resty match {
        case nir.Type.Unit =>
          genOp(buf, fresh(), op)
          buf.let(n, nir.Op.Copy(unit), unwind)
        case nir.Type.Nothing =>
          genOp(buf, fresh(), op)
          genUnreachable(buf)
          buf.label(fresh(), Seq(nir.Val.Local(n, op.resty)))
        case _ =>
          genOp(buf, n, op)
      }

    def genThrow(
        buf: nir.InstructionBuilder,
        exc: nir.Val
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      genGuardNotNull(buf, exc)
      genOp(buf, fresh(), nir.Op.Call(throwSig, throw_, Seq(exc)))
      buf.unreachable(nir.Next.None)
    }

    def genUnreachable(
        buf: nir.InstructionBuilder
    )(implicit pos: nir.SourcePosition) = {
      val failL = unreachableSlowPath.getOrElseUpdate(unwindHandler, fresh())

      buf.jump(nir.Next(failL))
    }

    def genOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      op match {
        case op: nir.Op.Field =>
          genFieldOp(buf, n, op)
        case op: nir.Op.Fieldload =>
          genFieldloadOp(buf, n, op)
        case op: nir.Op.Fieldstore =>
          genFieldstoreOp(buf, n, op)
        case op: nir.Op.Load =>
          genLoadOp(buf, n, op)
        case op: nir.Op.Store =>
          genStoreOp(buf, n, op)
        case op: nir.Op.Method =>
          genMethodOp(buf, n, op)
        case op: nir.Op.Dynmethod =>
          genDynmethodOp(buf, n, op)
        case op: nir.Op.Is =>
          genIsOp(buf, n, op)
        case op: nir.Op.As =>
          genAsOp(buf, n, op)
        case op: nir.Op.SizeOf =>
          genSizeOfOp(buf, n, op)
        case op: nir.Op.AlignmentOf =>
          genAlignmentOfOp(buf, n, op)
        case op: nir.Op.Classalloc =>
          genClassallocOp(buf, n, op)
        case op: nir.Op.Conv =>
          genConvOp(buf, n, op)
        case op: nir.Op.Call =>
          genCallOp(buf, n, op)
        case op: nir.Op.Comp =>
          genCompOp(buf, n, op)
        case op: nir.Op.Bin =>
          genBinOp(buf, n, op)
        case op: nir.Op.Box =>
          genBoxOp(buf, n, op)
        case op: nir.Op.Unbox =>
          genUnboxOp(buf, n, op)
        case op: nir.Op.Module =>
          genModuleOp(buf, n, op)
        case nir.Op.Var(_) =>
          () // Already emmited
        case nir.Op.Varload(nir.Val.Local(slot, nir.Type.Var(ty))) =>
          buf.let(n, nir.Op.Load(ty, nir.Val.Local(slot, nir.Type.Ptr)), unwind)
        case nir.Op.Varstore(nir.Val.Local(slot, nir.Type.Var(ty)), value) =>
          buf.let(
            n,
            nir.Op
              .Store(ty, nir.Val.Local(slot, nir.Type.Ptr), genVal(buf, value)),
            unwind
          )
        case op: nir.Op.Arrayalloc =>
          genArrayallocOp(buf, n, op)
        case op: nir.Op.Arrayload =>
          genArrayloadOp(buf, n, op)
        case op: nir.Op.Arraystore =>
          genArraystoreOp(buf, n, op)
        case op: nir.Op.Arraylength =>
          genArraylengthOp(buf, n, op)
        case op: nir.Op.Stackalloc =>
          genStackallocOp(buf, n, op)
        case op: nir.Op.Copy =>
          val v = genVal(buf, op.value)
          buf.let(n, nir.Op.Copy(v), unwind)
        case _ =>
          buf.let(n, op, unwind)
      }
    }

    def genGuardNotNull(
        buf: nir.InstructionBuilder,
        obj: nir.Val
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit =
      obj.ty match {
        case ty: nir.Type.RefKind if !ty.isNullable =>
          ()

        case _ if nullGuardedVals.add(obj) =>
          import buf._
          val v = genVal(buf, obj)

          val notNullL = fresh()
          val isNullL =
            nullPointerSlowPath.getOrElseUpdate(unwindHandler, fresh())

          val isNull = comp(nir.Comp.Ine, v.ty, v, nir.Val.Null, unwind)
          branch(isNull, nir.Next(notNullL), nir.Next(isNullL))
          label(notNullL)

        case _ => ()
      }

    def genGuardInBounds(
        buf: nir.InstructionBuilder,
        idx: nir.Val,
        len: nir.Val
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      import buf._

      val inBoundsL = fresh()
      val outOfBoundsL =
        outOfBoundsSlowPath.getOrElseUpdate(unwindHandler, fresh())

      val gt0 = comp(nir.Comp.Sge, nir.Type.Int, idx, zero, unwind)
      val ltLen = comp(nir.Comp.Slt, nir.Type.Int, idx, len, unwind)
      val inBounds = bin(nir.Bin.And, nir.Type.Bool, gt0, ltLen, unwind)
      branch(
        inBounds,
        nir.Next(inBoundsL),
        nir.Next.Label(outOfBoundsL, Seq(idx, len))
      )
      label(inBoundsL)
    }

    def genFieldElemOp(
        buf: nir.InstructionBuilder,
        obj: nir.Val,
        name: nir.Global.Member
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      import buf._
      val v = genVal(buf, obj)
      val FieldRef(cls: Class, fld) = name: @unchecked

      val layout = meta.layout(cls)
      val ty = layout.struct
      val index = layout.index(fld)

      genGuardNotNull(buf, v)
      elem(ty, v, Seq(zero, nir.Val.Int(index)), unwind)
    }

    def genFieldloadOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Fieldload
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      val nir.Op.Fieldload(ty, obj, name) = op
      val field = name match {
        case FieldRef(_, field) => field
        case _ =>
          throw new LinkingException(s"Metadata for field '$name' not found")
      }

      // No explicit memory order for load of final field,
      // all final fields are loaded after a acquire fence
      val memoryOrder =
        if (field.attrs.isVolatile) nir.MemoryOrder.SeqCst
        else nir.MemoryOrder.Unordered

      // Acquire memory fence before loading a final field
      if (field.attrs.isFinal && {
            config.semanticsConfig.finalFields.isStrict ||
            (field.attrs.isSafePublish && config.semanticsConfig.finalFields.isRelaxed)
          }) buf.fence(nir.MemoryOrder.Acquire)

      val elem = genFieldElemOp(buf, genVal(buf, obj), name)
      genLoadOp(buf, n, nir.Op.Load(ty, elem, Some(memoryOrder)))
    }

    def genFieldstoreOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Fieldstore
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      val nir.Op.Fieldstore(ty, obj, name, value) = op
      val field = name match {
        case FieldRef(_, field) => field
        case _ =>
          throw new LinkingException(s"Metadata for field '$name' not found")
      }

      // No explicit memory order for store of final field,
      // all final fields are published with release fence when existing the constructor
      val memoryOrder =
        if (field.attrs.isVolatile) nir.MemoryOrder.SeqCst
        else nir.MemoryOrder.Unordered
      val elem = genFieldElemOp(buf, genVal(buf, obj), name)
      genStoreOp(buf, n, nir.Op.Store(ty, elem, value, Some(memoryOrder)))
    }

    def genFieldOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      val nir.Op.Field(obj, name) = op: @unchecked
      val elem = genFieldElemOp(buf, obj, name)
      buf.let(n, nir.Op.Copy(elem), unwind)
    }

    def genLoadOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Load
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      op match {
        // Convert synchronized load(bool) into load(byte)
        // LLVM is not providing synchronization on booleans
        case nir.Op.Load(nir.Type.Bool, ptr, memoryOrder @ Some(_)) =>
          val valueAsByte = fresh()
          val asPtr =
            if (platform.useOpaquePointers) ptr
            else {
              val asPtr = fresh()
              genConvOp(
                buf,
                asPtr,
                nir.Op.Conv(nir.Conv.Bitcast, nir.Type.Ptr, ptr)
              )
              nir.Val.Local(asPtr, nir.Type.Ptr)
            }
          genLoadOp(
            buf,
            valueAsByte,
            nir.Op.Load(nir.Type.Byte, asPtr, memoryOrder)
          )
          genConvOp(
            buf,
            n,
            nir.Op.Conv(
              nir.Conv.Trunc,
              nir.Type.Bool,
              nir.Val.Local(valueAsByte, nir.Type.Byte)
            )
          )

        case nir.Op.Load(ty, ptr, memoryOrder) =>
          buf.let(
            n,
            nir.Op.Load(ty, genVal(buf, ptr), memoryOrder),
            unwind
          )
      }
    }

    def genStoreOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Store
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      op match {
        // Convert synchronized store(bool) into store(byte)
        // LLVM is not providing synchronization on booleans
        case nir.Op.Store(nir.Type.Bool, ptr, value, memoryOrder @ Some(_)) =>
          val valueAsByte = fresh()
          val asPtr =
            if (platform.useOpaquePointers) ptr
            else {
              val asPtr = fresh()
              genConvOp(
                buf,
                asPtr,
                nir.Op.Conv(nir.Conv.Bitcast, nir.Type.Ptr, ptr)
              )
              nir.Val.Local(asPtr, nir.Type.Ptr)
            }
          genConvOp(
            buf,
            valueAsByte,
            nir.Op.Conv(nir.Conv.Zext, nir.Type.Byte, value)
          )
          genStoreOp(
            buf,
            n,
            nir.Op.Store(
              nir.Type.Byte,
              asPtr,
              nir.Val.Local(valueAsByte, nir.Type.Byte),
              memoryOrder
            )
          )

        case nir.Op.Store(ty, ptr, value, memoryOrder) =>
          buf.let(
            n,
            nir.Op.Store(ty, genVal(buf, ptr), genVal(buf, value), memoryOrder),
            unwind
          )
      }
    }

    def genCompOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Comp
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Comp(comp, ty, l, r) = op
      val left = genVal(buf, l)
      val right = genVal(buf, r)
      buf.let(n, nir.Op.Comp(comp, ty, left, right), unwind)
    }

    // Cached function
    private object shouldGenerateGCYieldPoints {
      import scalanative.build.GC._
      private var lastDefn: nir.Defn.Define = _
      private var lastResult: Boolean = false

      private val supportedGC = meta.config.gc match {
        case Immix | Commix => true
        case _              => false
      }
      private val multithreadingEnabled = meta.platform.isMultithreadingEnabled
      private val usesGCYieldPoints = multithreadingEnabled && supportedGC
      private val useYieldPointTraps = platform.useGCYieldPointTraps

      def apply(defn: nir.Defn.Define): Boolean = {
        if (!usesGCYieldPoints) false
        else if (defn eq lastDefn) lastResult
        else {
          lastDefn = defn
          val nir.Global.Member(_, sig) = defn.name
          lastResult = {
            // Exclude accessors and generated methods
            def mayContainLoops =
              defn.insts.exists {
                case nir.Inst.Jump(_: nir.Next.Label) => true
                case _                                => false
              }
            !sig.isGenerated && (defn.insts.size > 4 || mayContainLoops)
          }
          lastResult
        }
      }
    }
    private def genGCYieldpoint(
        buf: nir.InstructionBuilder,
        genUnwind: Boolean = true
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      if (shouldGenerateGCYieldPoints(currentDefn.get)) {
        // Intrinsic method for LLVM codegen
        buf.call(GCYieldSig, GCYield, Nil, nir.Next.None)
      }
    }

    def genCallOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Call
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Call(ty, ptr, args) = op
      def genCall() = {
        buf.let(
          n,
          nir.Op.Call(
            ty = ty,
            ptr = genVal(buf, ptr),
            args = args.map(genVal(buf, _))
          ),
          unwind
        )
      }

      def switchThreadState(managed: Boolean) = buf.call(
        GCSetMutatorThreadStateSig,
        GCSetMutatorThreadState,
        Seq(nir.Val.Int(if (managed) 0 else 1)),
        if (unwindHandler.isInitialized) unwind else nir.Next.None
      )

      // Extern functions that don't block in strict mode
      object isWellKnownNonBlockingExternFunction
          extends Function1[nir.Sig, Boolean] {
        var nonBlocking = mutable.HashSet.empty[nir.Sig]
        nonBlocking ++= Seq(
          "scalanative_GC_alloc",
          "scalanative_GC_alloc_small",
          "scalanative_GC_alloc_large",
          "scalanative_GC_alloc_array",
          "memcpy",
          "errno"
        ).map(nir.Sig.Extern(_).mangled)
        nonBlocking ++= Set(
          GCYieldName.sig,
          memsetName.sig
        )

        def apply(sig: nir.Sig): Boolean = {
          nonBlocking.contains(sig) || {
            sig.unmangled match {
              case nir.Sig.Extern(name) =>
                val isNonBlocking =
                  name.startsWith("scalanative_atomic_") ||
                    name.startsWith("llvm.")
                if (isNonBlocking) nonBlocking += sig
                isNonBlocking
              case _ => false
            }
          }
        }
      }
      def shouldSwitchThreadState(name: nir.Global.Member) =
        platform.isMultithreadingEnabled && analysis.infos.get(name).exists {
          info =>
            val attrs = info.attrs
            attrs.isExtern && {
              config.semanticsConfig.strictExternCallSemantics match {
                case false => attrs.isBlocking
                case _     => !isWellKnownNonBlockingExternFunction(name.sig)
              }
            }
        }

      ptr match {
        case nir.Val.Global(global: nir.Global.Member, _)
            if shouldSwitchThreadState(global) =>
          switchThreadState(managed = false)
          genCall()
          genGCYieldpoint(buf, genUnwind = false)
          switchThreadState(managed = true)

        case _ => genCall()
      }
    }

    def genMethodOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Method
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      import buf._

      val nir.Op.Method(v, sig) = op
      val obj = genVal(buf, v)

      def genClassVirtualLookup(cls: Class): Unit = {
        val vindex = vtable(cls).index(sig)
        assert(
          vindex != -1,
          s"The virtual table of ${cls.name} does not contain $sig"
        )

        val typeptr = let(nir.Op.Load(nir.Type.Ptr, obj), unwind)
        val methptrptr = let(
          nir.Op.Elem(
            rtti(cls).struct,
            typeptr,
            ClassRttiVtablePath :+ nir.Val.Int(vindex)
          ),
          unwind
        )

        let(n, nir.Op.Load(nir.Type.Ptr, methptrptr), unwind)
      }

      def genTraitVirtualLookup(trt: Trait): Unit = {
        val sigid = dispatchTable.traitSigIds(sig)
        val typeptr = let(nir.Op.Load(nir.Type.Ptr, obj), unwind)
        val idptr =
          let(nir.Op.Elem(Rtti.layout, typeptr, RttiTraitIdPath), unwind)
        val id = let(nir.Op.Load(nir.Type.Int, idptr), unwind)
        val rowptr = let(
          nir.Op.Elem(
            nir.Type.Ptr,
            dispatchTable.dispatchVal,
            Seq(nir.Val.Int(dispatchTable.dispatchOffset(sigid)))
          ),
          unwind
        )
        val methptrptr =
          let(nir.Op.Elem(nir.Type.Ptr, rowptr, Seq(id)), unwind)
        let(n, nir.Op.Load(nir.Type.Ptr, methptrptr), unwind)
      }

      def genMethodLookup(scope: ScopeInfo): Unit = {
        scope.targets(sig).toSeq match {
          case Seq() =>
            let(n, nir.Op.Copy(nir.Val.Null), unwind)
          case Seq(impl) =>
            let(n, nir.Op.Copy(nir.Val.Global(impl, nir.Type.Ptr)), unwind)
          case _ =>
            obj.ty match {
              case ClassRef(cls) =>
                genClassVirtualLookup(cls)
              case TraitRef(_) if Object.calls.contains(sig) =>
                genClassVirtualLookup(Object)
              case TraitRef(trt) =>
                genTraitVirtualLookup(trt)
              case _ => util.unreachable
            }
        }
      }

      def genStaticMethod(cls: Class): Unit = {
        val method = cls
          .resolve(sig)
          .getOrElse {
            unsupported(
              s"Did not find the signature of method $sig in ${cls.name}"
            )
          }
        let(n, nir.Op.Copy(nir.Val.Global(method, nir.Type.Ptr)), unwind)
      }

      def staticMethodIn(cls: Class): Boolean =
        !sig.isVirtual || !cls.calls.contains(sig)

      // We check type of original value, because it may change inside `genVal` transformation
      // Eg. nir.Val.String is transformed to Const(StructValue) which changes type from Ref to Ptr
      v.ty match {
        // Method call with `null` ref argument might be inlined, in such case materialization of local value in Eval would
        // result with nir.Val.Null. We're directly throwing NPE which normally would be handled in slow path of `genGuardNotNull`
        case nir.Type.Null =>
          let(
            n,
            nir.Op
              .Call(throwNullPointerTy, throwNullPointerVal, Seq(nir.Val.Null)),
            unwind
          )
          buf.unreachable(nir.Next.None)

        case ClassRef(cls) if staticMethodIn(cls) =>
          genStaticMethod(cls)

        case ScopeRef(scope) =>
          genGuardNotNull(buf, obj)
          genMethodLookup(scope)

        case _ => util.unreachable
      }
    }

    def genDynmethodOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Dynmethod
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      import buf._

      val nir.Op.Dynmethod(v, sig) = op
      val obj = genVal(buf, v)

      def throwIfNull(value: nir.Val) = {
        val notNullL = fresh()
        val noSuchMethodL =
          noSuchMethodSlowPath.getOrElseUpdate(unwindHandler, fresh())

        val condNull =
          comp(nir.Comp.Ine, nir.Type.Ptr, value, nir.Val.Null, unwind)
        branch(
          condNull,
          nir.Next(notNullL),
          nir.Next.Label(noSuchMethodL, Seq(nir.Val.String(sig.mangle)))
        )
        label(notNullL)
      }

      def genReflectiveLookup(): nir.Val = {
        val methodIndex =
          meta.analysis.dynsigs.zipWithIndex.find(_._1 == sig).get._2

        // Load the type information pointer
        val typeptr = load(nir.Type.Ptr, obj, unwind)
        // Load the dynamic hash map for given type, make sure it's not null
        val mapelem = elem(classRttiType, typeptr, ClassRttiDynmapPath, unwind)
        val mapptr = load(nir.Type.Ptr, mapelem, unwind)
        // If hash map is not null, it has to contain at least one entry
        throwIfNull(mapptr)
        // Perform dynamic dispatch via dyndispatch helper
        val methptrptr = call(
          dyndispatchSig,
          dyndispatch,
          Seq(mapptr, nir.Val.Int(methodIndex)),
          unwind
        )
        // Hash map lookup can still not contain given signature
        throwIfNull(methptrptr)
        let(n, nir.Op.Load(nir.Type.Ptr, methptrptr), unwind)
      }

      genGuardNotNull(buf, obj)
      genReflectiveLookup()
    }

    def genIsOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Is
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      import buf._

      op match {
        case nir.Op.Is(_, nir.Val.Null | nir.Val.Zero(_)) =>
          let(n, nir.Op.Copy(nir.Val.False), unwind)

        case nir.Op.Is(ty, v) =>
          val obj = genVal(buf, v)
          val isNullL, checkL, resultL = fresh()

          // check if obj is null
          val isNull = let(
            nir.Op.Comp(nir.Comp.Ieq, nir.Type.Ptr, obj, nir.Val.Null),
            unwind
          )
          branch(isNull, nir.Next(isNullL), nir.Next(checkL))

          // in case it's null, result is always false
          label(isNullL)
          jump(resultL, Seq(nir.Val.False))

          // otherwise, do an actual instance check
          label(checkL)
          val isInstanceOf = genIsOp(buf, ty, obj)
          jump(resultL, Seq(isInstanceOf))

          // merge the result of two branches
          label(resultL, Seq(nir.Val.Local(n, op.resty)))
      }
    }

    def genIsOp(
        buf: nir.InstructionBuilder,
        ty: nir.Type,
        v: nir.Val
    )(implicit
        srcPosition: nir.SourcePosition,
        scopeId: nir.ScopeId
    ): nir.Val = {
      import buf._
      val obj = genVal(buf, v)

      ty match {
        case ClassRef(cls) if meta.ranges(cls).length == 1 =>
          val typeptr = let(nir.Op.Load(nir.Type.Ptr, obj), unwind)
          let(
            nir.Op.Comp(nir.Comp.Ieq, nir.Type.Ptr, typeptr, rtti(cls).const),
            unwind
          )

        case ClassRef(cls) =>
          val range = meta.ranges(cls)
          val typeptr = let(nir.Op.Load(nir.Type.Ptr, obj), unwind)
          val idptr =
            let(
              nir.Op.Elem(Rtti.layout, typeptr, RttiClassIdPath),
              unwind
            )
          val id = let(nir.Op.Load(nir.Type.Int, idptr), unwind)
          val ge =
            let(
              nir.Op
                .Comp(nir.Comp.Sle, nir.Type.Int, nir.Val.Int(range.start), id),
              unwind
            )
          val le =
            let(
              nir.Op
                .Comp(nir.Comp.Sle, nir.Type.Int, id, nir.Val.Int(range.end)),
              unwind
            )
          let(nir.Op.Bin(nir.Bin.And, nir.Type.Bool, ge, le), unwind)

        case TraitRef(trt) =>
          val typeptr = let(nir.Op.Load(nir.Type.Ptr, obj), unwind)
          val idptr =
            let(
              nir.Op.Elem(Rtti.layout, typeptr, RttiClassIdPath),
              unwind
            )
          val id = let(nir.Op.Load(nir.Type.Int, idptr), unwind)
          let(
            nir.Op.Call(
              Generate.ClassHasTraitSig,
              nir.Val.Global(
                Generate.ClassHasTraitName,
                Generate.ClassHasTraitSig
              ),
              Seq(id, nir.Val.Int(meta.ids(trt)))
            ),
            unwind
          )
        case _ =>
          util.unsupported(s"is[$ty] $obj")
      }
    }

    def genAsOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.As
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      import buf._

      op match {
        case nir.Op.As(ty: nir.Type.RefKind, v) if v.ty == nir.Type.Null =>
          let(n, nir.Op.Copy(nir.Val.Null), unwind)

        case nir.Op.As(ty: nir.Type.RefKind, obj)
            if obj.ty.isInstanceOf[nir.Type.RefKind] =>
          val v = genVal(buf, obj)
          val checkIfIsInstanceOfL, castL = fresh()
          val failL = classCastSlowPath.getOrElseUpdate(unwindHandler, fresh())

          val isNull = comp(nir.Comp.Ieq, v.ty, v, nir.Val.Null, unwind)
          branch(isNull, nir.Next(castL), nir.Next(checkIfIsInstanceOfL))

          label(checkIfIsInstanceOfL)
          val isInstanceOf = genIsOp(buf, ty, v)
          val toTy = rtti(analysis.infos(ty.className)).const
          branch(
            isInstanceOf,
            nir.Next(castL),
            nir.Next.Label(failL, Seq(v, toTy))
          )

          label(castL)
          if (platform.useOpaquePointers)
            let(n, nir.Op.Copy(v), unwind)
          else
            let(n, nir.Op.Conv(nir.Conv.Bitcast, ty, v), unwind)

        case nir.Op.As(to, v) =>
          util.unsupported(s"can't cast from ${v.ty} to $to")
      }
    }

    def genSizeOfOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.SizeOf
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val size = op.ty match {
        case ClassRef(cls) if op.ty != nir.Type.Unit =>
          if (!cls.allocated) {
            val nir.Global.Top(clsName) = cls.name
            logger.warn(
              s"Referencing size of non allocated type ${clsName} in ${srcPosition.show}"
            )
          }
          meta.layout(cls).size
        case _ => MemoryLayout.sizeOf(op.ty)
      }
      buf.let(n, nir.Op.Copy(nir.Val.Size(size)), unwind)
    }

    def genAlignmentOfOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.AlignmentOf
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val alignment = MemoryLayout.alignmentOf(op.ty)
      buf.let(n, nir.Op.Copy(nir.Val.Size(alignment)), unwind)
    }

    def genClassallocOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Classalloc
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Classalloc(ClassRef(cls), v) = op: @unchecked
      val zone = v.map(genVal(buf, _))

      val size = meta.layout(cls).size
      assert(size == size.toInt)

      zone match {
        case Some(zone) =>
          val safeZoneAllocImplMethod = nir.Val.Local(fresh(), nir.Type.Ptr)
          genMethodOp(
            buf,
            safeZoneAllocImplMethod.id,
            nir.Op.Method(zone, safeZoneAllocImpl.sig)
          )
          buf.let(
            n,
            nir.Op.Call(
              safeZoneAllocImplSig,
              safeZoneAllocImplMethod,
              Seq(zone, rtti(cls).const, nir.Val.Size(size.toInt))
            ),
            unwind
          )
        case None =>
          val allocMethod =
            if (size < LARGE_OBJECT_MIN_SIZE) alloc else largeAlloc
          buf.let(
            n,
            nir.Op.Call(
              allocSig(cls.ty),
              allocMethod,
              Seq(rtti(cls).const, nir.Val.Size(size.toInt))
            ),
            unwind
          )
      }
    }

    def genConvOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Conv
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      import buf._

      op match {
        // Fptosi is undefined behaviour on LLVM if the resulting
        // value doesn't fit the MIN...MAX range for given integer type.
        // We insert range checks and return MIN_VALUE for floating values
        // that are numerically less than or equal to MIN_VALUE and MAX_VALUE
        // for the ones which are greate or equal to MAX_VALUE. Additionally,
        // NaNs are converted to 0.
        case nir.Op.Conv(nir.Conv.Fptosi, toty, value) =>
          val v = genVal(buf, value)
          val (imin, imax, fmin, fmax) = toty match {
            case nir.Type.Int =>
              val min = java.lang.Integer.MIN_VALUE
              val max = java.lang.Integer.MAX_VALUE
              v.ty match {
                case nir.Type.Float =>
                  (
                    nir.Val.Int(min),
                    nir.Val.Int(max),
                    nir.Val.Float(min.toFloat),
                    nir.Val.Float(max.toFloat)
                  )
                case nir.Type.Double =>
                  (
                    nir.Val.Int(min),
                    nir.Val.Int(max),
                    nir.Val.Double(min.toDouble),
                    nir.Val.Double(max.toDouble)
                  )
                case _ =>
                  util.unreachable
              }
            case nir.Type.Long =>
              val min = java.lang.Long.MIN_VALUE
              val max = java.lang.Long.MAX_VALUE
              v.ty match {
                case nir.Type.Float =>
                  (
                    nir.Val.Long(min),
                    nir.Val.Long(max),
                    nir.Val.Float(min.toFloat),
                    nir.Val.Float(max.toFloat)
                  )
                case nir.Type.Double =>
                  (
                    nir.Val.Long(min),
                    nir.Val.Long(max),
                    nir.Val.Double(min.toDouble),
                    nir.Val.Double(max.toDouble)
                  )
                case _ =>
                  util.unreachable
              }
            case _ =>
              util.unreachable
          }

          val isNaNL, checkLessThanMinL, lessThanMinL, checkLargerThanMaxL,
              largerThanMaxL, inBoundsL, resultL = fresh()

          val isNaN = comp(nir.Comp.Fne, v.ty, v, v, unwind)
          branch(isNaN, nir.Next(isNaNL), nir.Next(checkLessThanMinL))

          label(isNaNL)
          jump(resultL, Seq(nir.Val.Zero(op.resty)))

          label(checkLessThanMinL)
          val isLessThanMin = comp(nir.Comp.Fle, v.ty, v, fmin, unwind)
          branch(
            isLessThanMin,
            nir.Next(lessThanMinL),
            nir.Next(checkLargerThanMaxL)
          )

          label(lessThanMinL)
          jump(resultL, Seq(imin))

          label(checkLargerThanMaxL)
          val isLargerThanMax = comp(nir.Comp.Fge, v.ty, v, fmax, unwind)
          branch(isLargerThanMax, nir.Next(largerThanMaxL), nir.Next(inBoundsL))

          label(largerThanMaxL)
          jump(resultL, Seq(imax))

          label(inBoundsL)
          val inBoundsResult = let(op, unwind)
          jump(resultL, Seq(inBoundsResult))

          label(resultL, Seq(nir.Val.Local(n, op.resty)))

        case nir.Op.Conv(conv, ty, value) =>
          let(n, nir.Op.Conv(conv, ty, genVal(buf, value)), unwind)
      }
    }

    def genBinOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Bin
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      import buf._

      // LLVM's division by zero is undefined behaviour. We guard
      // the case when the divisor is zero and fail gracefully
      // by throwing an arithmetic exception.
      def checkDivisionByZero(op: nir.Op.Bin): Unit = {
        val nir.Op.Bin(bin, ty: nir.Type.I, dividend, divisor) = op: @unchecked

        val thenL, elseL = fresh()

        val succL = fresh()
        val failL =
          divisionByZeroSlowPath.getOrElseUpdate(unwindHandler, fresh())

        val isZero =
          comp(nir.Comp.Ine, ty, divisor, nir.Val.Zero(ty), unwind)
        branch(isZero, nir.Next(succL), nir.Next(failL))

        label(succL)
        if (bin == nir.Bin.Srem || bin == nir.Bin.Sdiv) {
          checkDivisionOverflow(op)
        } else {
          let(n, op, unwind)
        }
      }

      // Detects taking remainder for division by -1 and replaces
      // it by division by 1 which can't overflow.
      //
      // We implement '%' (remainder) with LLVM's 'srem' and it
      // can overflow for cases:
      //
      // - Int.MinValue % -1
      // - Long.MinValue % -1
      //
      // E.g. On x86_64 'srem' might get translated to 'idiv'
      // which computes both quotient and remainder at once
      // and quotient can overflow.
      def checkDivisionOverflow(op: nir.Op.Bin): Unit = {
        val nir.Op.Bin(bin, ty: nir.Type.I, dividend, divisor) = op: @unchecked

        val mayOverflowL, noOverflowL, didOverflowL, resultL = fresh()

        val minus1 = ty match {
          case nir.Type.Int =>
            nir.Val.Int(-1)
          case nir.Type.Long =>
            nir.Val.Long(-1L)
          case nir.Type.Size =>
            nir.Val.Size(-1L)
          case _ =>
            util.unreachable
        }

        val minValue = ty match {
          case nir.Type.Int =>
            nir.Val.Int(java.lang.Integer.MIN_VALUE)
          case nir.Type.Long =>
            nir.Val.Long(java.lang.Long.MIN_VALUE)
          case nir.Type.Size =>
            if (platform.is32Bit) nir.Val.Size(java.lang.Integer.MIN_VALUE)
            else nir.Val.Size(java.lang.Long.MIN_VALUE)
          case _ =>
            util.unreachable
        }

        val divisorIsMinus1 =
          let(nir.Op.Comp(nir.Comp.Ieq, ty, divisor, minus1), unwind)
        branch(divisorIsMinus1, nir.Next(mayOverflowL), nir.Next(noOverflowL))

        label(mayOverflowL)
        val dividendIsMinValue =
          let(nir.Op.Comp(nir.Comp.Ieq, ty, dividend, minValue), unwind)
        branch(
          dividendIsMinValue,
          nir.Next(didOverflowL),
          nir.Next(noOverflowL)
        )

        label(didOverflowL)
        val overflowResult = bin match {
          case nir.Bin.Srem =>
            nir.Val.Zero(ty)
          case nir.Bin.Sdiv =>
            minValue
          case _ =>
            util.unreachable
        }
        jump(resultL, Seq(overflowResult))

        label(noOverflowL)
        val noOverflowResult = let(op, unwind)
        jump(resultL, Seq(noOverflowResult))

        label(resultL, Seq(nir.Val.Local(n, ty)))
      }

      // Shifts are undefined if the bits shifted by are >= bits in the type.
      // We mask the right hand side with bits in type - 1 to make it defined.
      def maskShift(op: nir.Op.Bin) = {
        val nir.Op.Bin(_, ty: nir.Type.I, _, r) = op: @unchecked
        val mask = ty match {
          case nir.Type.Int =>
            nir.Val.Int(31)
          case nir.Type.Long =>
            nir.Val.Int(63)
          case _ =>
            util.unreachable
        }
        val masked = bin(nir.Bin.And, ty, r, mask, unwind)
        let(n, op.copy(r = masked), unwind)
      }

      op match {
        case op @ nir.Op.Bin(
              bin @ (nir.Bin.Srem | nir.Bin.Urem | nir.Bin.Sdiv | nir.Bin.Udiv),
              ty: nir.Type.I,
              l,
              r
            ) =>
          checkDivisionByZero(op)

        case op @ nir.Op.Bin(
              bin @ (nir.Bin.Shl | nir.Bin.Lshr | nir.Bin.Ashr),
              ty: nir.Type.I,
              l,
              r
            ) =>
          maskShift(op)

        case op =>
          let(n, op, unwind)
      }
    }

    def genBoxOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Box
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Box(ty, v) = op
      val from = genVal(buf, v)

      val methodName = BoxTo(ty)
      val moduleName = methodName.top

      val boxTy =
        nir.Type.Function(Seq(nir.Type.Ref(moduleName), nir.Type.unbox(ty)), ty)

      buf.let(
        n,
        nir.Op.Call(
          boxTy,
          nir.Val.Global(methodName, nir.Type.Ptr),
          Seq(nir.Val.Null, from)
        ),
        unwind
      )
    }

    def genUnboxOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Unbox
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Unbox(ty, v) = op
      val from = genVal(buf, v)

      val methodName = UnboxTo(ty)
      val moduleName = methodName.top

      val unboxTy =
        nir.Type.Function(Seq(nir.Type.Ref(moduleName), ty), nir.Type.unbox(ty))

      buf.let(
        n,
        nir.Op.Call(
          unboxTy,
          nir.Val.Global(methodName, nir.Type.Ptr),
          Seq(nir.Val.Null, from)
        ),
        unwind
      )
    }

    def genModuleOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Module
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId) = {
      val nir.Op.Module(name) = op

      meta.analysis.infos(name) match {
        case cls: Class if cls.isConstantModule =>
          val instance = name.member(nir.Sig.Generated("instance"))
          buf.let(
            n,
            nir.Op.Copy(nir.Val.Global(instance, nir.Type.Ptr)),
            unwind
          )

        case _ =>
          val loadSig = nir.Type.Function(Seq.empty, nir.Type.Ref(name))
          val load =
            nir.Val.Global(name.member(nir.Sig.Generated("load")), nir.Type.Ptr)

          buf.let(n, nir.Op.Call(loadSig, load, Seq.empty), unwind)
      }
    }

    def genArrayallocOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Arrayalloc
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Arrayalloc(ty, v1, v2) = op
      val init = genVal(buf, v1)
      val zone = v2.map(genVal(buf, _))
      init match {
        case len if len.ty == nir.Type.Int =>
          val (arrayAlloc, arrayAllocSig) = zone match {
            case Some(_) => (arrayZoneAlloc, arrayZoneAllocSig)
            case None    => (arrayHeapAlloc, arrayHeapAllocSig)
          }
          val sig = arrayAllocSig.getOrElse(ty, arrayAllocSig(nir.Rt.Object))
          val func = arrayAlloc.getOrElse(ty, arrayAlloc(nir.Rt.Object))
          val module = genModuleOp(buf, fresh(), nir.Op.Module(func.owner))
          buf.let(
            n,
            nir.Op.Call(
              sig,
              nir.Val.Global(func, nir.Type.Ptr),
              zone match {
                case Some(zone) => Seq(module, len, zone)
                case None       => Seq(module, len)
              }
            ),
            unwind
          )
        case arrval: nir.Val.ArrayValue =>
          val sig =
            arraySnapshotSig.getOrElse(ty, arraySnapshotSig(nir.Rt.Object))
          val func = arraySnapshot.getOrElse(ty, arraySnapshot(nir.Rt.Object))
          val module = genModuleOp(buf, fresh(), nir.Op.Module(func.owner))
          val len = nir.Val.Int(arrval.values.length)
          val init =
            if (arrval.values.exists(!_.isCanonical)) {
              // At least one of init values in non canonical (e.g. Val.Local), create a copy on stack
              val alloc = buf.stackalloc(arrval.ty, one, unwind)
              arrval.values.zipWithIndex.foreach {
                case (value, idx) =>
                  val innerPtr =
                    buf.elem(
                      arrval.ty,
                      alloc,
                      Seq(zero, nir.Val.Int(idx)),
                      unwind
                    )
                  buf.store(arrval.elemty, innerPtr, genVal(buf, value), unwind)
              }
              alloc
            } else nir.Val.Const(arrval)
          buf.let(
            n,
            nir.Op.Call(
              sig,
              nir.Val.Global(func, nir.Type.Ptr),
              Seq(module, len, init)
            ),
            unwind
          )
        case _ => util.unreachable
      }
    }

    private def arrayMemoryLayout(
        ty: nir.Type,
        length: Int = 0
    ): nir.Type.StructValue = nir.Type.StructValue(
      Seq(ArrayHeader.layout, nir.Type.ArrayValue(ty, length))
    )
    private def arrayValuePath(idx: nir.Val) = Seq(zero, one, idx)

    def genArrayloadOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Arrayload
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Arrayload(ty, v, idx) = op
      val arr = genVal(buf, v)

      val len = fresh()

      genArraylengthOp(buf, len, nir.Op.Arraylength(arr))
      genGuardInBounds(buf, idx, nir.Val.Local(len, nir.Type.Int))

      val arrTy = arrayMemoryLayout(ty)
      val elemPtr = buf.elem(arrTy, arr, arrayValuePath(idx), unwind)
      buf.let(n, nir.Op.Load(ty, elemPtr), unwind)
    }

    def genArraystoreOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Arraystore
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Arraystore(ty, arr, idx, v) = op
      val len = fresh()
      val value = genVal(buf, v)

      genArraylengthOp(buf, len, nir.Op.Arraylength(arr))
      genGuardInBounds(buf, idx, nir.Val.Local(len, nir.Type.Int))

      val arrTy = arrayMemoryLayout(ty)
      val elemPtr = buf.elem(arrTy, arr, arrayValuePath(idx), unwind)
      genStoreOp(buf, n, nir.Op.Store(ty, elemPtr, value))
    }

    def genArraylengthOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Arraylength
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Arraylength(v) = op
      val arr = genVal(buf, v)

      val sig = arrayLengthSig
      val func = arrayLength

      genGuardNotNull(buf, arr)
      val lenPtr =
        buf.elem(ArrayHeader.layout, arr, ArrayHeaderLengthPath, unwind)
      buf.let(n, nir.Op.Load(nir.Type.Int, lenPtr), unwind)
    }

    def genStackallocOp(
        buf: nir.InstructionBuilder,
        n: nir.Local,
        op: nir.Op.Stackalloc
    )(implicit srcPosition: nir.SourcePosition, scopeId: nir.ScopeId): Unit = {
      val nir.Op.Stackalloc(ty, size) = op
      val initValue = nir.Val.Zero(ty).canonicalize
      val pointee = buf.let(n, op, unwind)
      size match {
        case nir.Val.Size(1) if initValue.isCanonical =>
          buf.let(
            nir.Op.Store(ty, pointee, initValue, None),
            unwind
          )
        case sizeV =>
          val elemSize = MemoryLayout.sizeOf(ty)
          val size = sizeV match {
            case nir.Val.Size(v) => nir.Val.Size(v * elemSize)
            case _ =>
              val asSize = sizeV.ty match {
                case i: nir.Type.FixedSizeI =>
                  if (i.width == platform.sizeOfPtrBits) sizeV
                  else if (i.width > platform.sizeOfPtrBits)
                    buf.conv(nir.Conv.Trunc, nir.Type.Size, sizeV, unwind)
                  else
                    buf.conv(nir.Conv.Zext, nir.Type.Size, sizeV, unwind)

                case _ => sizeV
              }
              if (elemSize == 1) asSize
              else
                buf.let(
                  nir.Op.Bin(
                    nir.Bin.Imul,
                    nir.Type.Size,
                    asSize,
                    nir.Val.Size(elemSize)
                  ),
                  unwind
                )
          }
          buf.call(
            memsetSig,
            memset,
            Seq(pointee, nir.Val.Int(0), size),
            unwind
          )
      }
    }

    def genStringVal(value: String): nir.Val = {
      val StringCls = ClassRef.unapply(nir.Rt.StringName).get
      val CharArrayCls = ClassRef.unapply(CharArrayName).get

      val chars = value.toCharArray
      val charsLength = nir.Val.Int(chars.length)
      val charsConst = nir.Val.Const(
        nir.Val.StructValue(
          rtti(CharArrayCls).const ::
            meta.lockWordVals :::
            charsLength ::
            nir.Val.Int(2) :: // stride is used only by GC
            nir.Val.ArrayValue(
              nir.Type.Char,
              chars.toSeq.map(nir.Val.Char(_))
            ) :: Nil
        )
      )

      val fieldValues = stringFieldNames.map {
        case nir.Rt.StringValueName =>
          charsConst
        case nir.Rt.StringOffsetName =>
          zero
        case nir.Rt.StringCountName =>
          charsLength
        case nir.Rt.StringCachedHashCodeName =>
          nir.Val.Int(stringHashCode(value))
        case _ =>
          util.unreachable
      }

      nir.Val.Const(
        nir.Val.StructValue(
          rtti(StringCls).const ::
            meta.lockWordVals ++
            fieldValues
        )
      )
    }

    private def genThisValueNullGuardIfUsed(
        defn: nir.Defn.Define,
        buf: nir.InstructionBuilder,
        createUnwindHandler: () => Option[nir.Local]
    ) = {
      def usesValue(expected: nir.Val): Boolean = {
        var wasUsed = false
        import scala.util.control.Breaks._
        breakable {
          new nir.Traverse {
            override def onVal(value: nir.Val): Unit = {
              wasUsed = expected eq value
              if (wasUsed) break()
              else super.onVal(value)
            }
            // We're not intrested in cheecking these structures, skip them
            override def onType(ty: nir.Type): Unit = ()
            override def onNext(next: nir.Next): Unit = ()
          }.onDefn(defn)
        }
        wasUsed
      }

      val nir.Global.Member(_, sig) = defn.name
      val nir.Inst.Label(_, args) = defn.insts.head: @unchecked

      val canHaveThisValue =
        !(sig.isStatic || sig.isClinit || sig.isExtern)

      if (canHaveThisValue) {
        args.headOption.foreach { thisValue =>
          thisValue.ty match {
            case ref: nir.Type.Ref if ref.isNullable && usesValue(thisValue) =>
              implicit def pos: nir.SourcePosition = defn.pos
              implicit def scopeId: nir.ScopeId = nir.ScopeId.TopLevel
              ScopedVar.scoped(
                unwindHandler := createUnwindHandler()
              ) {
                genGuardNotNull(buf, thisValue)
              }
            case _ => ()
          }
        }
      }
    }
  }

  // Update java.lang.String::hashCode whenever you change this method.
  def stringHashCode(s: String): Int =
    if (s.length == 0) {
      0
    } else {
      val value = s.toCharArray
      var hash = 0
      var i = 0
      while (i < value.length) {
        hash = value(i) + ((hash << 5) - hash)
        i += 1
      }
      hash
    }

  val LARGE_OBJECT_MIN_SIZE = 8192

  val allocSig: nir.Type.Function =
    nir.Type.Function(Seq(nir.Type.Ptr, nir.Type.Size), nir.Type.Ptr)
  def allocSig(clsType: nir.Type.RefKind): nir.Type.Function =
    allocSig.copy(ret = clsType)

  val allocSmallName = extern("scalanative_GC_alloc_small")
  val alloc = nir.Val.Global(allocSmallName, allocSig)

  val largeAllocName = extern("scalanative_GC_alloc_large")
  val largeAlloc = nir.Val.Global(largeAllocName, allocSig)

  val SafeZone =
    nir.Type.Ref(nir.Global.Top("scala.scalanative.memory.SafeZone"))
  val safeZoneAllocImplSig =
    nir.Type.Function(Seq(SafeZone, nir.Type.Ptr, nir.Type.Size), nir.Type.Ptr)
  val safeZoneAllocImpl = SafeZone.name.member(
    nir.Sig.Method("allocImpl", Seq(nir.Type.Ptr, nir.Type.Size, nir.Type.Ptr))
  )

  val dyndispatchName = extern("scalanative_dyndispatch")
  val dyndispatchSig =
    nir.Type.Function(Seq(nir.Type.Ptr, nir.Type.Int), nir.Type.Ptr)
  val dyndispatch = nir.Val.Global(dyndispatchName, dyndispatchSig)

  val excptnGlobal = nir.Global.Top("java.lang.NoSuchMethodException")
  val excptnInitGlobal =
    nir.Global.Member(excptnGlobal, nir.Sig.Ctor(Seq(nir.Rt.String)))

  val excInitSig = nir.Type.Function(
    Seq(
      nir.Type.Ref(excptnGlobal),
      nir.Type.Ref(nir.Global.Top("java.lang.String"))
    ),
    nir.Type.Unit
  )
  val excInit = nir.Val.Global(excptnInitGlobal, nir.Type.Ptr)

  val CharArrayName =
    nir.Global.Top("scala.scalanative.runtime.CharArray")

  val BoxesRunTime = nir.Global.Top("scala.runtime.BoxesRunTime$")
  val RuntimeBoxes = nir.Global.Top("scala.scalanative.runtime.Boxes$")

  val BoxTo: Map[nir.Type, nir.Global] = nir.Type.boxClasses.map { cls =>
    val name = cls.asInstanceOf[nir.Global.Top].id
    val boxty = nir.Type.Ref(nir.Global.Top(name))
    val module = if (name.startsWith("java.")) BoxesRunTime else RuntimeBoxes
    val id = "boxTo" + name.split("\\.").last
    val tys = Seq(nir.Type.unbox(boxty), boxty)
    val meth = module.member(nir.Sig.Method(id, tys))

    boxty -> meth
  }.toMap

  val UnboxTo: Map[nir.Type, nir.Global] = nir.Type.boxClasses.map { cls =>
    val name = cls.asInstanceOf[nir.Global.Top].id
    val boxty = nir.Type.Ref(nir.Global.Top(name))
    val module = if (name.startsWith("java.")) BoxesRunTime else RuntimeBoxes
    val id = {
      val last = name.split("\\.").last
      val suffix =
        if (last == "Integer") "Int"
        else if (last == "Character") "Char"
        else last
      "unboxTo" + suffix
    }
    val tys = Seq(nir.Rt.Object, nir.Type.unbox(boxty))
    val meth = module.member(nir.Sig.Method(id, tys))

    boxty -> meth
  }.toMap

  private def extern(id: String): nir.Global.Member =
    nir.Global.Member(nir.Global.Top("__"), nir.Sig.Extern(id))

  val unitName = nir.Global.Top("scala.scalanative.runtime.BoxedUnit$")
  val unitInstance = unitName.member(nir.Sig.Generated("instance"))
  val unit = nir.Val.Global(unitInstance, nir.Type.Ptr)

  val throwName = extern("scalanative_throw")
  val throwSig = nir.Type.Function(Seq(nir.Type.Ptr), nir.Type.Nothing)
  val throw_ = nir.Val.Global(throwName, nir.Type.Ptr)

  val arrayHeapAlloc = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      val nir.Global.Top(id) = arrname
      val arrcls = nir.Type.Ref(arrname)
      ty -> nir.Global.Member(
        nir.Global.Top(id + "$"),
        nir.Sig.Method("alloc", Seq(nir.Type.Int, arrcls))
      )
  }.toMap
  val arrayHeapAllocSig = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      val nir.Global.Top(id) = arrname
      ty -> nir.Type.Function(
        Seq(nir.Type.Ref(nir.Global.Top(id + "$")), nir.Type.Int),
        nir.Type.Ref(arrname)
      )
  }.toMap
  val arrayZoneAlloc = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      val nir.Global.Top(id) = arrname
      val arrcls = nir.Type.Ref(arrname)
      ty -> nir.Global.Member(
        nir.Global.Top(id + "$"),
        nir.Sig.Method("alloc", Seq(nir.Type.Int, SafeZone, arrcls))
      )
  }.toMap
  val arrayZoneAllocSig = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      val nir.Global.Top(id) = arrname
      ty -> nir.Type.Function(
        Seq(nir.Type.Ref(nir.Global.Top(id + "$")), nir.Type.Int, SafeZone),
        nir.Type.Ref(arrname)
      )
  }.toMap
  val arraySnapshot = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      val nir.Global.Top(id) = arrname
      val arrcls = nir.Type.Ref(arrname)
      ty -> nir.Global.Member(
        nir.Global.Top(id + "$"),
        nir.Sig.Method("snapshot", Seq(nir.Type.Int, nir.Type.Ptr, arrcls))
      )
  }.toMap
  val arraySnapshotSig = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      val nir.Global.Top(id) = arrname
      ty -> nir.Type.Function(
        Seq(nir.Type.Ref(nir.Global.Top(id + "$")), nir.Type.Int, nir.Type.Ptr),
        nir.Type.Ref(arrname)
      )
  }.toMap
  val arrayApplyGeneric = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      ty -> nir.Global.Member(
        arrname,
        nir.Sig.Method("apply", Seq(nir.Type.Int, nir.Rt.Object))
      )
  }
  val arrayApply = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      ty -> nir.Global.Member(
        arrname,
        nir.Sig.Method("apply", Seq(nir.Type.Int, ty))
      )
  }.toMap
  val arrayApplySig = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      ty -> nir.Type.Function(Seq(nir.Type.Ref(arrname), nir.Type.Int), ty)
  }.toMap
  val arrayUpdateGeneric = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      ty -> nir.Global.Member(
        arrname,
        nir.Sig
          .Method("update", Seq(nir.Type.Int, nir.Rt.Object, nir.Type.Unit))
      )
  }
  val arrayUpdate = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      ty -> nir.Global.Member(
        arrname,
        nir.Sig.Method("update", Seq(nir.Type.Int, ty, nir.Type.Unit))
      )
  }.toMap
  val arrayUpdateSig = nir.Type.typeToArray.map {
    case (ty, arrname) =>
      ty -> nir.Type.Function(
        Seq(nir.Type.Ref(arrname), nir.Type.Int, ty),
        nir.Type.Unit
      )
  }.toMap
  val arrayLength =
    nir.Global.Member(
      nir.Global.Top("scala.scalanative.runtime.Array"),
      nir.Sig.Method("length", Seq(nir.Type.Int))
    )
  val arrayLengthSig =
    nir.Type.Function(
      Seq(nir.Type.Ref(nir.Global.Top("scala.scalanative.runtime.Array"))),
      nir.Type.Int
    )

  val throwDivisionByZeroTy =
    nir.Type.Function(Seq(nir.Rt.Runtime), nir.Type.Nothing)
  val throwDivisionByZero =
    nir.Global.Member(
      nir.Rt.Runtime.name,
      nir.Sig.Method("throwDivisionByZero", Seq(nir.Type.Nothing))
    )
  val throwDivisionByZeroVal =
    nir.Val.Global(throwDivisionByZero, nir.Type.Ptr)

  val throwClassCastTy =
    nir.Type.Function(
      Seq(nir.Rt.Runtime, nir.Type.Ptr, nir.Type.Ptr),
      nir.Type.Nothing
    )
  val throwClassCast =
    nir.Global.Member(
      nir.Rt.Runtime.name,
      nir.Sig.Method(
        "throwClassCast",
        Seq(nir.Type.Ptr, nir.Type.Ptr, nir.Type.Nothing)
      )
    )
  val throwClassCastVal =
    nir.Val.Global(throwClassCast, nir.Type.Ptr)

  val throwNullPointerTy =
    nir.Type.Function(Seq(nir.Rt.Runtime), nir.Type.Nothing)
  val throwNullPointer =
    nir.Global.Member(
      nir.Rt.Runtime.name,
      nir.Sig.Method("throwNullPointer", Seq(nir.Type.Nothing))
    )
  val throwNullPointerVal =
    nir.Val.Global(throwNullPointer, nir.Type.Ptr)

  val throwUndefinedTy =
    nir.Type.Function(Seq(nir.Type.Ptr), nir.Type.Nothing)
  val throwUndefined =
    nir.Global.Member(
      nir.Rt.Runtime.name,
      nir.Sig.Method("throwUndefined", Seq(nir.Type.Nothing))
    )
  val throwUndefinedVal =
    nir.Val.Global(throwUndefined, nir.Type.Ptr)

  val throwOutOfBoundsTy =
    nir.Type.Function(
      Seq(nir.Type.Ptr, nir.Type.Int, nir.Type.Int),
      nir.Type.Nothing
    )
  val throwOutOfBounds =
    nir.Global.Member(
      nir.Rt.Runtime.name,
      nir.Sig.Method(
        "throwOutOfBounds",
        Seq(nir.Type.Int, nir.Type.Int, nir.Type.Nothing)
      )
    )
  val throwOutOfBoundsVal =
    nir.Val.Global(throwOutOfBounds, nir.Type.Ptr)

  val throwNoSuchMethodTy =
    nir.Type.Function(Seq(nir.Type.Ptr, nir.Type.Ptr), nir.Type.Nothing)
  val throwNoSuchMethod =
    nir.Global.Member(
      nir.Rt.Runtime.name,
      nir.Sig.Method("throwNoSuchMethod", Seq(nir.Rt.String, nir.Type.Nothing))
    )
  val throwNoSuchMethodVal =
    nir.Val.Global(throwNoSuchMethod, nir.Type.Ptr)

  val GC = nir.Global.Top("scala.scalanative.runtime.GC$")
  val GCYieldName =
    GC.member(nir.Sig.Extern("scalanative_GC_yield"))
  val GCYieldSig = nir.Type.Function(Nil, nir.Type.Unit)
  val GCYield = nir.Val.Global(GCYieldName, nir.Type.Ptr)

  val GCYieldPointTrapName =
    GC.member(nir.Sig.Extern("scalanative_GC_yieldpoint_trap"))
  val GCYieldPointTrap = nir.Val.Global(GCYieldPointTrapName, nir.Type.Ptr)

  val GCSetMutatorThreadStateSig =
    nir.Type.Function(Seq(nir.Type.Int), nir.Type.Unit)
  val GCSetMutatorThreadState = nir.Val.Global(
    GC.member(nir.Sig.Extern("scalanative_GC_set_mutator_thread_state")),
    nir.Type.Ptr
  )

  val memsetSig =
    nir.Type.Function(
      Seq(nir.Type.Ptr, nir.Type.Int, nir.Type.Size),
      nir.Type.Ptr
    )
  val memsetName = extern("memset")
  val memset = nir.Val.Global(memsetName, nir.Type.Ptr)

  val RuntimeNull = nir.Type.Ref(nir.Global.Top("scala.runtime.Null$"))
  val RuntimeNothing = nir.Type.Ref(nir.Global.Top("scala.runtime.Nothing$"))

  val injects: Seq[nir.Defn] = {
    implicit val pos = nir.SourcePosition.NoPosition
    val buf = mutable.UnrolledBuffer.empty[nir.Defn]
    buf += nir.Defn.Declare(nir.Attrs.None, allocSmallName, allocSig)
    buf += nir.Defn.Declare(nir.Attrs.None, largeAllocName, allocSig)
    buf += nir.Defn.Declare(nir.Attrs.None, dyndispatchName, dyndispatchSig)
    buf += nir.Defn.Declare(nir.Attrs.None, throwName, throwSig)
    buf += nir.Defn.Declare(nir.Attrs(isExtern = true), memsetName, memsetSig)
    buf.toSeq
  }

  def depends(implicit platform: PlatformInfo): Seq[nir.Global] = {
    val buf = mutable.UnrolledBuffer.empty[nir.Global]
    buf ++= nir.Rt.PrimitiveTypes
    buf += nir.Rt.ClassName
    buf += nir.Rt.ClassIdName
    buf += nir.Rt.ClassTraitIdName
    buf += nir.Rt.ClassNameName
    buf += nir.Rt.ClassSizeName
    buf += nir.Rt.ClassIdRangeUntilName
    buf += nir.Rt.StringName
    buf += nir.Rt.StringValueName
    buf += nir.Rt.StringOffsetName
    buf += nir.Rt.StringCountName
    buf += nir.Rt.StringCachedHashCodeName
    buf += CharArrayName
    buf += BoxesRunTime
    buf += RuntimeBoxes
    buf += unitName
    buf ++= BoxTo.values
    buf ++= UnboxTo.values
    buf += arrayLength
    buf ++= arrayHeapAlloc.values
    buf ++= arrayZoneAlloc.values
    buf ++= arraySnapshot.values
    buf ++= arrayApplyGeneric.values
    buf ++= arrayApply.values
    buf ++= arrayUpdateGeneric.values
    buf ++= arrayUpdate.values
    buf += throwDivisionByZero
    buf += throwClassCast
    buf += throwNullPointer
    buf += throwUndefined
    buf += throwOutOfBounds
    buf += throwNoSuchMethod
    buf += RuntimeNull.name
    buf += RuntimeNothing.name
    if (platform.isMultithreadingEnabled) {
      buf += GCYield.name
      if (platform.useGCYieldPointTraps) buf += GCYieldPointTrap.name
      buf += GCSetMutatorThreadState.name
    }
    buf.toSeq
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy