Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
scala.scalanative.codegen.Lower.scala Maven / Gradle / Ivy
package scala.scalanative
package codegen
import scala.collection.mutable
import scalanative.util.{ScopedVar, unsupported}
import scalanative.nir._
import scalanative.linker.{
Class,
Trait,
ScopeInfo,
ScopeRef,
ClassRef,
TraitRef,
FieldRef,
MethodRef,
Result
}
import scalanative.interflow.UseDef.eliminateDeadCode
object Lower {
def apply(defns: Seq[Defn])(implicit meta: Metadata): Seq[Defn] =
(new Impl).onDefns(defns)
private final class Impl(implicit meta: Metadata) extends Transform {
import meta._
implicit val linked: Result = meta.linked
val Object = linked.infos(Rt.Object.name).asInstanceOf[Class]
// Type of the bare runtime type information struct.
private val classRttiType =
rtti(linked.infos(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(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[Fresh]
private val unwindHandler = new util.ScopedVar[Option[Local]]
private val unreachableSlowPath = mutable.Map.empty[Option[Local], Local]
private val nullPointerSlowPath = mutable.Map.empty[Option[Local], Local]
private val divisionByZeroSlowPath = mutable.Map.empty[Option[Local], Local]
private val classCastSlowPath = mutable.Map.empty[Option[Local], Local]
private val outOfBoundsSlowPath = mutable.Map.empty[Option[Local], Local]
private val noSuchMethodSlowPath = mutable.Map.empty[Option[Local], Local]
private def unwind: Next =
unwindHandler.get.fold[Next](Next.None) { handler =>
val exc = Val.Local(fresh(), Rt.Object)
Next.Unwind(exc, Next.Label(handler, Seq(exc)))
}
override def onDefns(defns: Seq[Defn]): Seq[Defn] = {
val buf = mutable.UnrolledBuffer.empty[Defn]
defns.foreach {
case _: Defn.Class | _: Defn.Module | _: Defn.Trait =>
()
case Defn.Declare(attrs, MethodRef(_: Class | _: Trait, _), _)
if !attrs.isExtern =>
()
case Defn.Var(attrs, FieldRef(_: Class, _), _, _) if !attrs.isExtern =>
()
case defn =>
buf += onDefn(defn)
}
buf.toSeq
}
override def onDefn(defn: Defn): Defn = defn match {
case defn: Defn.Define =>
val Type.Function(_, ty) = defn.ty
ScopedVar.scoped(
fresh := Fresh(defn.insts)
) {
super.onDefn(defn)
}
case _ =>
super.onDefn(defn)
}
def genNext(buf: Buffer, next: Next)(implicit pos: Position): Next = {
next match {
case Next.Unwind(exc, next) => Next.Unwind(exc, genNext(buf, next))
case Next.Case(value, next) =>
Next.Case(genVal(buf, value), genNext(buf, next))
case Next.Label(name, args) =>
Next.Label(name, args.map(genVal(buf, _)))
case n => n
}
}
override def onInsts(insts: Seq[Inst]): Seq[Inst] = {
val buf = new nir.Buffer()(fresh)
val handlers = new nir.Buffer()(fresh)
buf += insts.head
def newUnwindHandler(next: Next)(implicit pos: Position): Option[Local] =
next match {
case Next.None =>
None
case Next.Unwind(exc, next) =>
val handler = fresh()
handlers.label(handler, Seq(exc))
handlers.jump(next)
Some(handler)
case _ =>
util.unreachable
}
insts.foreach {
case inst @ Inst.Let(n, Op.Var(ty), unwind) =>
buf.let(n, Op.Stackalloc(ty, Val.Int(1)), unwind)(inst.pos)
case _ =>
()
}
insts.tail.foreach {
case inst @ Inst.Let(n, op, unwind) =>
ScopedVar.scoped(
unwindHandler := newUnwindHandler(unwind)(inst.pos)
) {
genLet(buf, n, op)(inst.pos)
}
case inst @ Inst.Throw(v, unwind) =>
ScopedVar.scoped(
unwindHandler := newUnwindHandler(unwind)(inst.pos)
) {
genThrow(buf, v)(inst.pos)
}
case inst @ Inst.Unreachable(unwind) =>
ScopedVar.scoped(
unwindHandler := newUnwindHandler(unwind)(inst.pos)
) {
genUnreachable(buf)(inst.pos)
}
case inst @ Inst.Ret(v) =>
implicit val pos: Position = inst.pos
buf += Inst.Ret(genVal(buf, v))
case inst @ Inst.Jump(next) =>
implicit val pos: Position = inst.pos
buf += Inst.Jump(genNext(buf, next))
case inst =>
buf += inst
}
implicit val pos: Position = Position.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(super.onInst))
}
override def onVal(value: Val): Val = value match {
case Val.ClassOf(_) =>
util.unsupported("Lowering ClassOf needs nir.Buffer")
case Val.Global(ScopeRef(node), _) => rtti(node).const
case Val.String(v) => genStringVal(v)
case Val.Unit => unit
case _ => super.onVal(value)
}
def genVal(buf: Buffer, value: Val)(implicit pos: Position): Val =
value match {
case Val.ClassOf(ScopeRef(node)) => rtti(node).const
case Val.Const(v) => Val.Const(genVal(buf, v))
case Val.StructValue(values) =>
Val.StructValue(values.map(genVal(buf, _)))
case Val.ArrayValue(ty, values) =>
Val.ArrayValue(onType(ty), values.map(genVal(buf, _)))
case _ => onVal(value)
}
def genNullPointerSlowPath(buf: Buffer)(implicit pos: Position): Unit = {
nullPointerSlowPath.toSeq.sortBy(_._2.id).foreach {
case (slowPathUnwindHandler, slowPath) =>
ScopedVar.scoped(
unwindHandler := slowPathUnwindHandler
) {
buf.label(slowPath)
buf.call(
throwNullPointerTy,
throwNullPointerVal,
Seq(Val.Null),
unwind
)
buf.unreachable(Next.None)
}
}
}
def genDivisionByZeroSlowPath(buf: Buffer)(implicit pos: Position): Unit = {
divisionByZeroSlowPath.toSeq.sortBy(_._2.id).foreach {
case (slowPathUnwindHandler, slowPath) =>
ScopedVar.scoped(
unwindHandler := slowPathUnwindHandler
) {
buf.label(slowPath)
buf.call(
throwDivisionByZeroTy,
throwDivisionByZeroVal,
Seq(Val.Null),
unwind
)
buf.unreachable(Next.None)
}
}
}
def genClassCastSlowPath(buf: Buffer)(implicit pos: Position): Unit = {
classCastSlowPath.toSeq.sortBy(_._2.id).foreach {
case (slowPathUnwindHandler, slowPath) =>
ScopedVar.scoped(
unwindHandler := slowPathUnwindHandler
) {
val obj = Val.Local(fresh(), Type.Ptr)
val toty = Val.Local(fresh(), Type.Ptr)
buf.label(slowPath, Seq(obj, toty))
val fromty = buf.let(Op.Load(Type.Ptr, obj), unwind)
buf.call(
throwClassCastTy,
throwClassCastVal,
Seq(Val.Null, fromty, toty),
unwind
)
buf.unreachable(Next.None)
}
}
}
def genUnreachableSlowPath(buf: Buffer)(implicit pos: Position): Unit = {
unreachableSlowPath.toSeq.sortBy(_._2.id).foreach {
case (slowPathUnwindHandler, slowPath) =>
ScopedVar.scoped(
unwindHandler := slowPathUnwindHandler
) {
buf.label(slowPath)
buf.call(throwUndefinedTy, throwUndefinedVal, Seq(Val.Null), unwind)
buf.unreachable(Next.None)
}
}
}
def genOutOfBoundsSlowPath(buf: Buffer)(implicit pos: Position): Unit = {
outOfBoundsSlowPath.toSeq.sortBy(_._2.id).foreach {
case (slowPathUnwindHandler, slowPath) =>
ScopedVar.scoped(
unwindHandler := slowPathUnwindHandler
) {
val idx = Val.Local(fresh(), Type.Int)
buf.label(slowPath, Seq(idx))
buf.call(
throwOutOfBoundsTy,
throwOutOfBoundsVal,
Seq(Val.Null, idx),
unwind
)
buf.unreachable(Next.None)
}
}
}
def genNoSuchMethodSlowPath(buf: Buffer)(implicit pos: Position): Unit = {
noSuchMethodSlowPath.toSeq.sortBy(_._2.id).foreach {
case (slowPathUnwindHandler, slowPath) =>
ScopedVar.scoped(
unwindHandler := slowPathUnwindHandler
) {
val sig = Val.Local(fresh(), Type.Ptr)
buf.label(slowPath, Seq(sig))
buf.call(
throwNoSuchMethodTy,
throwNoSuchMethodVal,
Seq(Val.Null, sig),
unwind
)
buf.unreachable(Next.None)
}
}
}
def genLet(buf: Buffer, n: Local, op: Op)(implicit pos: Position): Unit =
op.resty match {
case Type.Unit =>
genOp(buf, fresh(), op)
buf.let(n, Op.Copy(unit), unwind)
case Type.Nothing =>
genOp(buf, fresh(), op)
genUnreachable(buf)
buf.label(fresh(), Seq(Val.Local(n, op.resty)))
case _ =>
genOp(buf, n, op)
}
def genThrow(buf: Buffer, exc: Val)(implicit pos: Position) = {
genGuardNotNull(buf, exc)
genOp(buf, fresh(), Op.Call(throwSig, throw_, Seq(exc)))
buf.unreachable(Next.None)
}
def genUnreachable(buf: Buffer)(implicit pos: Position) = {
val failL = unreachableSlowPath.getOrElseUpdate(unwindHandler, fresh())
buf.jump(Next(failL))
}
def genOp(buf: Buffer, n: Local, op: Op)(implicit pos: Position): Unit = {
op match {
case op: Op.Field =>
genFieldOp(buf, n, op)
case op: Op.Fieldload =>
genFieldloadOp(buf, n, op)
case op: Op.Fieldstore =>
genFieldstoreOp(buf, n, op)
case op: Op.Store =>
genStoreOp(buf, n, op)
case op: Op.Method =>
genMethodOp(buf, n, op)
case op: Op.Dynmethod =>
genDynmethodOp(buf, n, op)
case op: Op.Is =>
genIsOp(buf, n, op)
case op: Op.As =>
genAsOp(buf, n, op)
case op: Op.Sizeof =>
genSizeofOp(buf, n, op)
case op: Op.Classalloc =>
genClassallocOp(buf, n, op)
case op: Op.Conv =>
genConvOp(buf, n, op)
case op: Op.Call =>
genCallOp(buf, n, op)
case op: Op.Comp =>
genCompOp(buf, n, op)
case op: Op.Bin =>
genBinOp(buf, n, op)
case op: Op.Box =>
genBoxOp(buf, n, op)
case op: Op.Unbox =>
genUnboxOp(buf, n, op)
case op: Op.Module =>
genModuleOp(buf, n, op)
case op: Op.Var =>
()
case Op.Varload(Val.Local(slot, Type.Var(ty))) =>
buf.let(n, Op.Load(ty, Val.Local(slot, Type.Ptr)), unwind)
case Op.Varstore(Val.Local(slot, Type.Var(ty)), value) =>
buf.let(n, Op.Store(ty, Val.Local(slot, Type.Ptr), value), unwind)
case op: Op.Arrayalloc =>
genArrayallocOp(buf, n, op)
case op: Op.Arrayload =>
genArrayloadOp(buf, n, op)
case op: Op.Arraystore =>
genArraystoreOp(buf, n, op)
case op: Op.Arraylength =>
genArraylengthOp(buf, n, op)
case _ =>
buf.let(n, op, unwind)
}
}
def genGuardNotNull(buf: Buffer, obj: Val)(implicit pos: Position): Unit =
obj.ty match {
case ty: Type.RefKind if !ty.isNullable =>
()
case _ =>
import buf._
val v = genVal(buf, obj)
val notNullL = fresh()
val isNullL =
nullPointerSlowPath.getOrElseUpdate(unwindHandler, fresh())
val isNull = comp(Comp.Ine, v.ty, v, Val.Null, unwind)
branch(isNull, Next(notNullL), Next(isNullL))
label(notNullL)
}
def genGuardInBounds(buf: Buffer, idx: Val, len: Val)(implicit
pos: Position
): Unit = {
import buf._
val inBoundsL = fresh()
val outOfBoundsL =
outOfBoundsSlowPath.getOrElseUpdate(unwindHandler, fresh())
val gt0 = comp(Comp.Sge, Type.Int, idx, Val.Int(0), unwind)
val ltLen = comp(Comp.Slt, Type.Int, idx, len, unwind)
val inBounds = bin(Bin.And, Type.Bool, gt0, ltLen, unwind)
branch(inBounds, Next(inBoundsL), Next.Label(outOfBoundsL, Seq(idx)))
label(inBoundsL)
}
def genFieldElemOp(buf: Buffer, obj: Val, name: Global)(implicit
pos: Position
) = {
import buf._
val v = genVal(buf, obj)
val FieldRef(cls: Class, fld) = name
val layout = meta.layout(cls)
val ty = layout.struct
val index = layout.index(fld)
genGuardNotNull(buf, v)
elem(ty, v, Seq(Val.Int(0), Val.Int(index)), unwind)
}
def genFieldloadOp(buf: Buffer, n: Local, op: Op.Fieldload)(implicit
pos: Position
) = {
val Op.Fieldload(ty, obj, name) = op
val elem = genFieldElemOp(buf, genVal(buf, obj), name)
buf.let(n, Op.Load(ty, elem), unwind)
}
def genFieldstoreOp(buf: Buffer, n: Local, op: Op.Fieldstore)(implicit
pos: Position
) = {
val Op.Fieldstore(ty, obj, name, value) = op
val elem = genFieldElemOp(buf, genVal(buf, obj), name)
genStoreOp(buf, n, Op.Store(ty, elem, value))
}
def genFieldOp(buf: Buffer, n: Local, op: Op)(implicit
pos: Position
) = {
val Op.Field(obj, name) = op
val elem = genFieldElemOp(buf, obj, name)
buf.let(n, Op.Copy(elem), unwind)
}
def genStoreOp(buf: Buffer, n: Local, op: Op.Store)(implicit
pos: Position
) = {
val Op.Store(ty, ptr, value) = op
buf.let(n, Op.Store(ty, genVal(buf, ptr), genVal(buf, value)), unwind)
}
def genCompOp(buf: Buffer, n: Local, op: Op.Comp)(implicit
pos: Position
): Unit = {
val Op.Comp(comp, ty, l, r) = op
val left = genVal(buf, l)
val right = genVal(buf, r)
buf.let(n, Op.Comp(comp, ty, left, right), unwind)
}
def genCallOp(buf: Buffer, n: Local, op: Op.Call)(implicit
pos: Position
): Unit = {
val Op.Call(ty, ptr, args) = op
buf.let(
n,
Op.Call(
ty = ty,
ptr = genVal(buf, ptr),
args = args.map(genVal(buf, _))
),
unwind
)
}
def genMethodOp(buf: Buffer, n: Local, op: Op.Method)(implicit
pos: Position
) = {
import buf._
val 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(Op.Load(Type.Ptr, obj), unwind)
val methptrptr = let(
Op.Elem(
rtti(cls).struct,
typeptr,
meta.RttiVtableIndex :+ Val.Int(vindex)
),
unwind
)
let(n, Op.Load(Type.Ptr, methptrptr), unwind)
}
def genTraitVirtualLookup(trt: Trait): Unit = {
val sigid = dispatchTable.traitSigIds(sig)
val typeptr = let(Op.Load(Type.Ptr, obj), unwind)
val idptr =
let(Op.Elem(meta.Rtti, typeptr, meta.RttiTraitIdIndex), unwind)
val id = let(Op.Load(Type.Int, idptr), unwind)
val rowptr = let(
Op.Elem(
Type.Ptr,
dispatchTable.dispatchVal,
Seq(Val.Int(dispatchTable.dispatchOffset(sigid)))
),
unwind
)
val methptrptr =
let(Op.Elem(Type.Ptr, rowptr, Seq(id)), unwind)
let(n, Op.Load(Type.Ptr, methptrptr), unwind)
}
def genMethodLookup(scope: ScopeInfo): Unit = {
scope.targets(sig).toSeq match {
case Seq() =>
let(n, Op.Copy(Val.Null), unwind)
case Seq(impl) =>
let(n, Op.Copy(Val.Global(impl, 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, Op.Copy(Val.Global(method, 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. 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 Val.Null. We're directly throwing NPE which normally would be handled in slow path of `genGuardNotNull`
case Type.Null =>
let(
n,
Op.Call(throwNullPointerTy, throwNullPointerVal, Seq(Val.Null)),
unwind
)
buf.unreachable(Next.None)
case ClassRef(cls) if staticMethodIn(cls) =>
genStaticMethod(cls)
case ScopeRef(scope) =>
genGuardNotNull(buf, obj)
genMethodLookup(scope)
case _ => util.unreachable
}
}
def genDynmethodOp(buf: Buffer, n: Local, op: Op.Dynmethod)(implicit
pos: Position
): Unit = {
import buf._
val Op.Dynmethod(v, sig) = op
val obj = genVal(buf, v)
def throwIfNull(value: Val) = {
val notNullL = fresh()
val noSuchMethodL =
noSuchMethodSlowPath.getOrElseUpdate(unwindHandler, fresh())
val condNull = comp(Comp.Ine, Type.Ptr, value, Val.Null, unwind)
branch(
condNull,
Next(notNullL),
Next.Label(noSuchMethodL, Seq(Val.String(sig.mangle)))
)
label(notNullL)
}
def genReflectiveLookup(): Val = {
val methodIndex =
meta.linked.dynsigs.zipWithIndex.find(_._1 == sig).get._2
// Load the type information pointer
val typeptr = load(Type.Ptr, obj, unwind)
// Load the dynamic hash map for given type, make sure it's not null
val mapelem = elem(classRttiType, typeptr, meta.RttiDynmapIndex, unwind)
val mapptr = load(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, Val.Int(methodIndex)),
unwind
)
// Hash map lookup can still not contain given signature
throwIfNull(methptrptr)
let(n, Op.Load(Type.Ptr, methptrptr), unwind)
}
genGuardNotNull(buf, obj)
genReflectiveLookup()
}
def genIsOp(buf: Buffer, n: Local, op: Op.Is)(implicit
pos: Position
): Unit = {
import buf._
op match {
case Op.Is(_, Val.Null | Val.Zero(_)) =>
let(n, Op.Copy(Val.False), unwind)
case Op.Is(ty, v) =>
val obj = genVal(buf, v)
val isNullL, checkL, resultL = fresh()
// check if obj is null
val isNull = let(Op.Comp(Comp.Ieq, Type.Ptr, obj, Val.Null), unwind)
branch(isNull, Next(isNullL), Next(checkL))
// in case it's null, result is always false
label(isNullL)
jump(resultL, Seq(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(Val.Local(n, op.resty)))
}
}
def genIsOp(buf: Buffer, ty: Type, v: Val)(implicit pos: Position): Val = {
import buf._
val obj = genVal(buf, v)
ty match {
case ClassRef(cls) if meta.ranges(cls).length == 1 =>
val typeptr = let(Op.Load(Type.Ptr, obj), unwind)
let(Op.Comp(Comp.Ieq, Type.Ptr, typeptr, rtti(cls).const), unwind)
case ClassRef(cls) =>
val range = meta.ranges(cls)
val typeptr = let(Op.Load(Type.Ptr, obj), unwind)
val idptr =
let(Op.Elem(meta.Rtti, typeptr, meta.RttiClassIdIndex), unwind)
val id = let(Op.Load(Type.Int, idptr), unwind)
val ge =
let(Op.Comp(Comp.Sle, Type.Int, Val.Int(range.start), id), unwind)
val le =
let(Op.Comp(Comp.Sle, Type.Int, id, Val.Int(range.end)), unwind)
let(Op.Bin(Bin.And, Type.Bool, ge, le), unwind)
case TraitRef(trt) =>
val typeptr = let(Op.Load(Type.Ptr, obj), unwind)
val idptr =
let(Op.Elem(meta.Rtti, typeptr, meta.RttiClassIdIndex), unwind)
val id = let(Op.Load(Type.Int, idptr), unwind)
val boolptr = let(
Op.Elem(
hasTraitTables.classHasTraitTy,
hasTraitTables.classHasTraitVal,
Seq(Val.Int(0), id, Val.Int(meta.ids(trt)))
),
unwind
)
let(Op.Load(Type.Bool, boolptr), unwind)
case _ =>
util.unsupported(s"is[$ty] $obj")
}
}
def genAsOp(buf: Buffer, n: Local, op: Op.As)(implicit
pos: Position
): Unit = {
import buf._
op match {
case Op.As(ty: Type.RefKind, v) if v.ty == Type.Null =>
let(n, Op.Copy(Val.Null), unwind)
case Op.As(ty: Type.RefKind, obj)
if obj.ty.isInstanceOf[Type.RefKind] =>
val v = genVal(buf, obj)
val checkIfIsInstanceOfL, castL = fresh()
val failL = classCastSlowPath.getOrElseUpdate(unwindHandler, fresh())
val isNull = comp(Comp.Ieq, v.ty, v, Val.Null, unwind)
branch(isNull, Next(castL), Next(checkIfIsInstanceOfL))
label(checkIfIsInstanceOfL)
val isInstanceOf = genIsOp(buf, ty, v)
val toTy = rtti(linked.infos(ty.className)).const
branch(isInstanceOf, Next(castL), Next.Label(failL, Seq(v, toTy)))
label(castL)
let(n, Op.Conv(Conv.Bitcast, ty, v), unwind)
case Op.As(to, v) =>
util.unsupported(s"can't cast from ${v.ty} to $to")
}
}
def genSizeofOp(buf: Buffer, n: Local, op: Op.Sizeof)(implicit
pos: Position
): Unit = {
val Op.Sizeof(ty) = op
buf.let(n, Op.Copy(Val.Long(MemoryLayout.sizeOf(ty))), unwind)
}
def genClassallocOp(buf: Buffer, n: Local, op: Op.Classalloc)(implicit
pos: Position
): Unit = {
val Op.Classalloc(ClassRef(cls)) = op
val size = MemoryLayout.sizeOf(layout(cls).struct)
val allocMethod =
if (size < LARGE_OBJECT_MIN_SIZE) alloc else largeAlloc
buf.let(
n,
Op.Call(allocSig, allocMethod, Seq(rtti(cls).const, Val.Long(size))),
unwind
)
}
def genConvOp(buf: Buffer, n: Local, op: Op.Conv)(implicit
pos: Position
): 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 Op.Conv(Conv.Fptosi, toty, value) =>
val v = genVal(buf, value)
val (imin, imax, fmin, fmax) = toty match {
case Type.Int =>
val min = java.lang.Integer.MIN_VALUE
val max = java.lang.Integer.MAX_VALUE
v.ty match {
case Type.Float =>
(
Val.Int(min),
Val.Int(max),
Val.Float(min.toFloat),
Val.Float(max.toFloat)
)
case Type.Double =>
(
Val.Int(min),
Val.Int(max),
Val.Double(min.toDouble),
Val.Double(max.toDouble)
)
case _ =>
util.unreachable
}
case Type.Long =>
val min = java.lang.Long.MIN_VALUE
val max = java.lang.Long.MAX_VALUE
v.ty match {
case Type.Float =>
(
Val.Long(min),
Val.Long(max),
Val.Float(min.toFloat),
Val.Float(max.toFloat)
)
case Type.Double =>
(
Val.Long(min),
Val.Long(max),
Val.Double(min.toDouble),
Val.Double(max.toDouble)
)
case _ =>
util.unreachable
}
case _ =>
util.unreachable
}
val isNaNL, checkLessThanMinL, lessThanMinL, checkLargerThanMaxL,
largerThanMaxL, inBoundsL, resultL = fresh()
val isNaN = comp(Comp.Fne, v.ty, v, v, unwind)
branch(isNaN, Next(isNaNL), Next(checkLessThanMinL))
label(isNaNL)
jump(resultL, Seq(Val.Zero(op.resty)))
label(checkLessThanMinL)
val isLessThanMin = comp(Comp.Fle, v.ty, v, fmin, unwind)
branch(isLessThanMin, Next(lessThanMinL), Next(checkLargerThanMaxL))
label(lessThanMinL)
jump(resultL, Seq(imin))
label(checkLargerThanMaxL)
val isLargerThanMax = comp(Comp.Fge, v.ty, v, fmax, unwind)
branch(isLargerThanMax, Next(largerThanMaxL), Next(inBoundsL))
label(largerThanMaxL)
jump(resultL, Seq(imax))
label(inBoundsL)
val inBoundsResult = let(op, unwind)
jump(resultL, Seq(inBoundsResult))
label(resultL, Seq(Val.Local(n, op.resty)))
case Op.Conv(conv, ty, value) =>
let(n, Op.Conv(conv, ty, genVal(buf, value)), unwind)
}
}
def genBinOp(buf: Buffer, n: Local, op: Op.Bin)(implicit
pos: Position
): 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: Op.Bin): Unit = {
val Op.Bin(bin, ty: Type.I, dividend, divisor) = op
val thenL, elseL = fresh()
val succL = fresh()
val failL =
divisionByZeroSlowPath.getOrElseUpdate(unwindHandler, fresh())
val isZero =
comp(Comp.Ine, ty, divisor, Val.Zero(ty), unwind)
branch(isZero, Next(succL), Next(failL))
label(succL)
if (bin == Bin.Srem || bin == 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: Op.Bin): Unit = {
val Op.Bin(bin, ty: Type.I, dividend, divisor) = op
val mayOverflowL, noOverflowL, didOverflowL, resultL = fresh()
val minus1 = ty match {
case Type.Int => Val.Int(-1)
case Type.Long => Val.Long(-1L)
case _ => util.unreachable
}
val minValue = ty match {
case Type.Int => Val.Int(java.lang.Integer.MIN_VALUE)
case Type.Long => Val.Long(java.lang.Long.MIN_VALUE)
case _ => util.unreachable
}
val divisorIsMinus1 =
let(Op.Comp(Comp.Ieq, ty, divisor, minus1), unwind)
branch(divisorIsMinus1, Next(mayOverflowL), Next(noOverflowL))
label(mayOverflowL)
val dividendIsMinValue =
let(Op.Comp(Comp.Ieq, ty, dividend, minValue), unwind)
branch(dividendIsMinValue, Next(didOverflowL), Next(noOverflowL))
label(didOverflowL)
val overflowResult = bin match {
case Bin.Srem => Val.Zero(ty)
case Bin.Sdiv => minValue
case _ => util.unreachable
}
jump(resultL, Seq(overflowResult))
label(noOverflowL)
val noOverflowResult = let(op, unwind)
jump(resultL, Seq(noOverflowResult))
label(resultL, Seq(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: Op.Bin) = {
val Op.Bin(_, ty: Type.I, _, r) = op
val mask = ty match {
case Type.Int => Val.Int(31)
case Type.Long => Val.Int(63)
case _ => util.unreachable
}
val masked = bin(Bin.And, ty, r, mask, unwind)
let(n, op.copy(r = masked), unwind)
}
op match {
case op @ Op.Bin(
bin @ (Bin.Srem | Bin.Urem | Bin.Sdiv | Bin.Udiv),
ty: Type.I,
l,
r
) =>
checkDivisionByZero(op)
case op @ Op.Bin(
bin @ (Bin.Shl | Bin.Lshr | Bin.Ashr),
ty: Type.I,
l,
r
) =>
maskShift(op)
case op =>
let(n, op, unwind)
}
}
def genBoxOp(buf: Buffer, n: Local, op: Op.Box)(implicit
pos: Position
): Unit = {
val Op.Box(ty, v) = op
val from = genVal(buf, v)
val methodName = BoxTo(ty)
val moduleName = methodName.top
val boxTy =
Type.Function(Seq(Type.Ref(moduleName), Type.unbox(ty)), ty)
buf.let(
n,
Op.Call(boxTy, Val.Global(methodName, Type.Ptr), Seq(Val.Null, from)),
unwind
)
}
def genUnboxOp(buf: Buffer, n: Local, op: Op.Unbox)(implicit
pos: Position
): Unit = {
val Op.Unbox(ty, v) = op
val from = genVal(buf, v)
val methodName = UnboxTo(ty)
val moduleName = methodName.top
val unboxTy =
Type.Function(Seq(Type.Ref(moduleName), ty), Type.unbox(ty))
buf.let(
n,
Op.Call(unboxTy, Val.Global(methodName, Type.Ptr), Seq(Val.Null, from)),
unwind
)
}
def genModuleOp(buf: Buffer, n: Local, op: Op.Module)(implicit
pos: Position
) = {
val Op.Module(name) = op
meta.linked.infos(name) match {
case cls: Class if cls.isConstantModule =>
val instance = name.member(Sig.Generated("instance"))
buf.let(n, Op.Copy(Val.Global(instance, Type.Ptr)), unwind)
case _ =>
val loadSig = Type.Function(Seq(), Type.Ref(name))
val load = Val.Global(name.member(Sig.Generated("load")), Type.Ptr)
buf.let(n, Op.Call(loadSig, load, Seq()), unwind)
}
}
def genArrayallocOp(buf: Buffer, n: Local, op: Op.Arrayalloc)(implicit
pos: Position
): Unit = {
val Op.Arrayalloc(ty, v) = op
val init = genVal(buf, v)
init match {
case len if len.ty == Type.Int =>
val sig = arrayAllocSig.getOrElse(ty, arrayAllocSig(Rt.Object))
val func = arrayAlloc.getOrElse(ty, arrayAlloc(Rt.Object))
val module = genModuleOp(buf, fresh(), Op.Module(func.owner))
buf.let(
n,
Op.Call(sig, Val.Global(func, Type.Ptr), Seq(module, len)),
unwind
)
case arrval: Val.ArrayValue =>
val sig = arraySnapshotSig.getOrElse(ty, arraySnapshotSig(Rt.Object))
val func = arraySnapshot.getOrElse(ty, arraySnapshot(Rt.Object))
val module = genModuleOp(buf, fresh(), Op.Module(func.owner))
val len = Val.Int(arrval.values.length)
val init = Val.Const(arrval)
buf.let(
n,
Op.Call(sig, Val.Global(func, Type.Ptr), Seq(module, len, init)),
unwind
)
case _ => util.unreachable
}
}
def genArrayloadOp(buf: Buffer, n: Local, op: Op.Arrayload)(implicit
pos: Position
): Unit = {
val Op.Arrayload(ty, v, idx) = op
val arr = genVal(buf, v)
val len = fresh()
genArraylengthOp(buf, len, Op.Arraylength(arr))
genGuardInBounds(buf, idx, Val.Local(len, Type.Int))
val arrTy = Type.StructValue(
Seq(Type.Ptr, Type.Int, Type.Int, Type.ArrayValue(ty, 0))
)
val elemPath = Seq(Val.Int(0), Val.Int(3), idx)
val elemPtr = buf.elem(arrTy, arr, elemPath, unwind)
buf.let(n, Op.Load(ty, elemPtr), unwind)
}
def genArraystoreOp(buf: Buffer, n: Local, op: Op.Arraystore)(implicit
pos: Position
): Unit = {
val Op.Arraystore(ty, arr, idx, v) = op
val len = fresh()
val value = genVal(buf, v)
genArraylengthOp(buf, len, Op.Arraylength(arr))
genGuardInBounds(buf, idx, Val.Local(len, Type.Int))
val arrTy = Type.StructValue(
Seq(Type.Ptr, Type.Int, Type.Int, Type.ArrayValue(ty, 0))
)
val elemPtr =
buf.elem(arrTy, arr, Seq(Val.Int(0), Val.Int(3), idx), unwind)
genStoreOp(buf, n, Op.Store(ty, elemPtr, value))
}
def genArraylengthOp(buf: Buffer, n: Local, op: Op.Arraylength)(implicit
pos: Position
): Unit = {
val Op.Arraylength(v) = op
val arr = genVal(buf, v)
val sig = arrayLengthSig
val func = arrayLength
genGuardNotNull(buf, arr)
val arrTy = Type.StructValue(Seq(Type.Ptr, Type.Int))
val lenPtr = buf.elem(arrTy, arr, Seq(Val.Int(0), Val.Int(1)), unwind)
buf.let(n, Op.Load(Type.Int, lenPtr), unwind)
}
def genStringVal(value: String): Val = {
val StringCls = ClassRef.unapply(Rt.StringName).get
val CharArrayCls = ClassRef.unapply(CharArrayName).get
val chars = value.toCharArray
val charsLength = Val.Int(chars.length)
val charsConst = Val.Const(
Val.StructValue(
Seq(
rtti(CharArrayCls).const,
charsLength,
Val.Int(0), // padding to get next field aligned properly
Val.ArrayValue(Type.Char, chars.toSeq.map(Val.Char(_)))
)
)
)
val fieldValues = stringFieldNames.map {
case Rt.StringValueName => charsConst
case Rt.StringOffsetName => Val.Int(0)
case Rt.StringCountName => charsLength
case Rt.StringCachedHashCodeName => Val.Int(stringHashCode(value))
case _ => util.unreachable
}
Val.Const(Val.StructValue(rtti(StringCls).const +: fieldValues))
}
}
// 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 = Type.Function(Seq(Type.Ptr, Type.Long), Type.Ptr)
val allocSmallName = extern("scalanative_alloc_small")
val alloc = Val.Global(allocSmallName, allocSig)
val largeAllocName = extern("scalanative_alloc_large")
val largeAlloc = Val.Global(largeAllocName, allocSig)
val dyndispatchName = extern("scalanative_dyndispatch")
val dyndispatchSig =
Type.Function(Seq(Type.Ptr, Type.Int), Type.Ptr)
val dyndispatch = Val.Global(dyndispatchName, dyndispatchSig)
val excptnGlobal = Global.Top("java.lang.NoSuchMethodException")
val excptnInitGlobal =
Global.Member(excptnGlobal, Sig.Ctor(Seq(nir.Rt.String)))
val excInitSig = Type.Function(
Seq(Type.Ref(excptnGlobal), Type.Ref(Global.Top("java.lang.String"))),
Type.Unit
)
val excInit = Val.Global(excptnInitGlobal, Type.Ptr)
val CharArrayName =
Global.Top("scala.scalanative.runtime.CharArray")
val BoxesRunTime = Global.Top("scala.runtime.BoxesRunTime$")
val RuntimeBoxes = Global.Top("scala.scalanative.runtime.Boxes$")
val BoxTo: Map[Type, Global] = Type.boxClasses.map { cls =>
val name = cls.asInstanceOf[Global.Top].id
val boxty = Type.Ref(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(Sig.Method(id, tys))
boxty -> meth
}.toMap
val UnboxTo: Map[Type, Global] = Type.boxClasses.map { cls =>
val name = cls.asInstanceOf[Global.Top].id
val boxty = Type.Ref(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(Sig.Method(id, tys))
boxty -> meth
}.toMap
private def extern(id: String): Global =
Global.Member(Global.Top("__"), Sig.Extern(id))
val unitName = Global.Top("scala.scalanative.runtime.BoxedUnit$")
val unitInstance = unitName.member(Sig.Generated("instance"))
val unit = Val.Global(unitInstance, Type.Ptr)
val throwName = extern("scalanative_throw")
val throwSig = Type.Function(Seq(Type.Ptr), Type.Nothing)
val throw_ = Val.Global(throwName, Type.Ptr)
val arrayAlloc = Type.typeToArray.map {
case (ty, arrname) =>
val Global.Top(id) = arrname
val arrcls = Type.Ref(arrname)
ty -> Global.Member(
Global.Top(id + "$"),
Sig.Method("alloc", Seq(Type.Int, arrcls))
)
}.toMap
val arrayAllocSig = Type.typeToArray.map {
case (ty, arrname) =>
val Global.Top(id) = arrname
ty -> Type.Function(
Seq(Type.Ref(Global.Top(id + "$")), Type.Int),
Type.Ref(arrname)
)
}.toMap
val arraySnapshot = Type.typeToArray.map {
case (ty, arrname) =>
val Global.Top(id) = arrname
val arrcls = Type.Ref(arrname)
ty -> Global.Member(
Global.Top(id + "$"),
Sig.Method("snapshot", Seq(Type.Int, Type.Ptr, arrcls))
)
}.toMap
val arraySnapshotSig = Type.typeToArray.map {
case (ty, arrname) =>
val Global.Top(id) = arrname
ty -> Type.Function(
Seq(Type.Ref(Global.Top(id + "$")), Type.Int, Type.Ptr),
Type.Ref(arrname)
)
}.toMap
val arrayApplyGeneric = Type.typeToArray.map {
case (ty, arrname) =>
ty -> Global.Member(
arrname,
Sig.Method("apply", Seq(Type.Int, nir.Rt.Object))
)
}
val arrayApply = Type.typeToArray.map {
case (ty, arrname) =>
ty -> Global.Member(arrname, Sig.Method("apply", Seq(Type.Int, ty)))
}.toMap
val arrayApplySig = Type.typeToArray.map {
case (ty, arrname) =>
ty -> Type.Function(Seq(Type.Ref(arrname), Type.Int), ty)
}.toMap
val arrayUpdateGeneric = Type.typeToArray.map {
case (ty, arrname) =>
ty -> Global.Member(
arrname,
Sig.Method("update", Seq(Type.Int, nir.Rt.Object, Type.Unit))
)
}
val arrayUpdate = Type.typeToArray.map {
case (ty, arrname) =>
ty -> Global.Member(
arrname,
Sig.Method("update", Seq(Type.Int, ty, Type.Unit))
)
}.toMap
val arrayUpdateSig = Type.typeToArray.map {
case (ty, arrname) =>
ty -> Type.Function(Seq(Type.Ref(arrname), Type.Int, ty), Type.Unit)
}.toMap
val arrayLength =
Global.Member(
Global.Top("scala.scalanative.runtime.Array"),
Sig.Method("length", Seq(Type.Int))
)
val arrayLengthSig =
Type.Function(
Seq(Type.Ref(Global.Top("scala.scalanative.runtime.Array"))),
Type.Int
)
val throwDivisionByZeroTy =
Type.Function(Seq(Rt.Runtime), Type.Nothing)
val throwDivisionByZero =
Global.Member(
Rt.Runtime.name,
Sig.Method("throwDivisionByZero", Seq(Type.Nothing))
)
val throwDivisionByZeroVal =
Val.Global(throwDivisionByZero, Type.Ptr)
val throwClassCastTy =
Type.Function(Seq(Rt.Runtime, Type.Ptr, Type.Ptr), Type.Nothing)
val throwClassCast =
Global.Member(
Rt.Runtime.name,
Sig.Method("throwClassCast", Seq(Type.Ptr, Type.Ptr, Type.Nothing))
)
val throwClassCastVal =
Val.Global(throwClassCast, Type.Ptr)
val throwNullPointerTy =
Type.Function(Seq(Rt.Runtime), Type.Nothing)
val throwNullPointer =
Global.Member(
Rt.Runtime.name,
Sig.Method("throwNullPointer", Seq(Type.Nothing))
)
val throwNullPointerVal =
Val.Global(throwNullPointer, Type.Ptr)
val throwUndefinedTy =
Type.Function(Seq(Type.Ptr), Type.Nothing)
val throwUndefined =
Global.Member(
Rt.Runtime.name,
Sig.Method("throwUndefined", Seq(Type.Nothing))
)
val throwUndefinedVal =
Val.Global(throwUndefined, Type.Ptr)
val throwOutOfBoundsTy =
Type.Function(Seq(Type.Ptr, Type.Int), Type.Nothing)
val throwOutOfBounds =
Global.Member(
Rt.Runtime.name,
Sig.Method("throwOutOfBounds", Seq(Type.Int, Type.Nothing))
)
val throwOutOfBoundsVal =
Val.Global(throwOutOfBounds, Type.Ptr)
val throwNoSuchMethodTy =
Type.Function(Seq(Type.Ptr, Type.Ptr), Type.Nothing)
val throwNoSuchMethod =
Global.Member(
Rt.Runtime.name,
Sig.Method("throwNoSuchMethod", Seq(Rt.String, Type.Nothing))
)
val throwNoSuchMethodVal =
Val.Global(throwNoSuchMethod, Type.Ptr)
val RuntimeNull = Type.Ref(Global.Top("scala.runtime.Null$"))
val RuntimeNothing = Type.Ref(Global.Top("scala.runtime.Nothing$"))
val injects: Seq[Defn] = {
implicit val pos = Position.NoPosition
val buf = mutable.UnrolledBuffer.empty[Defn]
buf += Defn.Declare(Attrs.None, allocSmallName, allocSig)
buf += Defn.Declare(Attrs.None, largeAllocName, allocSig)
buf += Defn.Declare(Attrs.None, dyndispatchName, dyndispatchSig)
buf += Defn.Declare(Attrs.None, throwName, throwSig)
buf.toSeq
}
val depends: Seq[Global] = {
val buf = mutable.UnrolledBuffer.empty[Global]
buf += Rt.ClassName
buf += Rt.ClassIdName
buf += Rt.ClassTraitIdName
buf += Rt.ClassNameName
buf += Rt.ClassSizeName
buf += Rt.ClassIdRangeUntilName
buf += Rt.StringName
buf += Rt.StringValueName
buf += Rt.StringOffsetName
buf += Rt.StringCountName
buf += Rt.StringCachedHashCodeName
buf += CharArrayName
buf += BoxesRunTime
buf += RuntimeBoxes
buf += unitName
buf ++= BoxTo.values
buf ++= UnboxTo.values
buf += arrayLength
buf ++= arrayAlloc.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
buf.toSeq
}
}